Something went wrong
An error occurred while loading the application.
@@ -135,16 +186,59 @@ export const ErrorPage: Component
= (props) => {
label="Error Details"
hideLabel
/>
-
+
+
+
Server
+
+ setServerUrl(e.currentTarget.value)}
+ placeholder="http://localhost:4096"
+ class="flex-1 font-mono"
+ onKeyDown={(e: KeyboardEvent) => {
+ if (e.key === "Enter" && inputValid()) {
+ applyServerUrl()
+ }
+ }}
+ />
+
+
+
+ Enter a valid HTTP or HTTPS URL.
+
+
+
+
+
+ This HTTP URL will be blocked by your browser (mixed content). Use{" "}
+ localhost or an HTTPS URL instead.
+
+
+
+
+
+
+
+
+
+
+ Using custom server URL. Reset to use default.
+
+
+
- Please report this error to the OpenCode team
+ Please report this error to the shuvcode team
- {/*
*/}
- {/* */}
- {/* */}
+
+
+
}
>
-
+
diff --git a/packages/ui/src/components/session-turn.css b/packages/ui/src/components/session-turn.css
index 86f7b7fe38c..fff3385245d 100644
--- a/packages/ui/src/components/session-turn.css
+++ b/packages/ui/src/components/session-turn.css
@@ -343,6 +343,8 @@
.error-card {
color: var(--text-on-critical-base);
+ max-height: 240px;
+ overflow-y: auto;
}
[data-slot="session-turn-collapsible-content-inner"] {
diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx
index ce4845a71c0..8cb4263872e 100644
--- a/packages/ui/src/components/session-turn.tsx
+++ b/packages/ui/src/components/session-turn.tsx
@@ -328,6 +328,12 @@ export function SessionTurn(
}
})
+ createEffect(() => {
+ if (permissionParts().length > 0) {
+ autoScroll.forceScrollToBottom()
+ }
+ })
+
createEffect(() => {
if (working() || !isLastUserMessage()) return
diff --git a/packages/ui/src/components/tabs.css b/packages/ui/src/components/tabs.css
index d60edc5c509..3ec7ece904e 100644
--- a/packages/ui/src/components/tabs.css
+++ b/packages/ui/src/components/tabs.css
@@ -205,4 +205,70 @@
/* [data-slot="tabs-content"] { */
/* } */
}
+
+ &[data-orientation="vertical"] {
+ flex-direction: row;
+
+ [data-slot="tabs-list"] {
+ flex-direction: column;
+ width: auto;
+ height: 100%;
+ overflow-x: hidden;
+ overflow-y: auto;
+
+ &::after {
+ width: 100%;
+ height: auto;
+ flex-grow: 1;
+ border-bottom: none;
+ border-right: 1px solid var(--border-weak-base);
+ }
+ }
+
+ [data-slot="tabs-trigger-wrapper"] {
+ width: 100%;
+ height: auto;
+ border-bottom: none;
+ border-right: 1px solid var(--border-weak-base);
+
+ &:has([data-selected]) {
+ border-right-color: transparent;
+ }
+ }
+
+ [data-slot="tabs-content"] {
+ overflow-x: auto;
+ overflow-y: auto;
+ }
+
+ &[data-variant="alt"] {
+ [data-slot="tabs-list"] {
+ padding-left: 0;
+ padding-right: 0;
+ padding-top: 24px;
+ padding-bottom: 24px;
+ border-bottom: none;
+ border-right: 1px solid var(--border-weak-base);
+
+ &::after {
+ border: none;
+ }
+ }
+
+ [data-slot="tabs-trigger-wrapper"] {
+ border-bottom: none;
+ border-right-width: 2px;
+ border-right-style: solid;
+ border-right-color: transparent;
+
+ [data-slot="tabs-trigger"] {
+ border-bottom: none;
+ }
+
+ &:has([data-selected]) {
+ border-right-color: var(--icon-strong-base);
+ }
+ }
+ }
+ }
}
diff --git a/packages/ui/src/components/tabs.tsx b/packages/ui/src/components/tabs.tsx
index d91ad3c4156..0a4d5b91a2c 100644
--- a/packages/ui/src/components/tabs.tsx
+++ b/packages/ui/src/components/tabs.tsx
@@ -4,6 +4,7 @@ import type { ComponentProps, ParentProps } from "solid-js"
export interface TabsProps extends ComponentProps {
variant?: "normal" | "alt"
+ orientation?: "horizontal" | "vertical"
}
export interface TabsListProps extends ComponentProps {}
export interface TabsTriggerProps extends ComponentProps {
@@ -16,12 +17,14 @@ export interface TabsTriggerProps extends ComponentProps
export interface TabsContentProps extends ComponentProps {}
function TabsRoot(props: TabsProps) {
- const [split, rest] = splitProps(props, ["class", "classList", "variant"])
+ const [split, rest] = splitProps(props, ["class", "classList", "variant", "orientation"])
return (
void)
- dismissAfter?: boolean
}
export interface ToastOptions {
@@ -132,10 +131,8 @@ export function showToast(options: ToastOptions | string) {
onClick={() => {
if (typeof action.onClick === "function") {
action.onClick()
- if (action.dismissAfter) toaster.dismiss(props.toastId)
- } else {
- toaster.dismiss(props.toastId)
}
+ toaster.dismiss(props.toastId)
}}
>
{action.label}
diff --git a/packages/ui/src/context/dialog.tsx b/packages/ui/src/context/dialog.tsx
index 8e1a6aad8e5..f85eb48df80 100644
--- a/packages/ui/src/context/dialog.tsx
+++ b/packages/ui/src/context/dialog.tsx
@@ -1,6 +1,5 @@
import {
createContext,
- createEffect,
createSignal,
getOwner,
Owner,
@@ -70,9 +69,6 @@ function init() {
export function DialogProvider(props: ParentProps) {
const ctx = init()
- createEffect(() => {
- console.log("active", ctx.active)
- })
return (
{props.children}
diff --git a/packages/ui/src/hooks/create-auto-scroll.tsx b/packages/ui/src/hooks/create-auto-scroll.tsx
index e262b7c6945..b780f47c666 100644
--- a/packages/ui/src/hooks/create-auto-scroll.tsx
+++ b/packages/ui/src/hooks/create-auto-scroll.tsx
@@ -35,6 +35,22 @@ export function createAutoScroll(options: AutoScrollOptions) {
})
}
+ function forceScrollToBottom() {
+ if (!scrollRef) return
+
+ setStore("userScrolled", false)
+ isAutoScrolling = true
+ if (autoScrollTimeout) clearTimeout(autoScrollTimeout)
+ autoScrollTimeout = setTimeout(() => {
+ isAutoScrolling = false
+ }, 1000)
+
+ scrollRef.scrollTo({
+ top: scrollRef.scrollHeight,
+ behavior: "smooth",
+ })
+ }
+
function handleScroll() {
if (!scrollRef) return
@@ -161,6 +177,7 @@ export function createAutoScroll(options: AutoScrollOptions) {
handleScroll,
handleInteraction,
scrollToBottom,
+ forceScrollToBottom,
userScrolled: () => store.userScrolled,
}
}
diff --git a/packages/ui/src/styles/theme.css b/packages/ui/src/styles/theme.css
index b3b163aad23..a8743a2110b 100644
--- a/packages/ui/src/styles/theme.css
+++ b/packages/ui/src/styles/theme.css
@@ -68,7 +68,7 @@
color-scheme: light;
--text-mix-blend-mode: multiply;
- /* OC-1-light */
+ /* OC-1 fallback variables (light) */
--background-base: #f8f7f7;
--background-weak: var(--smoke-light-3);
--background-strong: var(--smoke-light-1);
@@ -324,7 +324,7 @@
color-scheme: dark;
--text-mix-blend-mode: plus-lighter;
- /* OC-1-dark */
+ /* OC-1 fallback variables (dark) */
--background-base: var(--smoke-dark-1);
--background-weak: #1c1717;
--background-strong: #151313;
diff --git a/packages/ui/src/theme/color.ts b/packages/ui/src/theme/color.ts
new file mode 100644
index 00000000000..f0e15211e95
--- /dev/null
+++ b/packages/ui/src/theme/color.ts
@@ -0,0 +1,194 @@
+import type { HexColor, OklchColor } from "./types"
+
+export function hexToRgb(hex: HexColor): { r: number; g: number; b: number } {
+ const h = hex.replace("#", "")
+ const full =
+ h.length === 3
+ ? h
+ .split("")
+ .map((c) => c + c)
+ .join("")
+ : h
+
+ const num = parseInt(full, 16)
+ return {
+ r: ((num >> 16) & 255) / 255,
+ g: ((num >> 8) & 255) / 255,
+ b: (num & 255) / 255,
+ }
+}
+
+export function rgbToHex(r: number, g: number, b: number): HexColor {
+ const toHex = (v: number) => {
+ const clamped = Math.max(0, Math.min(1, v))
+ const int = Math.round(clamped * 255)
+ return int.toString(16).padStart(2, "0")
+ }
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`
+}
+
+function linearToSrgb(c: number): number {
+ if (c <= 0.0031308) return c * 12.92
+ return 1.055 * Math.pow(c, 1 / 2.4) - 0.055
+}
+
+function srgbToLinear(c: number): number {
+ if (c <= 0.04045) return c / 12.92
+ return Math.pow((c + 0.055) / 1.055, 2.4)
+}
+
+export function rgbToOklch(r: number, g: number, b: number): OklchColor {
+ const lr = srgbToLinear(r)
+ const lg = srgbToLinear(g)
+ const lb = srgbToLinear(b)
+
+ const l_ = 0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb
+ const m_ = 0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb
+ const s_ = 0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb
+
+ const l = Math.cbrt(l_)
+ const m = Math.cbrt(m_)
+ const s = Math.cbrt(s_)
+
+ const L = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s
+ const a = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s
+ const bOk = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s
+
+ const C = Math.sqrt(a * a + bOk * bOk)
+ let H = Math.atan2(bOk, a) * (180 / Math.PI)
+ if (H < 0) H += 360
+
+ return { l: L, c: C, h: H }
+}
+
+export function oklchToRgb(oklch: OklchColor): { r: number; g: number; b: number } {
+ const { l: L, c: C, h: H } = oklch
+
+ const a = C * Math.cos((H * Math.PI) / 180)
+ const b = C * Math.sin((H * Math.PI) / 180)
+
+ const l = L + 0.3963377774 * a + 0.2158037573 * b
+ const m = L - 0.1055613458 * a - 0.0638541728 * b
+ const s = L - 0.0894841775 * a - 1.291485548 * b
+
+ const l3 = l * l * l
+ const m3 = m * m * m
+ const s3 = s * s * s
+
+ const lr = 4.0767416621 * l3 - 3.3077115913 * m3 + 0.2309699292 * s3
+ const lg = -1.2684380046 * l3 + 2.6097574011 * m3 - 0.3413193965 * s3
+ const lb = -0.0041960863 * l3 - 0.7034186147 * m3 + 1.707614701 * s3
+
+ return {
+ r: linearToSrgb(lr),
+ g: linearToSrgb(lg),
+ b: linearToSrgb(lb),
+ }
+}
+
+export function hexToOklch(hex: HexColor): OklchColor {
+ const { r, g, b } = hexToRgb(hex)
+ return rgbToOklch(r, g, b)
+}
+
+export function oklchToHex(oklch: OklchColor): HexColor {
+ const { r, g, b } = oklchToRgb(oklch)
+ return rgbToHex(r, g, b)
+}
+
+export function generateScale(seed: HexColor, isDark: boolean): HexColor[] {
+ const base = hexToOklch(seed)
+ const scale: HexColor[] = []
+
+ const lightSteps = isDark
+ ? [0.15, 0.18, 0.22, 0.26, 0.32, 0.38, 0.46, 0.56, base.l, base.l - 0.05, 0.75, 0.93]
+ : [0.99, 0.97, 0.94, 0.9, 0.85, 0.79, 0.72, 0.64, base.l, base.l + 0.05, 0.45, 0.25]
+
+ const chromaMultipliers = isDark
+ ? [0.15, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.85, 1, 1, 0.9, 0.6]
+ : [0.1, 0.15, 0.25, 0.35, 0.45, 0.55, 0.7, 0.85, 1, 1, 0.95, 0.85]
+
+ for (let i = 0; i < 12; i++) {
+ scale.push(
+ oklchToHex({
+ l: lightSteps[i],
+ c: base.c * chromaMultipliers[i],
+ h: base.h,
+ }),
+ )
+ }
+
+ return scale
+}
+
+export function generateNeutralScale(seed: HexColor, isDark: boolean): HexColor[] {
+ const base = hexToOklch(seed)
+ const scale: HexColor[] = []
+ const neutralChroma = Math.min(base.c, 0.02)
+
+ const lightSteps = isDark
+ ? [0.13, 0.16, 0.2, 0.24, 0.28, 0.33, 0.4, 0.52, 0.58, 0.66, 0.82, 0.96]
+ : [0.995, 0.98, 0.96, 0.94, 0.91, 0.88, 0.84, 0.78, 0.62, 0.56, 0.46, 0.2]
+
+ for (let i = 0; i < 12; i++) {
+ scale.push(
+ oklchToHex({
+ l: lightSteps[i],
+ c: neutralChroma,
+ h: base.h,
+ }),
+ )
+ }
+
+ return scale
+}
+
+export function generateAlphaScale(scale: HexColor[], isDark: boolean): HexColor[] {
+ const alphas = isDark
+ ? [0.02, 0.04, 0.08, 0.12, 0.16, 0.2, 0.26, 0.36, 0.44, 0.52, 0.76, 0.96]
+ : [0.01, 0.03, 0.06, 0.09, 0.12, 0.15, 0.2, 0.28, 0.48, 0.56, 0.64, 0.88]
+
+ return scale.map((hex, i) => {
+ const { r, g, b } = hexToRgb(hex)
+ const a = alphas[i]
+
+ const bg = isDark ? 0 : 1
+ const blendedR = r * a + bg * (1 - a)
+ const blendedG = g * a + bg * (1 - a)
+ const blendedB = b * a + bg * (1 - a)
+
+ return rgbToHex(blendedR, blendedG, blendedB)
+ })
+}
+
+export function mixColors(color1: HexColor, color2: HexColor, amount: number): HexColor {
+ const c1 = hexToOklch(color1)
+ const c2 = hexToOklch(color2)
+
+ return oklchToHex({
+ l: c1.l + (c2.l - c1.l) * amount,
+ c: c1.c + (c2.c - c1.c) * amount,
+ h: c1.h + (c2.h - c1.h) * amount,
+ })
+}
+
+export function lighten(color: HexColor, amount: number): HexColor {
+ const oklch = hexToOklch(color)
+ return oklchToHex({
+ ...oklch,
+ l: Math.min(1, oklch.l + amount),
+ })
+}
+
+export function darken(color: HexColor, amount: number): HexColor {
+ const oklch = hexToOklch(color)
+ return oklchToHex({
+ ...oklch,
+ l: Math.max(0, oklch.l - amount),
+ })
+}
+
+export function withAlpha(color: HexColor, alpha: number): string {
+ const { r, g, b } = hexToRgb(color)
+ return `rgba(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)}, ${alpha})`
+}
diff --git a/packages/ui/src/theme/context.tsx b/packages/ui/src/theme/context.tsx
new file mode 100644
index 00000000000..c1c1637d674
--- /dev/null
+++ b/packages/ui/src/theme/context.tsx
@@ -0,0 +1,180 @@
+import { onMount, onCleanup, createEffect } from "solid-js"
+import { createStore } from "solid-js/store"
+import type { DesktopTheme } from "./types"
+import { resolveThemeVariant, themeToCss } from "./resolve"
+import { DEFAULT_THEMES } from "./default-themes"
+import { createSimpleContext } from "../context/helper"
+
+export type ColorScheme = "light" | "dark" | "system"
+
+const STORAGE_KEYS = {
+ THEME_ID: "opencode-theme-id",
+ COLOR_SCHEME: "opencode-color-scheme",
+ THEME_CSS_LIGHT: "opencode-theme-css-light",
+ THEME_CSS_DARK: "opencode-theme-css-dark",
+} as const
+
+const THEME_STYLE_ID = "oc-theme"
+
+function ensureThemeStyleElement(): HTMLStyleElement {
+ const existing = document.getElementById(THEME_STYLE_ID) as HTMLStyleElement | null
+ if (existing) return existing
+ const element = document.createElement("style")
+ element.id = THEME_STYLE_ID
+ document.head.appendChild(element)
+ return element
+}
+
+function getSystemMode(): "light" | "dark" {
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
+}
+
+function applyThemeCss(theme: DesktopTheme, themeId: string, mode: "light" | "dark") {
+ const isDark = mode === "dark"
+ const variant = isDark ? theme.dark : theme.light
+ const tokens = resolveThemeVariant(variant, isDark)
+ const css = themeToCss(tokens)
+
+ if (themeId !== "oc-1") {
+ try {
+ localStorage.setItem(isDark ? STORAGE_KEYS.THEME_CSS_DARK : STORAGE_KEYS.THEME_CSS_LIGHT, css)
+ } catch {}
+ }
+
+ const fullCss = `:root {
+ color-scheme: ${mode};
+ --text-mix-blend-mode: ${isDark ? "plus-lighter" : "multiply"};
+ ${css}
+}`
+
+ document.getElementById("oc-theme-preload")?.remove()
+ ensureThemeStyleElement().textContent = fullCss
+ document.documentElement.dataset.theme = themeId
+ document.documentElement.dataset.colorScheme = mode
+}
+
+function cacheThemeVariants(theme: DesktopTheme, themeId: string) {
+ if (themeId === "oc-1") return
+ for (const mode of ["light", "dark"] as const) {
+ const isDark = mode === "dark"
+ const variant = isDark ? theme.dark : theme.light
+ const tokens = resolveThemeVariant(variant, isDark)
+ const css = themeToCss(tokens)
+ try {
+ localStorage.setItem(isDark ? STORAGE_KEYS.THEME_CSS_DARK : STORAGE_KEYS.THEME_CSS_LIGHT, css)
+ } catch {}
+ }
+}
+
+export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
+ name: "Theme",
+ init: (props: { defaultTheme?: string }) => {
+ const [store, setStore] = createStore({
+ themes: DEFAULT_THEMES as Record,
+ themeId: props.defaultTheme ?? "oc-1",
+ colorScheme: "system" as ColorScheme,
+ mode: getSystemMode(),
+ previewThemeId: null as string | null,
+ previewScheme: null as ColorScheme | null,
+ })
+
+ onMount(() => {
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
+ const handler = () => {
+ if (store.colorScheme === "system") {
+ setStore("mode", getSystemMode())
+ }
+ }
+ mediaQuery.addEventListener("change", handler)
+ onCleanup(() => mediaQuery.removeEventListener("change", handler))
+
+ const savedTheme = localStorage.getItem(STORAGE_KEYS.THEME_ID)
+ const savedScheme = localStorage.getItem(STORAGE_KEYS.COLOR_SCHEME) as ColorScheme | null
+ if (savedTheme && store.themes[savedTheme]) {
+ setStore("themeId", savedTheme)
+ }
+ if (savedScheme) {
+ setStore("colorScheme", savedScheme)
+ if (savedScheme !== "system") {
+ setStore("mode", savedScheme)
+ }
+ }
+ const currentTheme = store.themes[store.themeId]
+ if (currentTheme) {
+ cacheThemeVariants(currentTheme, store.themeId)
+ }
+ })
+
+ createEffect(() => {
+ const theme = store.themes[store.themeId]
+ if (theme) {
+ applyThemeCss(theme, store.themeId, store.mode)
+ }
+ })
+
+ const setTheme = (id: string) => {
+ const theme = store.themes[id]
+ if (!theme) {
+ console.warn(`Theme "${id}" not found`)
+ return
+ }
+ setStore("themeId", id)
+ localStorage.setItem(STORAGE_KEYS.THEME_ID, id)
+ cacheThemeVariants(theme, id)
+ }
+
+ const setColorScheme = (scheme: ColorScheme) => {
+ setStore("colorScheme", scheme)
+ localStorage.setItem(STORAGE_KEYS.COLOR_SCHEME, scheme)
+ setStore("mode", scheme === "system" ? getSystemMode() : scheme)
+ }
+
+ return {
+ themeId: () => store.themeId,
+ colorScheme: () => store.colorScheme,
+ mode: () => store.mode,
+ themes: () => store.themes,
+ setTheme,
+ setColorScheme,
+ registerTheme: (theme: DesktopTheme) => setStore("themes", theme.id, theme),
+ previewTheme: (id: string) => {
+ const theme = store.themes[id]
+ if (!theme) return
+ setStore("previewThemeId", id)
+ const previewMode = store.previewScheme
+ ? store.previewScheme === "system"
+ ? getSystemMode()
+ : store.previewScheme
+ : store.mode
+ applyThemeCss(theme, id, previewMode)
+ },
+ previewColorScheme: (scheme: ColorScheme) => {
+ setStore("previewScheme", scheme)
+ const previewMode = scheme === "system" ? getSystemMode() : scheme
+ const id = store.previewThemeId ?? store.themeId
+ const theme = store.themes[id]
+ if (theme) {
+ applyThemeCss(theme, id, previewMode)
+ }
+ },
+ commitPreview: () => {
+ if (store.previewThemeId) {
+ setTheme(store.previewThemeId)
+ }
+ if (store.previewScheme) {
+ setColorScheme(store.previewScheme)
+ }
+ setStore("previewThemeId", null)
+ setStore("previewScheme", null)
+ },
+ cancelPreview: () => {
+ setStore("previewThemeId", null)
+ setStore("previewScheme", null)
+ const theme = store.themes[store.themeId]
+ if (theme) {
+ applyThemeCss(theme, store.themeId, store.mode)
+ }
+ },
+ }
+ },
+})
diff --git a/packages/ui/src/theme/default-themes.ts b/packages/ui/src/theme/default-themes.ts
new file mode 100644
index 00000000000..749d5e97ccb
--- /dev/null
+++ b/packages/ui/src/theme/default-themes.ts
@@ -0,0 +1,35 @@
+import type { DesktopTheme } from "./types"
+import oc1ThemeJson from "./themes/oc-1.json"
+import tokyoThemeJson from "./themes/tokyonight.json"
+import draculaThemeJson from "./themes/dracula.json"
+import monokaiThemeJson from "./themes/monokai.json"
+import solarizedThemeJson from "./themes/solarized.json"
+import nordThemeJson from "./themes/nord.json"
+import catppuccinThemeJson from "./themes/catppuccin.json"
+import ayuThemeJson from "./themes/ayu.json"
+import oneDarkProThemeJson from "./themes/onedarkpro.json"
+import shadesOfPurpleThemeJson from "./themes/shadesofpurple.json"
+
+export const oc1Theme = oc1ThemeJson as DesktopTheme
+export const tokyonightTheme = tokyoThemeJson as DesktopTheme
+export const draculaTheme = draculaThemeJson as DesktopTheme
+export const monokaiTheme = monokaiThemeJson as DesktopTheme
+export const solarizedTheme = solarizedThemeJson as DesktopTheme
+export const nordTheme = nordThemeJson as DesktopTheme
+export const catppuccinTheme = catppuccinThemeJson as DesktopTheme
+export const ayuTheme = ayuThemeJson as DesktopTheme
+export const oneDarkProTheme = oneDarkProThemeJson as DesktopTheme
+export const shadesOfPurpleTheme = shadesOfPurpleThemeJson as DesktopTheme
+
+export const DEFAULT_THEMES: Record = {
+ "oc-1": oc1Theme,
+ tokyonight: tokyonightTheme,
+ dracula: draculaTheme,
+ monokai: monokaiTheme,
+ solarized: solarizedTheme,
+ nord: nordTheme,
+ catppuccin: catppuccinTheme,
+ ayu: ayuTheme,
+ onedarkpro: oneDarkProTheme,
+ shadesofpurple: shadesOfPurpleTheme,
+}
diff --git a/packages/ui/src/theme/desktop-theme.schema.json b/packages/ui/src/theme/desktop-theme.schema.json
new file mode 100644
index 00000000000..b60a8f37cad
--- /dev/null
+++ b/packages/ui/src/theme/desktop-theme.schema.json
@@ -0,0 +1,104 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "https://opencode.ai/desktop-theme.json",
+ "title": "OpenCode Desktop Theme",
+ "description": "A theme definition for the OpenCode desktop application",
+ "type": "object",
+ "required": ["name", "id", "light", "dark"],
+ "properties": {
+ "$schema": {
+ "type": "string",
+ "description": "JSON Schema reference"
+ },
+ "name": {
+ "type": "string",
+ "description": "Human-readable theme name"
+ },
+ "id": {
+ "type": "string",
+ "description": "Unique theme identifier (slug)",
+ "pattern": "^[a-z0-9-]+$"
+ },
+ "light": {
+ "$ref": "#/definitions/ThemeVariant",
+ "description": "Light mode color variant"
+ },
+ "dark": {
+ "$ref": "#/definitions/ThemeVariant",
+ "description": "Dark mode color variant"
+ }
+ },
+ "definitions": {
+ "HexColor": {
+ "type": "string",
+ "pattern": "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$",
+ "description": "A hex color value like #fff, #ffff, #ffffff, or #ffffffff"
+ },
+ "ColorValue": {
+ "type": "string",
+ "pattern": "^(#([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})|var\(--[a-z0-9-]+\))$",
+ "description": "Either a hex color value (#rgb/#rgba/#rrggbb/#rrggbbaa) or a CSS variable reference"
+ },
+ "ThemeSeedColors": {
+ "type": "object",
+ "description": "The minimum set of colors needed to generate a theme",
+ "required": ["neutral", "primary", "success", "warning", "error", "info", "interactive", "diffAdd", "diffDelete"],
+ "properties": {
+ "neutral": {
+ "$ref": "#/definitions/HexColor",
+ "description": "Base neutral color for generating the gray scale"
+ },
+ "primary": {
+ "$ref": "#/definitions/HexColor",
+ "description": "Primary brand/accent color"
+ },
+ "success": {
+ "$ref": "#/definitions/HexColor",
+ "description": "Success state color (typically green)"
+ },
+ "warning": {
+ "$ref": "#/definitions/HexColor",
+ "description": "Warning state color (typically yellow/orange)"
+ },
+ "error": {
+ "$ref": "#/definitions/HexColor",
+ "description": "Error/critical state color (typically red)"
+ },
+ "info": {
+ "$ref": "#/definitions/HexColor",
+ "description": "Informational state color (typically purple/blue)"
+ },
+ "interactive": {
+ "$ref": "#/definitions/HexColor",
+ "description": "Interactive element color (links, buttons)"
+ },
+ "diffAdd": {
+ "$ref": "#/definitions/HexColor",
+ "description": "Color for diff additions"
+ },
+ "diffDelete": {
+ "$ref": "#/definitions/HexColor",
+ "description": "Color for diff deletions"
+ }
+ }
+ },
+ "ThemeVariant": {
+ "type": "object",
+ "description": "A theme variant (light or dark) with seed colors and optional overrides",
+ "required": ["seeds"],
+ "properties": {
+ "seeds": {
+ "$ref": "#/definitions/ThemeSeedColors",
+ "description": "Seed colors used to generate the full palette"
+ },
+ "overrides": {
+ "type": "object",
+ "description": "Optional direct overrides for any CSS variable (without -- prefix)",
+ "additionalProperties": {
+ "$ref": "#/definitions/ColorValue"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/ui/src/theme/index.ts b/packages/ui/src/theme/index.ts
new file mode 100644
index 00000000000..d8e0461be36
--- /dev/null
+++ b/packages/ui/src/theme/index.ts
@@ -0,0 +1,44 @@
+export type {
+ DesktopTheme,
+ ThemeSeedColors,
+ ThemeVariant,
+ HexColor,
+ OklchColor,
+ ResolvedTheme,
+ ColorValue,
+ CssVarRef,
+} from "./types"
+
+export {
+ hexToRgb,
+ rgbToHex,
+ hexToOklch,
+ oklchToHex,
+ rgbToOklch,
+ oklchToRgb,
+ generateScale,
+ generateNeutralScale,
+ generateAlphaScale,
+ mixColors,
+ lighten,
+ darken,
+ withAlpha,
+} from "./color"
+
+export { resolveThemeVariant, resolveTheme, themeToCss } from "./resolve"
+export { applyTheme, loadThemeFromUrl, getActiveTheme, removeTheme, setColorScheme } from "./loader"
+export { ThemeProvider, useTheme, type ColorScheme } from "./context"
+
+export {
+ DEFAULT_THEMES,
+ oc1Theme,
+ tokyonightTheme,
+ draculaTheme,
+ monokaiTheme,
+ solarizedTheme,
+ nordTheme,
+ catppuccinTheme,
+ ayuTheme,
+ oneDarkProTheme,
+ shadesOfPurpleTheme,
+} from "./default-themes"
diff --git a/packages/ui/src/theme/loader.ts b/packages/ui/src/theme/loader.ts
new file mode 100644
index 00000000000..0f61076a001
--- /dev/null
+++ b/packages/ui/src/theme/loader.ts
@@ -0,0 +1,103 @@
+import type { DesktopTheme, ResolvedTheme } from "./types"
+import { resolveThemeVariant, themeToCss } from "./resolve"
+
+let activeTheme: DesktopTheme | null = null
+const THEME_STYLE_ID = "opencode-theme"
+
+function ensureLoaderStyleElement(): HTMLStyleElement {
+ const existing = document.getElementById(THEME_STYLE_ID) as HTMLStyleElement | null
+ if (existing) {
+ return existing
+ }
+ const element = document.createElement("style")
+ element.id = THEME_STYLE_ID
+ document.head.appendChild(element)
+ return element
+}
+
+export function applyTheme(theme: DesktopTheme, themeId?: string): void {
+ activeTheme = theme
+ const lightTokens = resolveThemeVariant(theme.light, false)
+ const darkTokens = resolveThemeVariant(theme.dark, true)
+ const targetThemeId = themeId ?? theme.id
+ const css = buildThemeCss(lightTokens, darkTokens, targetThemeId)
+ const themeStyleElement = ensureLoaderStyleElement()
+ themeStyleElement.textContent = css
+ document.documentElement.setAttribute("data-theme", targetThemeId)
+}
+
+function buildThemeCss(light: ResolvedTheme, dark: ResolvedTheme, themeId: string): string {
+ const isDefaultTheme = themeId === "oc-1"
+ const lightCss = themeToCss(light)
+ const darkCss = themeToCss(dark)
+
+ if (isDefaultTheme) {
+ return `
+:root {
+ color-scheme: light;
+ --text-mix-blend-mode: multiply;
+
+ ${lightCss}
+
+ @media (prefers-color-scheme: dark) {
+ color-scheme: dark;
+ --text-mix-blend-mode: plus-lighter;
+
+ ${darkCss}
+ }
+}
+`
+ }
+
+ return `
+html[data-theme="${themeId}"] {
+ color-scheme: light;
+ --text-mix-blend-mode: multiply;
+
+ ${lightCss}
+
+ @media (prefers-color-scheme: dark) {
+ color-scheme: dark;
+ --text-mix-blend-mode: plus-lighter;
+
+ ${darkCss}
+ }
+}
+`
+}
+
+export async function loadThemeFromUrl(url: string): Promise {
+ const response = await fetch(url)
+ if (!response.ok) {
+ throw new Error(`Failed to load theme from ${url}: ${response.statusText}`)
+ }
+ return response.json()
+}
+
+export function getActiveTheme(): DesktopTheme | null {
+ const activeId = document.documentElement.getAttribute("data-theme")
+ if (!activeId) {
+ return null
+ }
+ if (activeTheme?.id === activeId) {
+ return activeTheme
+ }
+ return null
+}
+
+export function removeTheme(): void {
+ activeTheme = null
+ const existingElement = document.getElementById(THEME_STYLE_ID)
+ if (existingElement) {
+ existingElement.remove()
+ }
+ document.documentElement.removeAttribute("data-theme")
+}
+
+export function setColorScheme(scheme: "light" | "dark" | "auto"): void {
+ if (scheme === "auto") {
+ document.documentElement.style.removeProperty("color-scheme")
+ } else {
+ document.documentElement.style.setProperty("color-scheme", scheme)
+ }
+}
diff --git a/packages/ui/src/theme/resolve.ts b/packages/ui/src/theme/resolve.ts
new file mode 100644
index 00000000000..7339088c48f
--- /dev/null
+++ b/packages/ui/src/theme/resolve.ts
@@ -0,0 +1,325 @@
+import type { ColorValue, DesktopTheme, HexColor, ResolvedTheme, ThemeVariant } from "./types"
+import { generateNeutralScale, generateScale, hexToOklch, oklchToHex, withAlpha } from "./color"
+
+export function resolveThemeVariant(variant: ThemeVariant, isDark: boolean): ResolvedTheme {
+ const { seeds, overrides = {} } = variant
+
+ const neutral = generateNeutralScale(seeds.neutral, isDark)
+ const primary = generateScale(seeds.primary, isDark)
+ const success = generateScale(seeds.success, isDark)
+ const warning = generateScale(seeds.warning, isDark)
+ const error = generateScale(seeds.error, isDark)
+ const info = generateScale(seeds.info, isDark)
+ const interactive = generateScale(seeds.interactive, isDark)
+ const diffAdd = generateScale(seeds.diffAdd, isDark)
+ const diffDelete = generateScale(seeds.diffDelete, isDark)
+
+ const neutralAlpha = generateNeutralAlphaScale(neutral, isDark)
+
+ const tokens: ResolvedTheme = {}
+
+ tokens["background-base"] = neutral[0]
+ tokens["background-weak"] = neutral[2]
+ tokens["background-strong"] = neutral[0]
+ tokens["background-stronger"] = isDark ? neutral[1] : "#fcfcfc"
+
+ tokens["surface-base"] = neutralAlpha[1]
+ tokens["base"] = neutralAlpha[1]
+ tokens["surface-base-hover"] = neutralAlpha[2]
+ tokens["surface-base-active"] = neutralAlpha[2]
+ tokens["surface-base-interactive-active"] = withAlpha(interactive[2], 0.3) as ColorValue
+ tokens["base2"] = neutralAlpha[1]
+ tokens["base3"] = neutralAlpha[1]
+ tokens["surface-inset-base"] = neutralAlpha[1]
+ tokens["surface-inset-base-hover"] = neutralAlpha[2]
+ tokens["surface-inset-strong"] = isDark
+ ? (withAlpha(neutral[0], 0.5) as ColorValue)
+ : (withAlpha(neutral[3], 0.09) as ColorValue)
+ tokens["surface-inset-strong-hover"] = tokens["surface-inset-strong"]
+ tokens["surface-raised-base"] = neutralAlpha[0]
+ tokens["surface-float-base"] = isDark ? neutral[0] : neutral[11]
+ tokens["surface-float-base-hover"] = isDark ? neutral[1] : neutral[10]
+ tokens["surface-raised-base-hover"] = neutralAlpha[1]
+ tokens["surface-raised-base-active"] = neutralAlpha[2]
+ tokens["surface-raised-strong"] = isDark ? neutralAlpha[3] : neutral[0]
+ tokens["surface-raised-strong-hover"] = isDark ? neutralAlpha[5] : "#ffffff"
+ tokens["surface-raised-stronger"] = isDark ? neutralAlpha[5] : "#ffffff"
+ tokens["surface-raised-stronger-hover"] = isDark ? neutralAlpha[6] : "#ffffff"
+ tokens["surface-weak"] = neutralAlpha[2]
+ tokens["surface-weaker"] = neutralAlpha[3]
+ tokens["surface-strong"] = isDark ? neutralAlpha[6] : "#ffffff"
+ tokens["surface-raised-stronger-non-alpha"] = isDark ? neutral[2] : "#ffffff"
+
+ tokens["surface-brand-base"] = primary[8]
+ tokens["surface-brand-hover"] = primary[9]
+
+ tokens["surface-interactive-base"] = interactive[2]
+ tokens["surface-interactive-hover"] = interactive[3]
+ tokens["surface-interactive-weak"] = interactive[1]
+ tokens["surface-interactive-weak-hover"] = interactive[2]
+
+ tokens["surface-success-base"] = success[2]
+ tokens["surface-success-weak"] = success[1]
+ tokens["surface-success-strong"] = success[8]
+ tokens["surface-warning-base"] = warning[2]
+ tokens["surface-warning-weak"] = warning[1]
+ tokens["surface-warning-strong"] = warning[8]
+ tokens["surface-critical-base"] = error[2]
+ tokens["surface-critical-weak"] = error[1]
+ tokens["surface-critical-strong"] = error[8]
+ tokens["surface-info-base"] = info[2]
+ tokens["surface-info-weak"] = info[1]
+ tokens["surface-info-strong"] = info[8]
+
+ tokens["surface-diff-unchanged-base"] = isDark ? neutral[0] : "#ffffff00"
+ tokens["surface-diff-skip-base"] = isDark ? neutralAlpha[0] : neutral[1]
+ tokens["surface-diff-hidden-base"] = interactive[isDark ? 1 : 2]
+ tokens["surface-diff-hidden-weak"] = interactive[isDark ? 0 : 1]
+ tokens["surface-diff-hidden-weaker"] = interactive[isDark ? 2 : 0]
+ tokens["surface-diff-hidden-strong"] = interactive[4]
+ tokens["surface-diff-hidden-stronger"] = interactive[isDark ? 10 : 8]
+ tokens["surface-diff-add-base"] = diffAdd[2]
+ tokens["surface-diff-add-weak"] = diffAdd[isDark ? 3 : 1]
+ tokens["surface-diff-add-weaker"] = diffAdd[isDark ? 2 : 0]
+ tokens["surface-diff-add-strong"] = diffAdd[4]
+ tokens["surface-diff-add-stronger"] = diffAdd[isDark ? 10 : 8]
+ tokens["surface-diff-delete-base"] = diffDelete[2]
+ tokens["surface-diff-delete-weak"] = diffDelete[isDark ? 3 : 1]
+ tokens["surface-diff-delete-weaker"] = diffDelete[isDark ? 2 : 0]
+ tokens["surface-diff-delete-strong"] = diffDelete[isDark ? 4 : 5]
+ tokens["surface-diff-delete-stronger"] = diffDelete[isDark ? 10 : 8]
+
+ tokens["input-base"] = isDark ? neutral[1] : neutral[0]
+ tokens["input-hover"] = neutral[1]
+ tokens["input-active"] = interactive[0]
+ tokens["input-selected"] = interactive[3]
+ tokens["input-focus"] = interactive[0]
+ tokens["input-disabled"] = neutral[3]
+
+ tokens["text-base"] = neutral[10]
+ tokens["text-weak"] = neutral[8]
+ tokens["text-weaker"] = neutral[7]
+ tokens["text-strong"] = neutral[11]
+ tokens["text-invert-base"] = isDark ? neutral[10] : neutralAlpha[10]
+ tokens["text-invert-weak"] = isDark ? neutral[8] : neutralAlpha[8]
+ tokens["text-invert-weaker"] = isDark ? neutral[7] : neutralAlpha[7]
+ tokens["text-invert-strong"] = isDark ? neutral[11] : neutralAlpha[11]
+ tokens["text-interactive-base"] = interactive[isDark ? 10 : 8]
+ tokens["text-on-brand-base"] = neutralAlpha[10]
+ tokens["text-on-interactive-base"] = isDark ? neutral[11] : neutral[0]
+ tokens["text-on-interactive-weak"] = neutralAlpha[10]
+ tokens["text-on-success-base"] = success[isDark ? 8 : 9]
+ tokens["text-on-critical-base"] = error[isDark ? 8 : 9]
+ tokens["text-on-critical-weak"] = error[7]
+ tokens["text-on-critical-strong"] = error[11]
+ tokens["text-on-warning-base"] = neutralAlpha[10]
+ tokens["text-on-info-base"] = neutralAlpha[10]
+ tokens["text-diff-add-base"] = diffAdd[10]
+ tokens["text-diff-delete-base"] = diffDelete[isDark ? 8 : 9]
+ tokens["text-diff-delete-strong"] = diffDelete[11]
+ tokens["text-diff-add-strong"] = diffAdd[isDark ? 7 : 11]
+ tokens["text-on-info-weak"] = neutralAlpha[8]
+ tokens["text-on-info-strong"] = neutralAlpha[11]
+ tokens["text-on-warning-weak"] = neutralAlpha[8]
+ tokens["text-on-warning-strong"] = neutralAlpha[11]
+ tokens["text-on-success-weak"] = success[isDark ? 7 : 5]
+ tokens["text-on-success-strong"] = success[11]
+ tokens["text-on-brand-weak"] = neutralAlpha[8]
+ tokens["text-on-brand-weaker"] = neutralAlpha[7]
+ tokens["text-on-brand-strong"] = neutralAlpha[11]
+
+ tokens["button-secondary-base"] = isDark ? neutral[2] : neutral[0]
+ tokens["button-secondary-hover"] = isDark ? neutral[3] : neutral[1]
+ tokens["button-ghost-hover"] = neutralAlpha[1]
+ tokens["button-ghost-hover2"] = neutralAlpha[2]
+
+ tokens["border-base"] = neutralAlpha[6]
+ tokens["border-hover"] = neutralAlpha[7]
+ tokens["border-active"] = neutralAlpha[8]
+ tokens["border-selected"] = withAlpha(interactive[8], isDark ? 0.9 : 0.99) as ColorValue
+ tokens["border-disabled"] = neutralAlpha[7]
+ tokens["border-focus"] = neutralAlpha[8]
+ tokens["border-weak-base"] = neutralAlpha[isDark ? 5 : 4]
+ tokens["border-strong-base"] = neutralAlpha[isDark ? 7 : 6]
+ tokens["border-strong-hover"] = neutralAlpha[7]
+ tokens["border-strong-active"] = neutralAlpha[isDark ? 7 : 6]
+ tokens["border-strong-selected"] = withAlpha(interactive[5], 0.6) as ColorValue
+ tokens["border-strong-disabled"] = neutralAlpha[5]
+ tokens["border-strong-focus"] = neutralAlpha[isDark ? 7 : 6]
+ tokens["border-weak-hover"] = neutralAlpha[isDark ? 6 : 5]
+ tokens["border-weak-active"] = neutralAlpha[isDark ? 7 : 6]
+ tokens["border-weak-selected"] = withAlpha(interactive[4], isDark ? 0.6 : 0.5) as ColorValue
+ tokens["border-weak-disabled"] = neutralAlpha[5]
+ tokens["border-weak-focus"] = neutralAlpha[isDark ? 7 : 6]
+ tokens["border-weaker-base"] = neutralAlpha[2]
+ tokens["border-weaker-hover"] = neutralAlpha[3]
+ tokens["border-weaker-active"] = neutralAlpha[5]
+ tokens["border-weaker-selected"] = withAlpha(interactive[3], isDark ? 0.3 : 0.4) as ColorValue
+ tokens["border-weaker-disabled"] = neutralAlpha[1]
+ tokens["border-weaker-focus"] = neutralAlpha[5]
+
+ tokens["border-interactive-base"] = interactive[6]
+ tokens["border-interactive-hover"] = interactive[7]
+ tokens["border-interactive-active"] = interactive[8]
+ tokens["border-interactive-selected"] = interactive[8]
+ tokens["border-interactive-disabled"] = neutral[7]
+ tokens["border-interactive-focus"] = interactive[8]
+
+ tokens["border-success-base"] = success[5]
+ tokens["border-success-hover"] = success[6]
+ tokens["border-success-selected"] = success[8]
+ tokens["border-warning-base"] = warning[5]
+ tokens["border-warning-hover"] = warning[6]
+ tokens["border-warning-selected"] = warning[8]
+ tokens["border-critical-base"] = error[isDark ? 4 : 5]
+ tokens["border-critical-hover"] = error[6]
+ tokens["border-critical-selected"] = error[8]
+ tokens["border-info-base"] = info[5]
+ tokens["border-info-hover"] = info[6]
+ tokens["border-info-selected"] = info[8]
+ tokens["border-color"] = "#ffffff"
+
+ tokens["icon-base"] = neutral[8]
+ tokens["icon-hover"] = neutral[isDark ? 9 : 10]
+ tokens["icon-active"] = neutral[isDark ? 10 : 11]
+ tokens["icon-selected"] = neutral[11]
+ tokens["icon-disabled"] = neutral[isDark ? 6 : 7]
+ tokens["icon-focus"] = neutral[11]
+ tokens["icon-invert-base"] = isDark ? neutral[0] : "#ffffff"
+ tokens["icon-weak-base"] = neutral[isDark ? 5 : 6]
+ tokens["icon-weak-hover"] = neutral[6]
+ tokens["icon-weak-active"] = neutral[7]
+ tokens["icon-weak-selected"] = neutral[8]
+ tokens["icon-weak-disabled"] = neutral[isDark ? 3 : 5]
+ tokens["icon-weak-focus"] = neutral[8]
+ tokens["icon-strong-base"] = neutral[11]
+ tokens["icon-strong-hover"] = isDark ? "#f6f3f3" : "#151313"
+ tokens["icon-strong-active"] = isDark ? "#fcfcfc" : "#020202"
+ tokens["icon-strong-selected"] = isDark ? "#fdfcfc" : "#020202"
+ tokens["icon-strong-disabled"] = neutral[7]
+ tokens["icon-strong-focus"] = isDark ? "#fdfcfc" : "#020202"
+ tokens["icon-brand-base"] = isDark ? "#ffffff" : neutral[11]
+ tokens["icon-interactive-base"] = interactive[8]
+ tokens["icon-success-base"] = success[isDark ? 6 : 6]
+ tokens["icon-success-hover"] = success[7]
+ tokens["icon-success-active"] = success[10]
+ tokens["icon-warning-base"] = warning[6]
+ tokens["icon-warning-hover"] = warning[7]
+ tokens["icon-warning-active"] = warning[10]
+ tokens["icon-critical-base"] = error[isDark ? 8 : 9]
+ tokens["icon-critical-hover"] = error[10]
+ tokens["icon-critical-active"] = error[11]
+ tokens["icon-info-base"] = info[isDark ? 6 : 6]
+ tokens["icon-info-hover"] = info[7]
+ tokens["icon-info-active"] = info[10]
+ tokens["icon-on-brand-base"] = neutralAlpha[10]
+ tokens["icon-on-brand-hover"] = neutralAlpha[11]
+ tokens["icon-on-brand-selected"] = neutralAlpha[11]
+ tokens["icon-on-interactive-base"] = isDark ? neutral[11] : neutral[0]
+
+ tokens["icon-agent-plan-base"] = info[8]
+ tokens["icon-agent-docs-base"] = warning[8]
+ tokens["icon-agent-ask-base"] = interactive[8]
+ tokens["icon-agent-build-base"] = interactive[isDark ? 10 : 8]
+
+ tokens["icon-on-success-base"] = withAlpha(success[8], 0.9) as ColorValue
+ tokens["icon-on-success-hover"] = withAlpha(success[9], 0.9) as ColorValue
+ tokens["icon-on-success-selected"] = withAlpha(success[10], 0.9) as ColorValue
+ tokens["icon-on-warning-base"] = withAlpha(warning[8], 0.9) as ColorValue
+ tokens["icon-on-warning-hover"] = withAlpha(warning[9], 0.9) as ColorValue
+ tokens["icon-on-warning-selected"] = withAlpha(warning[10], 0.9) as ColorValue
+ tokens["icon-on-critical-base"] = withAlpha(error[8], 0.9) as ColorValue
+ tokens["icon-on-critical-hover"] = withAlpha(error[9], 0.9) as ColorValue
+ tokens["icon-on-critical-selected"] = withAlpha(error[10], 0.9) as ColorValue
+ tokens["icon-on-info-base"] = info[8]
+ tokens["icon-on-info-hover"] = withAlpha(info[9], 0.9) as ColorValue
+ tokens["icon-on-info-selected"] = withAlpha(info[10], 0.9) as ColorValue
+
+ tokens["icon-diff-add-base"] = diffAdd[10]
+ tokens["icon-diff-add-hover"] = diffAdd[isDark ? 9 : 11]
+ tokens["icon-diff-add-active"] = diffAdd[isDark ? 10 : 11]
+ tokens["icon-diff-delete-base"] = diffDelete[isDark ? 8 : 9]
+ tokens["icon-diff-delete-hover"] = diffDelete[isDark ? 9 : 10]
+
+ tokens["syntax-comment"] = "var(--text-weak)"
+ tokens["syntax-regexp"] = "var(--text-base)"
+ tokens["syntax-string"] = isDark ? "#00ceb9" : "#006656"
+ tokens["syntax-keyword"] = "var(--text-weak)"
+ tokens["syntax-primitive"] = isDark ? "#ffba92" : "#fb4804"
+ tokens["syntax-operator"] = isDark ? "var(--text-weak)" : "var(--text-base)"
+ tokens["syntax-variable"] = "var(--text-strong)"
+ tokens["syntax-property"] = isDark ? "#ff9ae2" : "#ed6dc8"
+ tokens["syntax-type"] = isDark ? "#ecf58c" : "#596600"
+ tokens["syntax-constant"] = isDark ? "#93e9f6" : "#007b80"
+ tokens["syntax-punctuation"] = isDark ? "var(--text-weak)" : "var(--text-base)"
+ tokens["syntax-object"] = "var(--text-strong)"
+ tokens["syntax-success"] = success[9]
+ tokens["syntax-warning"] = warning[9]
+ tokens["syntax-critical"] = error[isDark ? 9 : 9]
+ tokens["syntax-info"] = isDark ? "#93e9f6" : "#0092a8"
+ tokens["syntax-diff-add"] = diffAdd[10]
+ tokens["syntax-diff-delete"] = diffDelete[10]
+ tokens["syntax-diff-unknown"] = "#ff0000"
+
+ tokens["markdown-heading"] = isDark ? "#9d7cd8" : "#d68c27"
+ tokens["markdown-text"] = isDark ? "#eeeeee" : "#1a1a1a"
+ tokens["markdown-link"] = isDark ? "#fab283" : "#3b7dd8"
+ tokens["markdown-link-text"] = isDark ? "#56b6c2" : "#318795"
+ tokens["markdown-code"] = isDark ? "#7fd88f" : "#3d9a57"
+ tokens["markdown-block-quote"] = isDark ? "#e5c07b" : "#b0851f"
+ tokens["markdown-emph"] = isDark ? "#e5c07b" : "#b0851f"
+ tokens["markdown-strong"] = isDark ? "#f5a742" : "#d68c27"
+ tokens["markdown-horizontal-rule"] = isDark ? "#808080" : "#8a8a8a"
+ tokens["markdown-list-item"] = isDark ? "#fab283" : "#3b7dd8"
+ tokens["markdown-list-enumeration"] = isDark ? "#56b6c2" : "#318795"
+ tokens["markdown-image"] = isDark ? "#fab283" : "#3b7dd8"
+ tokens["markdown-image-text"] = isDark ? "#56b6c2" : "#318795"
+ tokens["markdown-code-block"] = isDark ? "#eeeeee" : "#1a1a1a"
+
+ tokens["avatar-background-pink"] = isDark ? "#501b3f" : "#feeef8"
+ tokens["avatar-background-mint"] = isDark ? "#033a34" : "#e1fbf4"
+ tokens["avatar-background-orange"] = isDark ? "#5f2a06" : "#fff1e7"
+ tokens["avatar-background-purple"] = isDark ? "#432155" : "#f9f1fe"
+ tokens["avatar-background-cyan"] = isDark ? "#0f3058" : "#e7f9fb"
+ tokens["avatar-background-lime"] = isDark ? "#2b3711" : "#eefadc"
+ tokens["avatar-text-pink"] = isDark ? "#e34ba9" : "#cd1d8d"
+ tokens["avatar-text-mint"] = isDark ? "#95f3d9" : "#147d6f"
+ tokens["avatar-text-orange"] = isDark ? "#ff802b" : "#ed5f00"
+ tokens["avatar-text-purple"] = isDark ? "#9d5bd2" : "#8445bc"
+ tokens["avatar-text-cyan"] = isDark ? "#369eff" : "#0894b3"
+ tokens["avatar-text-lime"] = isDark ? "#c4f042" : "#5d770d"
+
+ for (const [key, value] of Object.entries(overrides)) {
+ tokens[key] = value
+ }
+
+ return tokens
+}
+
+function generateNeutralAlphaScale(neutralScale: HexColor[], isDark: boolean): HexColor[] {
+ const alphas = isDark
+ ? [0.02, 0.04, 0.08, 0.12, 0.16, 0.2, 0.26, 0.36, 0.44, 0.52, 0.72, 0.94]
+ : [0.01, 0.03, 0.06, 0.09, 0.12, 0.15, 0.2, 0.27, 0.46, 0.61, 0.5, 0.87]
+
+ return neutralScale.map((hex, i) => {
+ const baseOklch = hexToOklch(hex)
+ const targetL = isDark ? 0.1 + alphas[i] * 0.8 : 1 - alphas[i] * 0.8
+ return oklchToHex({
+ ...baseOklch,
+ l: baseOklch.l * alphas[i] + targetL * (1 - alphas[i]),
+ })
+ })
+}
+
+export function resolveTheme(theme: DesktopTheme): { light: ResolvedTheme; dark: ResolvedTheme } {
+ return {
+ light: resolveThemeVariant(theme.light, false),
+ dark: resolveThemeVariant(theme.dark, true),
+ }
+}
+
+export function themeToCss(tokens: ResolvedTheme): string {
+ return Object.entries(tokens)
+ .map(([key, value]) => `--${key}: ${value};`)
+ .join("\n ")
+}
diff --git a/packages/ui/src/theme/themes/ayu.json b/packages/ui/src/theme/themes/ayu.json
new file mode 100644
index 00000000000..944241450ac
--- /dev/null
+++ b/packages/ui/src/theme/themes/ayu.json
@@ -0,0 +1,131 @@
+{
+ "$schema": "https://opencode.ai/desktop-theme.json",
+ "name": "Ayu",
+ "id": "ayu",
+ "light": {
+ "seeds": {
+ "neutral": "#fdfaf4",
+ "primary": "#55b4d4",
+ "success": "#6ac782",
+ "warning": "#f2ae49",
+ "error": "#f05f65",
+ "info": "#36a3d9",
+ "interactive": "#55b4d4",
+ "diffAdd": "#b8df8a",
+ "diffDelete": "#f05f65"
+ },
+ "overrides": {
+ "background-base": "#fdfaf4",
+ "background-weak": "#f6f0e7",
+ "background-strong": "#f1ebe2",
+ "background-stronger": "#ece4da",
+ "border-weak-base": "#e6ddcf",
+ "border-weak-hover": "#dcd3c5",
+ "border-weak-active": "#d1c9ba",
+ "border-weak-selected": "#c6bfaf",
+ "border-weak-disabled": "#f7f0e6",
+ "border-weak-focus": "#cbc4b6",
+ "border-base": "#bfb3a3",
+ "border-hover": "#b4a898",
+ "border-active": "#a99e8e",
+ "border-selected": "#9e9383",
+ "border-disabled": "#efe5d8",
+ "border-focus": "#b09f8f",
+ "border-strong-base": "#8f806f",
+ "border-strong-hover": "#837465",
+ "border-strong-active": "#77685a",
+ "border-strong-selected": "#6b5d51",
+ "border-strong-disabled": "#d8cabc",
+ "border-strong-focus": "#7c6d5e",
+ "surface-diff-add-base": "#eef5e4",
+ "surface-diff-delete-base": "#fde5e5",
+ "surface-diff-hidden-base": "#e3edf3",
+ "text-base": "#5c6773",
+ "text-weak": "#8a939f",
+ "text-strong": "#2a3038",
+ "syntax-string": "#86b300",
+ "syntax-primitive": "#f28779",
+ "syntax-property": "#55b4d4",
+ "syntax-type": "#f29e32",
+ "syntax-constant": "#36a3d9",
+ "syntax-info": "#36a3d9",
+ "markdown-heading": "#55b4d4",
+ "markdown-text": "#5c6773",
+ "markdown-link": "#55b4d4",
+ "markdown-link-text": "#36a3d9",
+ "markdown-code": "#86b300",
+ "markdown-block-quote": "#f29e32",
+ "markdown-emph": "#f29e32",
+ "markdown-strong": "#f28779",
+ "markdown-horizontal-rule": "#d7cec0",
+ "markdown-list-item": "#55b4d4",
+ "markdown-list-enumeration": "#36a3d9",
+ "markdown-image": "#55b4d4",
+ "markdown-image-text": "#36a3d9",
+ "markdown-code-block": "#55b4d4"
+ }
+ },
+ "dark": {
+ "seeds": {
+ "neutral": "#0f1419",
+ "primary": "#39bae6",
+ "success": "#7fd962",
+ "warning": "#ebb062",
+ "error": "#ff8f77",
+ "info": "#73d0ff",
+ "interactive": "#39bae6",
+ "diffAdd": "#5cc885",
+ "diffDelete": "#ff8f77"
+ },
+ "overrides": {
+ "background-base": "#0f1419",
+ "background-weak": "#121920",
+ "background-strong": "#0d1116",
+ "background-stronger": "#0a0e13",
+ "border-weak-base": "#262c34",
+ "border-weak-hover": "#2b323d",
+ "border-weak-active": "#303746",
+ "border-weak-selected": "#363d50",
+ "border-weak-disabled": "#080b0f",
+ "border-weak-focus": "#323a48",
+ "border-base": "#3d4555",
+ "border-hover": "#454d61",
+ "border-active": "#4c556d",
+ "border-selected": "#545d79",
+ "border-disabled": "#0e1218",
+ "border-focus": "#495368",
+ "border-strong-base": "#626c81",
+ "border-strong-hover": "#6c7690",
+ "border-strong-active": "#76819f",
+ "border-strong-selected": "#808bae",
+ "border-strong-disabled": "#151b23",
+ "border-strong-focus": "#6f7a96",
+ "surface-diff-add-base": "#102922",
+ "surface-diff-delete-base": "#2b1718",
+ "surface-diff-hidden-base": "#182028",
+ "text-base": "#ced0d6",
+ "text-weak": "#8f9aa5",
+ "text-strong": "#f6f7f9",
+ "syntax-string": "#b8cc52",
+ "syntax-primitive": "#f59074",
+ "syntax-property": "#39bae6",
+ "syntax-type": "#ebb062",
+ "syntax-constant": "#73d0ff",
+ "syntax-info": "#73d0ff",
+ "markdown-heading": "#39bae6",
+ "markdown-text": "#ced0d6",
+ "markdown-link": "#39bae6",
+ "markdown-link-text": "#73d0ff",
+ "markdown-code": "#b8cc52",
+ "markdown-block-quote": "#ebb062",
+ "markdown-emph": "#ebb062",
+ "markdown-strong": "#f59074",
+ "markdown-horizontal-rule": "#1f2630",
+ "markdown-list-item": "#39bae6",
+ "markdown-list-enumeration": "#73d0ff",
+ "markdown-image": "#39bae6",
+ "markdown-image-text": "#73d0ff",
+ "markdown-code-block": "#ced0d6"
+ }
+ }
+}
diff --git a/packages/ui/src/theme/themes/catppuccin.json b/packages/ui/src/theme/themes/catppuccin.json
new file mode 100644
index 00000000000..2a32df09846
--- /dev/null
+++ b/packages/ui/src/theme/themes/catppuccin.json
@@ -0,0 +1,131 @@
+{
+ "$schema": "https://opencode.ai/desktop-theme.json",
+ "name": "Catppuccin",
+ "id": "catppuccin",
+ "light": {
+ "seeds": {
+ "neutral": "#f5e0dc",
+ "primary": "#7287fd",
+ "success": "#40a02b",
+ "warning": "#df8e1d",
+ "error": "#d20f39",
+ "info": "#04a5e5",
+ "interactive": "#7287fd",
+ "diffAdd": "#a6d189",
+ "diffDelete": "#e78284"
+ },
+ "overrides": {
+ "background-base": "#f5e0dc",
+ "background-weak": "#f2d8d4",
+ "background-strong": "#f9e8e4",
+ "background-stronger": "#fdeeee",
+ "border-weak-base": "#e0cfd3",
+ "border-weak-hover": "#d6c4c8",
+ "border-weak-active": "#cdb9be",
+ "border-weak-selected": "#c2aeb4",
+ "border-weak-disabled": "#fbeff2",
+ "border-weak-focus": "#c7b4ba",
+ "border-base": "#bca6b2",
+ "border-hover": "#b19ca8",
+ "border-active": "#a6929e",
+ "border-selected": "#9a8894",
+ "border-disabled": "#f3e4e7",
+ "border-focus": "#ab97a1",
+ "border-strong-base": "#83677f",
+ "border-strong-hover": "#775b73",
+ "border-strong-active": "#6b5068",
+ "border-strong-selected": "#5f465d",
+ "border-strong-disabled": "#d9c5cf",
+ "border-strong-focus": "#714f66",
+ "surface-diff-add-base": "#edf5e6",
+ "surface-diff-delete-base": "#fde1e3",
+ "surface-diff-hidden-base": "#e4e2f6",
+ "text-base": "#4c4f69",
+ "text-weak": "#6c6f85",
+ "text-strong": "#1f1f2a",
+ "syntax-string": "#40a02b",
+ "syntax-primitive": "#d20f39",
+ "syntax-property": "#7287fd",
+ "syntax-type": "#df8e1d",
+ "syntax-constant": "#04a5e5",
+ "syntax-info": "#04a5e5",
+ "markdown-heading": "#7287fd",
+ "markdown-text": "#4c4f69",
+ "markdown-link": "#7287fd",
+ "markdown-link-text": "#04a5e5",
+ "markdown-code": "#40a02b",
+ "markdown-block-quote": "#df8e1d",
+ "markdown-emph": "#df8e1d",
+ "markdown-strong": "#d20f39",
+ "markdown-horizontal-rule": "#d4c5cf",
+ "markdown-list-item": "#7287fd",
+ "markdown-list-enumeration": "#04a5e5",
+ "markdown-image": "#7287fd",
+ "markdown-image-text": "#04a5e5",
+ "markdown-code-block": "#7287fd"
+ }
+ },
+ "dark": {
+ "seeds": {
+ "neutral": "#1e1e2e",
+ "primary": "#b4befe",
+ "success": "#a6d189",
+ "warning": "#f4b8e4",
+ "error": "#f38ba8",
+ "info": "#89dceb",
+ "interactive": "#b4befe",
+ "diffAdd": "#94e2d5",
+ "diffDelete": "#f38ba8"
+ },
+ "overrides": {
+ "background-base": "#1e1e2e",
+ "background-weak": "#211f31",
+ "background-strong": "#1c1c29",
+ "background-stronger": "#191926",
+ "border-weak-base": "#35324a",
+ "border-weak-hover": "#393655",
+ "border-weak-active": "#403c61",
+ "border-weak-selected": "#47436d",
+ "border-weak-disabled": "#141426",
+ "border-weak-focus": "#3d3a63",
+ "border-base": "#4a4763",
+ "border-hover": "#524f70",
+ "border-active": "#5a577d",
+ "border-selected": "#625f8a",
+ "border-disabled": "#1b1a2c",
+ "border-focus": "#575379",
+ "border-strong-base": "#6e6a8c",
+ "border-strong-hover": "#787497",
+ "border-strong-active": "#8380a2",
+ "border-strong-selected": "#8d8bad",
+ "border-strong-disabled": "#232237",
+ "border-strong-focus": "#7b779b",
+ "surface-diff-add-base": "#1d2c30",
+ "surface-diff-delete-base": "#2c1f2a",
+ "surface-diff-hidden-base": "#232538",
+ "text-base": "#cdd6f4",
+ "text-weak": "#a6adc8",
+ "text-strong": "#f4f2ff",
+ "syntax-string": "#a6e3a1",
+ "syntax-primitive": "#f38ba8",
+ "syntax-property": "#b4befe",
+ "syntax-type": "#f9e2af",
+ "syntax-constant": "#89dceb",
+ "syntax-info": "#89dceb",
+ "markdown-heading": "#b4befe",
+ "markdown-text": "#cdd6f4",
+ "markdown-link": "#b4befe",
+ "markdown-link-text": "#89dceb",
+ "markdown-code": "#a6e3a1",
+ "markdown-block-quote": "#f9e2af",
+ "markdown-emph": "#f9e2af",
+ "markdown-strong": "#f38ba8",
+ "markdown-horizontal-rule": "#2e2d45",
+ "markdown-list-item": "#b4befe",
+ "markdown-list-enumeration": "#89dceb",
+ "markdown-image": "#b4befe",
+ "markdown-image-text": "#89dceb",
+ "markdown-code-block": "#cdd6f4"
+ }
+ }
+}
diff --git a/packages/ui/src/theme/themes/dracula.json b/packages/ui/src/theme/themes/dracula.json
new file mode 100644
index 00000000000..696f1060c70
--- /dev/null
+++ b/packages/ui/src/theme/themes/dracula.json
@@ -0,0 +1,131 @@
+{
+ "$schema": "https://opencode.ai/desktop-theme.json",
+ "name": "Dracula",
+ "id": "dracula",
+ "light": {
+ "seeds": {
+ "neutral": "#f8f8f2",
+ "primary": "#7c6bf5",
+ "success": "#2fbf71",
+ "warning": "#f7a14d",
+ "error": "#d9536f",
+ "info": "#1d7fc5",
+ "interactive": "#7c6bf5",
+ "diffAdd": "#9fe3b3",
+ "diffDelete": "#f8a1b8"
+ },
+ "overrides": {
+ "background-base": "#f8f8f2",
+ "background-weak": "#f1f2ed",
+ "background-strong": "#f6f6f1",
+ "background-stronger": "#f2f2ec",
+ "border-weak-base": "#e2e3da",
+ "border-weak-hover": "#d8d9d0",
+ "border-weak-active": "#cfd0c7",
+ "border-weak-selected": "#c4c6bc",
+ "border-weak-disabled": "#eceee3",
+ "border-weak-focus": "#c9cabf",
+ "border-base": "#c4c6ba",
+ "border-hover": "#b8baae",
+ "border-active": "#abada3",
+ "border-selected": "#979a90",
+ "border-disabled": "#e5e7dd",
+ "border-focus": "#b0b2a7",
+ "border-strong-base": "#9fa293",
+ "border-strong-hover": "#8e9185",
+ "border-strong-active": "#7e8176",
+ "border-strong-selected": "#6f7268",
+ "border-strong-disabled": "#c7c9be",
+ "border-strong-focus": "#878b7f",
+ "surface-diff-add-base": "#e4f5e6",
+ "surface-diff-delete-base": "#fae4eb",
+ "surface-diff-hidden-base": "#dedfe9",
+ "text-base": "#1f1f2f",
+ "text-weak": "#52526b",
+ "text-strong": "#05040c",
+ "syntax-string": "#2fbf71",
+ "syntax-primitive": "#d16090",
+ "syntax-property": "#7c6bf5",
+ "syntax-type": "#f7a14d",
+ "syntax-constant": "#1d7fc5",
+ "syntax-info": "#1d7fc5",
+ "markdown-heading": "#7c6bf5",
+ "markdown-text": "#1f1f2f",
+ "markdown-link": "#7c6bf5",
+ "markdown-link-text": "#1d7fc5",
+ "markdown-code": "#2fbf71",
+ "markdown-block-quote": "#f7a14d",
+ "markdown-emph": "#f7a14d",
+ "markdown-strong": "#d16090",
+ "markdown-horizontal-rule": "#c3c5d4",
+ "markdown-list-item": "#7c6bf5",
+ "markdown-list-enumeration": "#1d7fc5",
+ "markdown-image": "#7c6bf5",
+ "markdown-image-text": "#1d7fc5",
+ "markdown-code-block": "#1d7fc5"
+ }
+ },
+ "dark": {
+ "seeds": {
+ "neutral": "#1d1e28",
+ "primary": "#bd93f9",
+ "success": "#50fa7b",
+ "warning": "#ffb86c",
+ "error": "#ff5555",
+ "info": "#8be9fd",
+ "interactive": "#bd93f9",
+ "diffAdd": "#2fb27d",
+ "diffDelete": "#ff6b81"
+ },
+ "overrides": {
+ "background-base": "#14151f",
+ "background-weak": "#181926",
+ "background-strong": "#161722",
+ "background-stronger": "#191a26",
+ "border-weak-base": "#2d2f3c",
+ "border-weak-hover": "#303244",
+ "border-weak-active": "#35364c",
+ "border-weak-selected": "#3b3d55",
+ "border-weak-disabled": "#1e1f2b",
+ "border-weak-focus": "#383a50",
+ "border-base": "#3f415a",
+ "border-hover": "#464967",
+ "border-active": "#4d5073",
+ "border-selected": "#55587f",
+ "border-disabled": "#272834",
+ "border-focus": "#4a4d6d",
+ "border-strong-base": "#606488",
+ "border-strong-hover": "#6a6e96",
+ "border-strong-active": "#7378a3",
+ "border-strong-selected": "#7d82b1",
+ "border-strong-disabled": "#343649",
+ "border-strong-focus": "#6f739c",
+ "surface-diff-add-base": "#1f2a2f",
+ "surface-diff-delete-base": "#2d1f27",
+ "surface-diff-hidden-base": "#24253a",
+ "text-base": "#f8f8f2",
+ "text-weak": "#b6b9e4",
+ "text-strong": "#ffffff",
+ "syntax-string": "#50fa7b",
+ "syntax-primitive": "#ff79c6",
+ "syntax-property": "#bd93f9",
+ "syntax-type": "#ffb86c",
+ "syntax-constant": "#8be9fd",
+ "syntax-info": "#8be9fd",
+ "markdown-heading": "#bd93f9",
+ "markdown-text": "#f8f8f2",
+ "markdown-link": "#bd93f9",
+ "markdown-link-text": "#8be9fd",
+ "markdown-code": "#50fa7b",
+ "markdown-block-quote": "#ffb86c",
+ "markdown-emph": "#ffb86c",
+ "markdown-strong": "#ff79c6",
+ "markdown-horizontal-rule": "#44475a",
+ "markdown-list-item": "#bd93f9",
+ "markdown-list-enumeration": "#8be9fd",
+ "markdown-image": "#bd93f9",
+ "markdown-image-text": "#8be9fd",
+ "markdown-code-block": "#f8f8f2"
+ }
+ }
+}
diff --git a/packages/ui/src/theme/themes/monokai.json b/packages/ui/src/theme/themes/monokai.json
new file mode 100644
index 00000000000..d49846ddb3e
--- /dev/null
+++ b/packages/ui/src/theme/themes/monokai.json
@@ -0,0 +1,131 @@
+{
+ "$schema": "https://opencode.ai/desktop-theme.json",
+ "name": "Monokai",
+ "id": "monokai",
+ "light": {
+ "seeds": {
+ "neutral": "#fdf8ec",
+ "primary": "#bf7bff",
+ "success": "#4fb54b",
+ "warning": "#f1a948",
+ "error": "#e54b4b",
+ "info": "#2d9ad7",
+ "interactive": "#bf7bff",
+ "diffAdd": "#bfe7a3",
+ "diffDelete": "#f6a3ae"
+ },
+ "overrides": {
+ "background-base": "#fdf8ec",
+ "background-weak": "#f8f2e6",
+ "background-strong": "#fbf5e8",
+ "background-stronger": "#f7efdd",
+ "border-weak-base": "#e9e0cf",
+ "border-weak-hover": "#dfd5c3",
+ "border-weak-active": "#d5cab7",
+ "border-weak-selected": "#cabfad",
+ "border-weak-disabled": "#f3ebdd",
+ "border-weak-focus": "#d0c2b1",
+ "border-base": "#c7b9a5",
+ "border-hover": "#bcae98",
+ "border-active": "#b0a28c",
+ "border-selected": "#a49781",
+ "border-disabled": "#efe5d6",
+ "border-focus": "#b6a893",
+ "border-strong-base": "#998b76",
+ "border-strong-hover": "#8a7c67",
+ "border-strong-active": "#7a6d58",
+ "border-strong-selected": "#6c604c",
+ "border-strong-disabled": "#d7cabc",
+ "border-strong-focus": "#82745f",
+ "surface-diff-add-base": "#e8f7e1",
+ "surface-diff-delete-base": "#fde5e4",
+ "surface-diff-hidden-base": "#e9e0d0",
+ "text-base": "#292318",
+ "text-weak": "#6d5c40",
+ "text-strong": "#1c150c",
+ "syntax-string": "#4fb54b",
+ "syntax-primitive": "#d9487c",
+ "syntax-property": "#bf7bff",
+ "syntax-type": "#f1a948",
+ "syntax-constant": "#2d9ad7",
+ "syntax-info": "#2d9ad7",
+ "markdown-heading": "#bf7bff",
+ "markdown-text": "#292318",
+ "markdown-link": "#bf7bff",
+ "markdown-link-text": "#2d9ad7",
+ "markdown-code": "#4fb54b",
+ "markdown-block-quote": "#f1a948",
+ "markdown-emph": "#f1a948",
+ "markdown-strong": "#d9487c",
+ "markdown-horizontal-rule": "#cdbdab",
+ "markdown-list-item": "#bf7bff",
+ "markdown-list-enumeration": "#2d9ad7",
+ "markdown-image": "#bf7bff",
+ "markdown-image-text": "#2d9ad7",
+ "markdown-code-block": "#2d9ad7"
+ }
+ },
+ "dark": {
+ "seeds": {
+ "neutral": "#272822",
+ "primary": "#ae81ff",
+ "success": "#a6e22e",
+ "warning": "#fd971f",
+ "error": "#f92672",
+ "info": "#66d9ef",
+ "interactive": "#ae81ff",
+ "diffAdd": "#4d7f2a",
+ "diffDelete": "#f4477c"
+ },
+ "overrides": {
+ "background-base": "#23241e",
+ "background-weak": "#27281f",
+ "background-strong": "#25261f",
+ "background-stronger": "#292a23",
+ "border-weak-base": "#343528",
+ "border-weak-hover": "#393a2d",
+ "border-weak-active": "#3f4033",
+ "border-weak-selected": "#454639",
+ "border-weak-disabled": "#1d1e16",
+ "border-weak-focus": "#414235",
+ "border-base": "#494a3a",
+ "border-hover": "#50523f",
+ "border-active": "#585a45",
+ "border-selected": "#60624b",
+ "border-disabled": "#23241b",
+ "border-focus": "#555741",
+ "border-strong-base": "#6a6c55",
+ "border-strong-hover": "#73755d",
+ "border-strong-active": "#7d7f66",
+ "border-strong-selected": "#878970",
+ "border-strong-disabled": "#2c2d23",
+ "border-strong-focus": "#7a7c63",
+ "surface-diff-add-base": "#1e2a1d",
+ "surface-diff-delete-base": "#301c24",
+ "surface-diff-hidden-base": "#2f2f24",
+ "text-base": "#f8f8f2",
+ "text-weak": "#c5c5c0",
+ "text-strong": "#ffffff",
+ "syntax-string": "#a6e22e",
+ "syntax-primitive": "#f92672",
+ "syntax-property": "#ae81ff",
+ "syntax-type": "#fd971f",
+ "syntax-constant": "#66d9ef",
+ "syntax-info": "#66d9ef",
+ "markdown-heading": "#ae81ff",
+ "markdown-text": "#f8f8f2",
+ "markdown-link": "#ae81ff",
+ "markdown-link-text": "#66d9ef",
+ "markdown-code": "#a6e22e",
+ "markdown-block-quote": "#fd971f",
+ "markdown-emph": "#fd971f",
+ "markdown-strong": "#f92672",
+ "markdown-horizontal-rule": "#3b3c34",
+ "markdown-list-item": "#ae81ff",
+ "markdown-list-enumeration": "#66d9ef",
+ "markdown-image": "#ae81ff",
+ "markdown-image-text": "#66d9ef",
+ "markdown-code-block": "#f8f8f2"
+ }
+ }
+}
diff --git a/packages/ui/src/theme/themes/nord.json b/packages/ui/src/theme/themes/nord.json
new file mode 100644
index 00000000000..44378de06ab
--- /dev/null
+++ b/packages/ui/src/theme/themes/nord.json
@@ -0,0 +1,131 @@
+{
+ "$schema": "https://opencode.ai/desktop-theme.json",
+ "name": "Nord",
+ "id": "nord",
+ "light": {
+ "seeds": {
+ "neutral": "#eceff4",
+ "primary": "#5e81ac",
+ "success": "#8fbcbb",
+ "warning": "#d08770",
+ "error": "#bf616a",
+ "info": "#81a1c1",
+ "interactive": "#5e81ac",
+ "diffAdd": "#a3be8c",
+ "diffDelete": "#bf616a"
+ },
+ "overrides": {
+ "background-base": "#eceff4",
+ "background-weak": "#e4e8f0",
+ "background-strong": "#f1f3f8",
+ "background-stronger": "#f6f8fc",
+ "border-weak-base": "#d5dbe7",
+ "border-weak-hover": "#c9d0de",
+ "border-weak-active": "#bec5d4",
+ "border-weak-selected": "#b2bacc",
+ "border-weak-disabled": "#f0f3fa",
+ "border-weak-focus": "#b9bfd0",
+ "border-base": "#afb7cb",
+ "border-hover": "#a3abc1",
+ "border-active": "#979fb7",
+ "border-selected": "#8b94ad",
+ "border-disabled": "#e5e9f2",
+ "border-focus": "#9ca4ba",
+ "border-strong-base": "#757f97",
+ "border-strong-hover": "#69718a",
+ "border-strong-active": "#5d647d",
+ "border-strong-selected": "#525970",
+ "border-strong-disabled": "#c9cedc",
+ "border-strong-focus": "#636c84",
+ "surface-diff-add-base": "#e4f0e4",
+ "surface-diff-delete-base": "#f4e1e4",
+ "surface-diff-hidden-base": "#dfe6f2",
+ "text-base": "#2e3440",
+ "text-weak": "#4c566a",
+ "text-strong": "#1f2530",
+ "syntax-string": "#a3be8c",
+ "syntax-primitive": "#bf616a",
+ "syntax-property": "#5e81ac",
+ "syntax-type": "#d08770",
+ "syntax-constant": "#81a1c1",
+ "syntax-info": "#81a1c1",
+ "markdown-heading": "#5e81ac",
+ "markdown-text": "#2e3440",
+ "markdown-link": "#5e81ac",
+ "markdown-link-text": "#81a1c1",
+ "markdown-code": "#a3be8c",
+ "markdown-block-quote": "#d08770",
+ "markdown-emph": "#d08770",
+ "markdown-strong": "#bf616a",
+ "markdown-horizontal-rule": "#cbd3e1",
+ "markdown-list-item": "#5e81ac",
+ "markdown-list-enumeration": "#81a1c1",
+ "markdown-image": "#5e81ac",
+ "markdown-image-text": "#81a1c1",
+ "markdown-code-block": "#5e81ac"
+ }
+ },
+ "dark": {
+ "seeds": {
+ "neutral": "#2e3440",
+ "primary": "#88c0d0",
+ "success": "#a3be8c",
+ "warning": "#d08770",
+ "error": "#bf616a",
+ "info": "#81a1c1",
+ "interactive": "#88c0d0",
+ "diffAdd": "#81a1c1",
+ "diffDelete": "#bf616a"
+ },
+ "overrides": {
+ "background-base": "#1f2430",
+ "background-weak": "#222938",
+ "background-strong": "#1c202a",
+ "background-stronger": "#181c24",
+ "border-weak-base": "#343a47",
+ "border-weak-hover": "#383f50",
+ "border-weak-active": "#3d4458",
+ "border-weak-selected": "#434a62",
+ "border-weak-disabled": "#151923",
+ "border-weak-focus": "#3f4359",
+ "border-base": "#4a5163",
+ "border-hover": "#515870",
+ "border-active": "#585f7c",
+ "border-selected": "#606889",
+ "border-disabled": "#1b202a",
+ "border-focus": "#545b78",
+ "border-strong-base": "#6a7492",
+ "border-strong-hover": "#747e9f",
+ "border-strong-active": "#7e88ac",
+ "border-strong-selected": "#8993b9",
+ "border-strong-disabled": "#232836",
+ "border-strong-focus": "#76819f",
+ "surface-diff-add-base": "#1f2e33",
+ "surface-diff-delete-base": "#2e212a",
+ "surface-diff-hidden-base": "#222b3a",
+ "text-base": "#e5e9f0",
+ "text-weak": "#a4adbf",
+ "text-strong": "#f8fafc",
+ "syntax-string": "#a3be8c",
+ "syntax-primitive": "#d57780",
+ "syntax-property": "#88c0d0",
+ "syntax-type": "#eac196",
+ "syntax-constant": "#81a1c1",
+ "syntax-info": "#81a1c1",
+ "markdown-heading": "#88c0d0",
+ "markdown-text": "#e5e9f0",
+ "markdown-link": "#88c0d0",
+ "markdown-link-text": "#81a1c1",
+ "markdown-code": "#a3be8c",
+ "markdown-block-quote": "#d08770",
+ "markdown-emph": "#d08770",
+ "markdown-strong": "#bf616a",
+ "markdown-horizontal-rule": "#2f384a",
+ "markdown-list-item": "#88c0d0",
+ "markdown-list-enumeration": "#81a1c1",
+ "markdown-image": "#88c0d0",
+ "markdown-image-text": "#81a1c1",
+ "markdown-code-block": "#cbd3e1"
+ }
+ }
+}
diff --git a/packages/ui/src/theme/themes/oc-1.json b/packages/ui/src/theme/themes/oc-1.json
new file mode 100644
index 00000000000..9f54bd6b44f
--- /dev/null
+++ b/packages/ui/src/theme/themes/oc-1.json
@@ -0,0 +1,535 @@
+{
+ "$schema": "https://opencode.ai/desktop-theme.json",
+ "name": "OC-1",
+ "id": "oc-1",
+ "light": {
+ "seeds": {
+ "neutral": "#8e8b8b",
+ "primary": "#dcde8d",
+ "success": "#12c905",
+ "warning": "#ffdc17",
+ "error": "#fc533a",
+ "info": "#a753ae",
+ "interactive": "#034cff",
+ "diffAdd": "#9ff29a",
+ "diffDelete": "#fc533a"
+ },
+ "overrides": {
+ "background-base": "#f8f7f7",
+ "background-weak": "var(--smoke-light-3)",
+ "background-strong": "var(--smoke-light-1)",
+ "background-stronger": "#fcfcfc",
+ "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)",
+ "surface-base-interactive-active": "var(--cobalt-light-alpha-3)",
+ "base2": "var(--smoke-light-alpha-2)",
+ "base3": "var(--smoke-light-alpha-2)",
+ "surface-inset-base": "var(--smoke-light-alpha-2)",
+ "surface-inset-base-hover": "var(--smoke-light-alpha-3)",
+ "surface-inset-strong": "#1f000017",
+ "surface-inset-strong-hover": "#1f000017",
+ "surface-raised-base": "var(--smoke-light-alpha-1)",
+ "surface-float-base": "var(--smoke-dark-1)",
+ "surface-float-base-hover": "var(--smoke-dark-2)",
+ "surface-raised-base-hover": "var(--smoke-light-alpha-2)",
+ "surface-raised-base-active": "var(--smoke-light-alpha-3)",
+ "surface-raised-strong": "var(--smoke-light-1)",
+ "surface-raised-strong-hover": "var(--white)",
+ "surface-raised-stronger": "var(--white)",
+ "surface-raised-stronger-hover": "var(--white)",
+ "surface-weak": "var(--smoke-light-alpha-3)",
+ "surface-weaker": "var(--smoke-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(--smoke-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": "#dafbe0",
+ "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)",
+ "input-base": "var(--smoke-light-1)",
+ "input-hover": "var(--smoke-light-2)",
+ "input-active": "var(--cobalt-light-1)",
+ "input-selected": "var(--cobalt-light-4)",
+ "input-focus": "var(--cobalt-light-1)",
+ "input-disabled": "var(--smoke-light-4)",
+ "text-base": "var(--smoke-light-11)",
+ "text-weak": "var(--smoke-light-9)",
+ "text-weaker": "var(--smoke-light-8)",
+ "text-strong": "var(--smoke-light-12)",
+ "text-invert-base": "var(--smoke-dark-alpha-11)",
+ "text-invert-weak": "var(--smoke-dark-alpha-9)",
+ "text-invert-weaker": "var(--smoke-dark-alpha-8)",
+ "text-invert-strong": "var(--smoke-dark-alpha-12)",
+ "text-interactive-base": "var(--cobalt-light-9)",
+ "text-on-brand-base": "var(--smoke-light-alpha-11)",
+ "text-on-interactive-base": "var(--smoke-light-1)",
+ "text-on-interactive-weak": "var(--smoke-dark-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(--smoke-dark-alpha-11)",
+ "text-on-info-base": "var(--smoke-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(--smoke-dark-alpha-9)",
+ "text-on-info-strong": "var(--smoke-dark-alpha-12)",
+ "text-on-warning-weak": "var(--smoke-dark-alpha-9)",
+ "text-on-warning-strong": "var(--smoke-dark-alpha-12)",
+ "text-on-success-weak": "var(--apple-light-6)",
+ "text-on-success-strong": "var(--apple-light-12)",
+ "text-on-brand-weak": "var(--smoke-light-alpha-9)",
+ "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": "#faf9f9",
+ "border-base": "var(--smoke-light-alpha-7)",
+ "border-hover": "var(--smoke-light-alpha-8)",
+ "border-active": "var(--smoke-light-alpha-9)",
+ "border-selected": "var(--cobalt-light-alpha-9)",
+ "border-disabled": "var(--smoke-light-alpha-8)",
+ "border-focus": "var(--smoke-light-alpha-9)",
+ "border-weak-base": "var(--smoke-light-alpha-5)",
+ "border-strong-base": "var(--smoke-light-alpha-7)",
+ "border-strong-hover": "var(--smoke-light-alpha-8)",
+ "border-strong-active": "var(--smoke-light-alpha-7)",
+ "border-strong-selected": "var(--cobalt-light-alpha-6)",
+ "border-strong-disabled": "var(--smoke-light-alpha-6)",
+ "border-strong-focus": "var(--smoke-light-alpha-7)",
+ "border-weak-hover": "var(--smoke-light-alpha-6)",
+ "border-weak-active": "var(--smoke-light-alpha-7)",
+ "border-weak-selected": "var(--cobalt-light-alpha-5)",
+ "border-weak-disabled": "var(--smoke-light-alpha-6)",
+ "border-weak-focus": "var(--smoke-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(--smoke-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(--smoke-light-9)",
+ "icon-hover": "var(--smoke-light-11)",
+ "icon-active": "var(--smoke-light-12)",
+ "icon-selected": "var(--smoke-light-12)",
+ "icon-disabled": "var(--smoke-light-8)",
+ "icon-focus": "var(--smoke-light-12)",
+ "icon-invert-base": "#ffffff",
+ "icon-weak-base": "var(--smoke-light-7)",
+ "icon-weak-hover": "var(--smoke-light-8)",
+ "icon-weak-active": "var(--smoke-light-9)",
+ "icon-weak-selected": "var(--smoke-light-10)",
+ "icon-weak-disabled": "var(--smoke-light-6)",
+ "icon-weak-focus": "var(--smoke-light-9)",
+ "icon-strong-base": "var(--smoke-light-12)",
+ "icon-strong-hover": "#151313",
+ "icon-strong-active": "#020202",
+ "icon-strong-selected": "#020202",
+ "icon-strong-disabled": "var(--smoke-light-8)",
+ "icon-strong-focus": "#020202",
+ "icon-brand-base": "var(--smoke-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(--smoke-light-alpha-11)",
+ "icon-on-brand-hover": "var(--smoke-light-alpha-12)",
+ "icon-on-brand-selected": "var(--smoke-light-alpha-12)",
+ "icon-on-interactive-base": "var(--smoke-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-weak)",
+ "syntax-regexp": "var(--text-base)",
+ "syntax-string": "#006656",
+ "syntax-keyword": "var(--text-weak)",
+ "syntax-primitive": "#fb4804",
+ "syntax-operator": "var(--text-base)",
+ "syntax-variable": "var(--text-strong)",
+ "syntax-property": "#ed6dc8",
+ "syntax-type": "#596600",
+ "syntax-constant": "#007b80",
+ "syntax-punctuation": "var(--text-base)",
+ "syntax-object": "var(--text-strong)",
+ "syntax-success": "var(--apple-light-10)",
+ "syntax-warning": "var(--amber-light-10)",
+ "syntax-critical": "var(--ember-light-10)",
+ "syntax-info": "#0092a8",
+ "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(--smoke-light-alpha-3)",
+ "border-weaker-hover": "var(--smoke-light-alpha-4)",
+ "border-weaker-active": "var(--smoke-light-alpha-6)",
+ "border-weaker-selected": "var(--cobalt-light-alpha-4)",
+ "border-weaker-disabled": "var(--smoke-light-alpha-2)",
+ "border-weaker-focus": "var(--smoke-light-alpha-6)",
+ "button-ghost-hover": "var(--smoke-light-alpha-2)",
+ "button-ghost-hover2": "var(--smoke-light-alpha-3)",
+ "avatar-background-pink": "#feeef8",
+ "avatar-background-mint": "#e1fbf4",
+ "avatar-background-orange": "#fff1e7",
+ "avatar-background-purple": "#f9f1fe",
+ "avatar-background-cyan": "#e7f9fb",
+ "avatar-background-lime": "#eefadc",
+ "avatar-text-pink": "#cd1d8d",
+ "avatar-text-mint": "#147d6f",
+ "avatar-text-orange": "#ed5f00",
+ "avatar-text-purple": "#8445bc",
+ "avatar-text-cyan": "#0894b3",
+ "avatar-text-lime": "#5d770d"
+ }
+ },
+ "dark": {
+ "seeds": {
+ "neutral": "#716c6b",
+ "primary": "#fab283",
+ "success": "#12c905",
+ "warning": "#fcd53a",
+ "error": "#fc533a",
+ "info": "#edb2f1",
+ "interactive": "#034cff",
+ "diffAdd": "#c8ffc4",
+ "diffDelete": "#fc533a"
+ },
+ "overrides": {
+ "background-base": "var(--smoke-dark-1)",
+ "background-weak": "#1c1717",
+ "background-strong": "#151313",
+ "background-stronger": "#191515",
+ "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)",
+ "surface-base-interactive-active": "var(--cobalt-dark-alpha-2)",
+ "base2": "var(--smoke-dark-alpha-2)",
+ "base3": "var(--smoke-dark-alpha-2)",
+ "surface-inset-base": "#0e0b0b7f",
+ "surface-inset-base-hover": "#0e0b0b7f",
+ "surface-inset-strong": "#060505cc",
+ "surface-inset-strong-hover": "#060505cc",
+ "surface-raised-base": "var(--smoke-dark-alpha-3)",
+ "surface-float-base": "var(--smoke-dark-1)",
+ "surface-float-base-hover": "var(--smoke-dark-2)",
+ "surface-raised-base-hover": "var(--smoke-dark-alpha-4)",
+ "surface-raised-base-active": "var(--smoke-dark-alpha-5)",
+ "surface-raised-strong": "var(--smoke-dark-alpha-4)",
+ "surface-raised-strong-hover": "var(--smoke-dark-alpha-6)",
+ "surface-raised-stronger": "var(--smoke-dark-alpha-6)",
+ "surface-raised-stronger-hover": "var(--smoke-dark-alpha-7)",
+ "surface-weak": "var(--smoke-dark-alpha-4)",
+ "surface-weaker": "var(--smoke-dark-alpha-5)",
+ "surface-strong": "var(--smoke-dark-alpha-7)",
+ "surface-raised-stronger-non-alpha": "var(--smoke-dark-3)",
+ "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-dark-3)",
+ "surface-critical-weak": "var(--ember-dark-2)",
+ "surface-critical-strong": "var(--ember-dark-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-hidden-weak": "var(--blue-dark-1)",
+ "surface-diff-hidden-weaker": "var(--blue-dark-3)",
+ "surface-diff-hidden-strong": "var(--blue-dark-5)",
+ "surface-diff-hidden-stronger": "var(--blue-dark-11)",
+ "surface-diff-add-base": "var(--mint-dark-3)",
+ "surface-diff-add-weak": "var(--mint-dark-4)",
+ "surface-diff-add-weaker": "var(--mint-dark-3)",
+ "surface-diff-add-strong": "var(--mint-dark-5)",
+ "surface-diff-add-stronger": "var(--mint-dark-11)",
+ "surface-diff-delete-base": "var(--ember-dark-3)",
+ "surface-diff-delete-weak": "var(--ember-dark-4)",
+ "surface-diff-delete-weaker": "var(--ember-dark-3)",
+ "surface-diff-delete-strong": "var(--ember-dark-5)",
+ "surface-diff-delete-stronger": "var(--ember-dark-11)",
+ "input-base": "var(--smoke-dark-2)",
+ "input-hover": "var(--smoke-dark-2)",
+ "input-active": "var(--cobalt-dark-1)",
+ "input-selected": "var(--cobalt-dark-2)",
+ "input-focus": "var(--cobalt-dark-1)",
+ "input-disabled": "var(--smoke-dark-4)",
+ "text-base": "var(--smoke-dark-alpha-11)",
+ "text-weak": "var(--smoke-dark-alpha-9)",
+ "text-weaker": "var(--smoke-dark-alpha-8)",
+ "text-strong": "var(--smoke-dark-alpha-12)",
+ "text-invert-base": "var(--smoke-dark-alpha-11)",
+ "text-invert-weak": "var(--smoke-dark-alpha-9)",
+ "text-invert-weaker": "var(--smoke-dark-alpha-8)",
+ "text-invert-strong": "var(--smoke-dark-alpha-12)",
+ "text-interactive-base": "var(--cobalt-dark-11)",
+ "text-on-brand-base": "var(--smoke-dark-alpha-11)",
+ "text-on-interactive-base": "var(--smoke-dark-12)",
+ "text-on-interactive-weak": "var(--smoke-dark-alpha-11)",
+ "text-on-success-base": "var(--apple-dark-9)",
+ "text-on-critical-base": "var(--ember-dark-9)",
+ "text-on-critical-weak": "var(--ember-dark-8)",
+ "text-on-critical-strong": "var(--ember-dark-12)",
+ "text-on-warning-base": "var(--smoke-dark-alpha-11)",
+ "text-on-info-base": "var(--smoke-dark-alpha-11)",
+ "text-diff-add-base": "var(--mint-dark-11)",
+ "text-diff-delete-base": "var(--ember-dark-9)",
+ "text-diff-delete-strong": "var(--ember-dark-12)",
+ "text-diff-add-strong": "var(--mint-dark-8)",
+ "text-on-info-weak": "var(--smoke-dark-alpha-9)",
+ "text-on-info-strong": "var(--smoke-dark-alpha-12)",
+ "text-on-warning-weak": "var(--smoke-dark-alpha-9)",
+ "text-on-warning-strong": "var(--smoke-dark-alpha-12)",
+ "text-on-success-weak": "var(--apple-dark-8)",
+ "text-on-success-strong": "var(--apple-dark-12)",
+ "text-on-brand-weak": "var(--smoke-dark-alpha-9)",
+ "text-on-brand-weaker": "var(--smoke-dark-alpha-8)",
+ "text-on-brand-strong": "var(--smoke-dark-alpha-12)",
+ "button-secondary-base": "#231f1f",
+ "button-secondary-hover": "#2a2727",
+ "border-base": "var(--smoke-dark-alpha-7)",
+ "border-hover": "var(--smoke-dark-alpha-8)",
+ "border-active": "var(--smoke-dark-alpha-9)",
+ "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-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)",
+ "border-strong-selected": "var(--cobalt-dark-alpha-6)",
+ "border-strong-disabled": "var(--smoke-dark-alpha-6)",
+ "border-strong-focus": "var(--smoke-dark-alpha-8)",
+ "border-weak-hover": "var(--smoke-dark-alpha-7)",
+ "border-weak-active": "var(--smoke-dark-alpha-8)",
+ "border-weak-selected": "var(--cobalt-dark-alpha-6)",
+ "border-weak-disabled": "var(--smoke-dark-alpha-6)",
+ "border-weak-focus": "var(--smoke-dark-alpha-8)",
+ "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(--smoke-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-dark-5)",
+ "border-critical-hover": "var(--ember-dark-7)",
+ "border-critical-selected": "var(--ember-dark-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(--smoke-dark-9)",
+ "icon-hover": "var(--smoke-dark-10)",
+ "icon-active": "var(--smoke-dark-11)",
+ "icon-selected": "var(--smoke-dark-12)",
+ "icon-disabled": "var(--smoke-dark-7)",
+ "icon-focus": "var(--smoke-dark-12)",
+ "icon-invert-base": "var(--smoke-dark-1)",
+ "icon-weak-base": "var(--smoke-dark-6)",
+ "icon-weak-hover": "var(--smoke-light-7)",
+ "icon-weak-active": "var(--smoke-light-8)",
+ "icon-weak-selected": "var(--smoke-light-9)",
+ "icon-weak-disabled": "var(--smoke-light-4)",
+ "icon-weak-focus": "var(--smoke-light-9)",
+ "icon-strong-base": "var(--smoke-dark-12)",
+ "icon-strong-hover": "#f6f3f3",
+ "icon-strong-active": "#fcfcfc",
+ "icon-strong-selected": "#fdfcfc",
+ "icon-strong-disabled": "var(--smoke-dark-8)",
+ "icon-strong-focus": "#fdfcfc",
+ "icon-brand-base": "var(--white)",
+ "icon-interactive-base": "var(--cobalt-dark-9)",
+ "icon-success-base": "var(--apple-dark-7)",
+ "icon-success-hover": "var(--apple-dark-8)",
+ "icon-success-active": "var(--apple-dark-11)",
+ "icon-warning-base": "var(--amber-dark-7)",
+ "icon-warning-hover": "var(--amber-dark-8)",
+ "icon-warning-active": "var(--amber-dark-11)",
+ "icon-critical-base": "var(--ember-dark-9)",
+ "icon-critical-hover": "var(--ember-dark-11)",
+ "icon-critical-active": "var(--ember-dark-12)",
+ "icon-info-base": "var(--lilac-dark-7)",
+ "icon-info-hover": "var(--lilac-dark-8)",
+ "icon-info-active": "var(--lilac-dark-11)",
+ "icon-on-brand-base": "var(--smoke-light-alpha-11)",
+ "icon-on-brand-hover": "var(--smoke-light-alpha-12)",
+ "icon-on-brand-selected": "var(--smoke-light-alpha-12)",
+ "icon-on-interactive-base": "var(--smoke-dark-12)",
+ "icon-agent-plan-base": "var(--purple-dark-9)",
+ "icon-agent-docs-base": "var(--amber-dark-9)",
+ "icon-agent-ask-base": "var(--cyan-dark-9)",
+ "icon-agent-build-base": "var(--cobalt-dark-11)",
+ "icon-on-success-base": "var(--apple-dark-alpha-9)",
+ "icon-on-success-hover": "var(--apple-dark-alpha-10)",
+ "icon-on-success-selected": "var(--apple-dark-alpha-11)",
+ "icon-on-warning-base": "var(--amber-darkalpha-9)",
+ "icon-on-warning-hover": "var(--amber-darkalpha-10)",
+ "icon-on-warning-selected": "var(--amber-darkalpha-11)",
+ "icon-on-critical-base": "var(--ember-dark-alpha-9)",
+ "icon-on-critical-hover": "var(--ember-dark-alpha-10)",
+ "icon-on-critical-selected": "var(--ember-dark-alpha-11)",
+ "icon-on-info-base": "var(--lilac-dark-9)",
+ "icon-on-info-hover": "var(--lilac-dark-alpha-10)",
+ "icon-on-info-selected": "var(--lilac-dark-alpha-11)",
+ "icon-diff-add-base": "var(--mint-dark-11)",
+ "icon-diff-add-hover": "var(--mint-dark-10)",
+ "icon-diff-add-active": "var(--mint-dark-11)",
+ "icon-diff-delete-base": "var(--ember-dark-9)",
+ "icon-diff-delete-hover": "var(--ember-dark-10)",
+ "syntax-comment": "var(--text-weak)",
+ "syntax-regexp": "var(--text-base)",
+ "syntax-string": "#00ceb9",
+ "syntax-keyword": "var(--text-weak)",
+ "syntax-primitive": "#ffba92",
+ "syntax-operator": "var(--text-weak)",
+ "syntax-variable": "var(--text-strong)",
+ "syntax-property": "#ff9ae2",
+ "syntax-type": "#ecf58c",
+ "syntax-constant": "#93e9f6",
+ "syntax-punctuation": "var(--text-weak)",
+ "syntax-object": "var(--text-strong)",
+ "syntax-success": "var(--apple-dark-10)",
+ "syntax-warning": "var(--amber-dark-10)",
+ "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",
+ "markdown-link-text": "#56b6c2",
+ "markdown-code": "#7fd88f",
+ "markdown-block-quote": "#e5c07b",
+ "markdown-emph": "#e5c07b",
+ "markdown-strong": "#f5a742",
+ "markdown-horizontal-rule": "#808080",
+ "markdown-list-item": "#fab283",
+ "markdown-list-enumeration": "#56b6c2",
+ "markdown-image": "#fab283",
+ "markdown-image-text": "#56b6c2",
+ "markdown-code-block": "#eeeeee",
+ "border-color": "#ffffff",
+ "border-weaker-base": "var(--smoke-dark-alpha-3)",
+ "border-weaker-hover": "var(--smoke-dark-alpha-4)",
+ "border-weaker-active": "var(--smoke-dark-alpha-6)",
+ "border-weaker-selected": "var(--cobalt-dark-alpha-3)",
+ "border-weaker-disabled": "var(--smoke-dark-alpha-2)",
+ "border-weaker-focus": "var(--smoke-dark-alpha-6)",
+ "button-ghost-hover": "var(--smoke-dark-alpha-2)",
+ "button-ghost-hover2": "var(--smoke-dark-alpha-3)",
+ "avatar-background-pink": "#501b3f",
+ "avatar-background-mint": "#033a34",
+ "avatar-background-orange": "#5f2a06",
+ "avatar-background-purple": "#432155",
+ "avatar-background-cyan": "#0f3058",
+ "avatar-background-lime": "#2b3711",
+ "avatar-text-pink": "#e34ba9",
+ "avatar-text-mint": "#95f3d9",
+ "avatar-text-orange": "#ff802b",
+ "avatar-text-purple": "#9d5bd2",
+ "avatar-text-cyan": "#369eff",
+ "avatar-text-lime": "#c4f042"
+ }
+ }
+}
diff --git a/packages/ui/src/theme/themes/onedarkpro.json b/packages/ui/src/theme/themes/onedarkpro.json
new file mode 100644
index 00000000000..ce01511e85c
--- /dev/null
+++ b/packages/ui/src/theme/themes/onedarkpro.json
@@ -0,0 +1,131 @@
+{
+ "$schema": "https://opencode.ai/desktop-theme.json",
+ "name": "One Dark Pro",
+ "id": "onedarkpro",
+ "light": {
+ "seeds": {
+ "neutral": "#f5f6f8",
+ "primary": "#528bff",
+ "success": "#4fa66d",
+ "warning": "#d19a66",
+ "error": "#e06c75",
+ "info": "#61afef",
+ "interactive": "#528bff",
+ "diffAdd": "#c2ebcf",
+ "diffDelete": "#f7c1c5"
+ },
+ "overrides": {
+ "background-base": "#f5f6f8",
+ "background-weak": "#eef0f4",
+ "background-strong": "#fafbfc",
+ "background-stronger": "#ffffff",
+ "border-weak-base": "#dee2eb",
+ "border-weak-hover": "#d4d9e3",
+ "border-weak-active": "#caced6",
+ "border-weak-selected": "#bec4d0",
+ "border-weak-disabled": "#f4f6fb",
+ "border-weak-focus": "#c4cada",
+ "border-base": "#b5bccd",
+ "border-hover": "#aab1c2",
+ "border-active": "#a0a7b8",
+ "border-selected": "#959cae",
+ "border-disabled": "#eceef4",
+ "border-focus": "#a6adbf",
+ "border-strong-base": "#747c92",
+ "border-strong-hover": "#6a7287",
+ "border-strong-active": "#60687c",
+ "border-strong-selected": "#565e71",
+ "border-strong-disabled": "#cbd0dd",
+ "border-strong-focus": "#666d82",
+ "surface-diff-add-base": "#e5f4ea",
+ "surface-diff-delete-base": "#fde7ea",
+ "surface-diff-hidden-base": "#e4e8f4",
+ "text-base": "#2b303b",
+ "text-weak": "#6b717f",
+ "text-strong": "#0e1118",
+ "syntax-string": "#4fa66d",
+ "syntax-primitive": "#d85462",
+ "syntax-property": "#528bff",
+ "syntax-type": "#d19a66",
+ "syntax-constant": "#61afef",
+ "syntax-info": "#61afef",
+ "markdown-heading": "#528bff",
+ "markdown-text": "#2b303b",
+ "markdown-link": "#528bff",
+ "markdown-link-text": "#61afef",
+ "markdown-code": "#4fa66d",
+ "markdown-block-quote": "#d19a66",
+ "markdown-emph": "#d19a66",
+ "markdown-strong": "#d85462",
+ "markdown-horizontal-rule": "#d3d7e4",
+ "markdown-list-item": "#528bff",
+ "markdown-list-enumeration": "#61afef",
+ "markdown-image": "#528bff",
+ "markdown-image-text": "#61afef",
+ "markdown-code-block": "#528bff"
+ }
+ },
+ "dark": {
+ "seeds": {
+ "neutral": "#1e222a",
+ "primary": "#61afef",
+ "success": "#98c379",
+ "warning": "#e5c07b",
+ "error": "#e06c75",
+ "info": "#56b6c2",
+ "interactive": "#61afef",
+ "diffAdd": "#4b815a",
+ "diffDelete": "#b2555f"
+ },
+ "overrides": {
+ "background-base": "#1e222a",
+ "background-weak": "#212631",
+ "background-strong": "#1b1f27",
+ "background-stronger": "#171b23",
+ "border-weak-base": "#323848",
+ "border-weak-hover": "#363d52",
+ "border-weak-active": "#3c435c",
+ "border-weak-selected": "#424967",
+ "border-weak-disabled": "#141720",
+ "border-weak-focus": "#3f4560",
+ "border-base": "#4a5164",
+ "border-hover": "#515871",
+ "border-active": "#585f7e",
+ "border-selected": "#60688a",
+ "border-disabled": "#1a1e27",
+ "border-focus": "#555c79",
+ "border-strong-base": "#6a7390",
+ "border-strong-hover": "#737c9d",
+ "border-strong-active": "#7d87ab",
+ "border-strong-selected": "#8791b8",
+ "border-strong-disabled": "#212533",
+ "border-strong-focus": "#7680a2",
+ "surface-diff-add-base": "#1c2a26",
+ "surface-diff-delete-base": "#2a1c22",
+ "surface-diff-hidden-base": "#232836",
+ "text-base": "#abb2bf",
+ "text-weak": "#818899",
+ "text-strong": "#f6f7fb",
+ "syntax-string": "#98c379",
+ "syntax-primitive": "#e06c75",
+ "syntax-property": "#61afef",
+ "syntax-type": "#e5c07b",
+ "syntax-constant": "#56b6c2",
+ "syntax-info": "#56b6c2",
+ "markdown-heading": "#61afef",
+ "markdown-text": "#abb2bf",
+ "markdown-link": "#61afef",
+ "markdown-link-text": "#56b6c2",
+ "markdown-code": "#98c379",
+ "markdown-block-quote": "#e5c07b",
+ "markdown-emph": "#e5c07b",
+ "markdown-strong": "#e06c75",
+ "markdown-horizontal-rule": "#2d3444",
+ "markdown-list-item": "#61afef",
+ "markdown-list-enumeration": "#56b6c2",
+ "markdown-image": "#61afef",
+ "markdown-image-text": "#56b6c2",
+ "markdown-code-block": "#abb2bf"
+ }
+ }
+}
diff --git a/packages/ui/src/theme/themes/shadesofpurple.json b/packages/ui/src/theme/themes/shadesofpurple.json
new file mode 100644
index 00000000000..bc625770f9d
--- /dev/null
+++ b/packages/ui/src/theme/themes/shadesofpurple.json
@@ -0,0 +1,131 @@
+{
+ "$schema": "https://opencode.ai/desktop-theme.json",
+ "name": "Shades of Purple",
+ "id": "shadesofpurple",
+ "light": {
+ "seeds": {
+ "neutral": "#f7ebff",
+ "primary": "#7a5af8",
+ "success": "#3dd598",
+ "warning": "#f7c948",
+ "error": "#ff6bd5",
+ "info": "#62d4ff",
+ "interactive": "#7a5af8",
+ "diffAdd": "#c8f8da",
+ "diffDelete": "#ffc3ef"
+ },
+ "overrides": {
+ "background-base": "#f7ebff",
+ "background-weak": "#f2e2ff",
+ "background-strong": "#fbf2ff",
+ "background-stronger": "#fff7ff",
+ "border-weak-base": "#e5d3ff",
+ "border-weak-hover": "#dac8f5",
+ "border-weak-active": "#d1bdeb",
+ "border-weak-selected": "#c6b3e1",
+ "border-weak-disabled": "#fcf6ff",
+ "border-weak-focus": "#ccb9e7",
+ "border-base": "#baa4d5",
+ "border-hover": "#b098cb",
+ "border-active": "#a68dc2",
+ "border-selected": "#9b82b8",
+ "border-disabled": "#f1e7ff",
+ "border-focus": "#a692c6",
+ "border-strong-base": "#8769a9",
+ "border-strong-hover": "#7b5c9d",
+ "border-strong-active": "#704f91",
+ "border-strong-selected": "#664587",
+ "border-strong-disabled": "#d8c4f0",
+ "border-strong-focus": "#755495",
+ "surface-diff-add-base": "#edf8f1",
+ "surface-diff-delete-base": "#ffe4f4",
+ "surface-diff-hidden-base": "#e9e4ff",
+ "text-base": "#3b2c59",
+ "text-weak": "#6c568f",
+ "text-strong": "#1c1033",
+ "syntax-string": "#3dd598",
+ "syntax-primitive": "#ff6bd5",
+ "syntax-property": "#7a5af8",
+ "syntax-type": "#f7c948",
+ "syntax-constant": "#62d4ff",
+ "syntax-info": "#62d4ff",
+ "markdown-heading": "#7a5af8",
+ "markdown-text": "#3b2c59",
+ "markdown-link": "#7a5af8",
+ "markdown-link-text": "#62d4ff",
+ "markdown-code": "#3dd598",
+ "markdown-block-quote": "#f7c948",
+ "markdown-emph": "#f7c948",
+ "markdown-strong": "#ff6bd5",
+ "markdown-horizontal-rule": "#decbed",
+ "markdown-list-item": "#7a5af8",
+ "markdown-list-enumeration": "#62d4ff",
+ "markdown-image": "#7a5af8",
+ "markdown-image-text": "#62d4ff",
+ "markdown-code-block": "#7a5af8"
+ }
+ },
+ "dark": {
+ "seeds": {
+ "neutral": "#1a102b",
+ "primary": "#c792ff",
+ "success": "#7be0b0",
+ "warning": "#ffd580",
+ "error": "#ff7ac6",
+ "info": "#7dd4ff",
+ "interactive": "#c792ff",
+ "diffAdd": "#53c39f",
+ "diffDelete": "#d85aa0"
+ },
+ "overrides": {
+ "background-base": "#1a102b",
+ "background-weak": "#1f1434",
+ "background-strong": "#1c122f",
+ "background-stronger": "#170e26",
+ "border-weak-base": "#352552",
+ "border-weak-hover": "#3a2a5d",
+ "border-weak-active": "#402f68",
+ "border-weak-selected": "#463674",
+ "border-weak-disabled": "#10091b",
+ "border-weak-focus": "#3d2d65",
+ "border-base": "#4d3a73",
+ "border-hover": "#553f7f",
+ "border-active": "#5d468c",
+ "border-selected": "#654c99",
+ "border-disabled": "#150d21",
+ "border-focus": "#594283",
+ "border-strong-base": "#7659b0",
+ "border-strong-hover": "#8262be",
+ "border-strong-active": "#8e6ccc",
+ "border-strong-selected": "#9a77da",
+ "border-strong-disabled": "#1c122c",
+ "border-strong-focus": "#8666c4",
+ "surface-diff-add-base": "#142c27",
+ "surface-diff-delete-base": "#2d1424",
+ "surface-diff-hidden-base": "#231737",
+ "text-base": "#f5f0ff",
+ "text-weak": "#c9b6ff",
+ "text-strong": "#ffffff",
+ "syntax-string": "#7be0b0",
+ "syntax-primitive": "#ff7ac6",
+ "syntax-property": "#c792ff",
+ "syntax-type": "#ffd580",
+ "syntax-constant": "#7dd4ff",
+ "syntax-info": "#7dd4ff",
+ "markdown-heading": "#c792ff",
+ "markdown-text": "#f5f0ff",
+ "markdown-link": "#c792ff",
+ "markdown-link-text": "#7dd4ff",
+ "markdown-code": "#7be0b0",
+ "markdown-block-quote": "#ffd580",
+ "markdown-emph": "#ffd580",
+ "markdown-strong": "#ff7ac6",
+ "markdown-horizontal-rule": "#2d1d41",
+ "markdown-list-item": "#c792ff",
+ "markdown-list-enumeration": "#7dd4ff",
+ "markdown-image": "#c792ff",
+ "markdown-image-text": "#7dd4ff",
+ "markdown-code-block": "#f5f0ff"
+ }
+ }
+}
diff --git a/packages/ui/src/theme/themes/solarized.json b/packages/ui/src/theme/themes/solarized.json
new file mode 100644
index 00000000000..7cb44775af6
--- /dev/null
+++ b/packages/ui/src/theme/themes/solarized.json
@@ -0,0 +1,131 @@
+{
+ "$schema": "https://opencode.ai/desktop-theme.json",
+ "name": "Solarized",
+ "id": "solarized",
+ "light": {
+ "seeds": {
+ "neutral": "#fdf6e3",
+ "primary": "#268bd2",
+ "success": "#859900",
+ "warning": "#b58900",
+ "error": "#dc322f",
+ "info": "#2aa198",
+ "interactive": "#268bd2",
+ "diffAdd": "#c6dc7a",
+ "diffDelete": "#f2a1a1"
+ },
+ "overrides": {
+ "background-base": "#fdf6e3",
+ "background-weak": "#f6efda",
+ "background-strong": "#faf3dc",
+ "background-stronger": "#f6edd4",
+ "border-weak-base": "#e3e0cd",
+ "border-weak-hover": "#d9d4c2",
+ "border-weak-active": "#cfcab7",
+ "border-weak-selected": "#c5c0ad",
+ "border-weak-disabled": "#f2edda",
+ "border-weak-focus": "#cbc6b2",
+ "border-base": "#bcb5a0",
+ "border-hover": "#b1aa96",
+ "border-active": "#a59f8c",
+ "border-selected": "#999382",
+ "border-disabled": "#ede7d4",
+ "border-focus": "#aca58f",
+ "border-strong-base": "#8c8572",
+ "border-strong-hover": "#7f7866",
+ "border-strong-active": "#716b5b",
+ "border-strong-selected": "#645f50",
+ "border-strong-disabled": "#d5cdb8",
+ "border-strong-focus": "#78715f",
+ "surface-diff-add-base": "#eef5d6",
+ "surface-diff-delete-base": "#fde4dd",
+ "surface-diff-hidden-base": "#e3ecf3",
+ "text-base": "#586e75",
+ "text-weak": "#7a8c8e",
+ "text-strong": "#073642",
+ "syntax-string": "#859900",
+ "syntax-primitive": "#d33682",
+ "syntax-property": "#268bd2",
+ "syntax-type": "#b58900",
+ "syntax-constant": "#2aa198",
+ "syntax-info": "#2aa198",
+ "markdown-heading": "#268bd2",
+ "markdown-text": "#586e75",
+ "markdown-link": "#268bd2",
+ "markdown-link-text": "#2aa198",
+ "markdown-code": "#859900",
+ "markdown-block-quote": "#b58900",
+ "markdown-emph": "#b58900",
+ "markdown-strong": "#d33682",
+ "markdown-horizontal-rule": "#cfd1bf",
+ "markdown-list-item": "#268bd2",
+ "markdown-list-enumeration": "#2aa198",
+ "markdown-image": "#268bd2",
+ "markdown-image-text": "#2aa198",
+ "markdown-code-block": "#2aa198"
+ }
+ },
+ "dark": {
+ "seeds": {
+ "neutral": "#002b36",
+ "primary": "#6c71c4",
+ "success": "#859900",
+ "warning": "#b58900",
+ "error": "#dc322f",
+ "info": "#2aa198",
+ "interactive": "#6c71c4",
+ "diffAdd": "#4c7654",
+ "diffDelete": "#c34b4b"
+ },
+ "overrides": {
+ "background-base": "#001f27",
+ "background-weak": "#022733",
+ "background-strong": "#01222b",
+ "background-stronger": "#032830",
+ "border-weak-base": "#20373f",
+ "border-weak-hover": "#243e47",
+ "border-weak-active": "#28434f",
+ "border-weak-selected": "#2d4958",
+ "border-weak-disabled": "#0f2026",
+ "border-weak-focus": "#2a4552",
+ "border-base": "#31505b",
+ "border-hover": "#365765",
+ "border-active": "#3c5e70",
+ "border-selected": "#42657a",
+ "border-disabled": "#13272e",
+ "border-focus": "#3a5a6b",
+ "border-strong-base": "#4a7887",
+ "border-strong-hover": "#528294",
+ "border-strong-active": "#5a8ca1",
+ "border-strong-selected": "#6396ae",
+ "border-strong-disabled": "#1b323b",
+ "border-strong-focus": "#56879a",
+ "surface-diff-add-base": "#0f2f29",
+ "surface-diff-delete-base": "#321c1c",
+ "surface-diff-hidden-base": "#0f3844",
+ "text-base": "#93a1a1",
+ "text-weak": "#6c7f80",
+ "text-strong": "#fdf6e3",
+ "syntax-string": "#859900",
+ "syntax-primitive": "#d33682",
+ "syntax-property": "#6c71c4",
+ "syntax-type": "#b58900",
+ "syntax-constant": "#2aa198",
+ "syntax-info": "#2aa198",
+ "markdown-heading": "#6c71c4",
+ "markdown-text": "#93a1a1",
+ "markdown-link": "#6c71c4",
+ "markdown-link-text": "#2aa198",
+ "markdown-code": "#859900",
+ "markdown-block-quote": "#b58900",
+ "markdown-emph": "#b58900",
+ "markdown-strong": "#d33682",
+ "markdown-horizontal-rule": "#0e3b46",
+ "markdown-list-item": "#6c71c4",
+ "markdown-list-enumeration": "#2aa198",
+ "markdown-image": "#6c71c4",
+ "markdown-image-text": "#2aa198",
+ "markdown-code-block": "#93a1a1"
+ }
+ }
+}
diff --git a/packages/ui/src/theme/themes/tokyonight.json b/packages/ui/src/theme/themes/tokyonight.json
new file mode 100644
index 00000000000..31d0e8a4743
--- /dev/null
+++ b/packages/ui/src/theme/themes/tokyonight.json
@@ -0,0 +1,155 @@
+{
+ "$schema": "https://opencode.ai/desktop-theme.json",
+ "name": "Tokyonight",
+ "id": "tokyonight",
+ "light": {
+ "seeds": {
+ "neutral": "#e1e2e7",
+ "primary": "#2e7de9",
+ "success": "#587539",
+ "warning": "#8c6c3e",
+ "error": "#c94060",
+ "info": "#007197",
+ "interactive": "#2e7de9",
+ "diffAdd": "#4f8f7b",
+ "diffDelete": "#d05f7c"
+ },
+ "overrides": {
+ "background-base": "#e1e2e7",
+ "background-weak": "#dee0ea",
+ "background-strong": "#e5e6ee",
+ "background-stronger": "#e9eaf1",
+ "border-weak-base": "#cdd0dc",
+ "border-weak-hover": "#c3c6d2",
+ "border-weak-active": "#b9bcc8",
+ "border-weak-selected": "#aeb2bf",
+ "border-weak-disabled": "#e6e7ef",
+ "border-weak-focus": "#b3b6c3",
+ "border-base": "#a7abbb",
+ "border-hover": "#9ba0b1",
+ "border-active": "#9095a8",
+ "border-selected": "#83889e",
+ "border-disabled": "#dedfe6",
+ "border-focus": "#9599a8",
+ "border-strong-base": "#757b90",
+ "border-strong-hover": "#6a7084",
+ "border-strong-active": "#5f6578",
+ "border-strong-selected": "#545a6d",
+ "border-strong-disabled": "#c4c6d0",
+ "border-strong-focus": "#666b7f",
+ "surface-diff-add-base": "#dfe7da",
+ "surface-diff-delete-base": "#f4dadd",
+ "surface-diff-hidden-base": "#cfd1dd",
+ "text-base": "#273153",
+ "text-weak": "#5c6390",
+ "text-strong": "#1c2544",
+ "syntax-string": "#587539",
+ "syntax-primitive": "#b15c00",
+ "syntax-property": "#9854f1",
+ "syntax-type": "#3760bf",
+ "syntax-constant": "#007197",
+ "syntax-info": "#007197",
+ "markdown-heading": "#9854f1",
+ "markdown-text": "#273153",
+ "markdown-link": "#2e7de9",
+ "markdown-link-text": "#007197",
+ "markdown-code": "#587539",
+ "markdown-block-quote": "#8c6c3e",
+ "markdown-emph": "#8c6c3e",
+ "markdown-strong": "#b15c00",
+ "markdown-horizontal-rule": "#a1a6c5",
+ "markdown-list-item": "#2e7de9",
+ "markdown-list-enumeration": "#007197",
+ "markdown-image": "#2e7de9",
+ "markdown-image-text": "#007197",
+ "markdown-code-block": "#3760bf"
+ }
+ },
+ "dark": {
+ "seeds": {
+ "neutral": "#1a1b26",
+ "primary": "#7aa2f7",
+ "success": "#9ece6a",
+ "warning": "#e0af68",
+ "error": "#f7768e",
+ "info": "#7dcfff",
+ "interactive": "#7aa2f7",
+ "diffAdd": "#41a6b5",
+ "diffDelete": "#c34043"
+ },
+ "overrides": {
+ "background-base": "#0f111a",
+ "background-weak": "#111428",
+ "background-strong": "#101324",
+ "background-stronger": "#13172a",
+ "border-weak-base": "#25283b",
+ "border-weak-hover": "#292c43",
+ "border-weak-active": "#2e314b",
+ "border-weak-selected": "#343755",
+ "border-weak-disabled": "#151727",
+ "border-weak-focus": "#30324f",
+ "border-base": "#3a3e57",
+ "border-hover": "#414264",
+ "border-active": "#474972",
+ "border-selected": "#4f507f",
+ "border-disabled": "#1c1d2d",
+ "border-focus": "#45496f",
+ "border-strong-base": "#5a5f82",
+ "border-strong-hover": "#646994",
+ "border-strong-active": "#6f74a6",
+ "border-strong-selected": "#7a7fb8",
+ "border-strong-disabled": "#23243a",
+ "border-strong-focus": "#6a6f9f",
+ "surface-base": "#1f2335",
+ "base": "#1f2335",
+ "surface-base-hover": "#232840",
+ "surface-base-active": "#262c46",
+ "surface-base-interactive-active": "#2b3357",
+ "base2": "#1f2335",
+ "base3": "#1f2335",
+ "surface-inset-base": "#161a2ab3",
+ "surface-inset-base-hover": "#161a2acc",
+ "surface-inset-strong": "#0d111fcc",
+ "surface-inset-strong-hover": "#0d111fcc",
+ "surface-raised-base": "#242a42",
+ "surface-float-base": "#242b45",
+ "surface-float-base-hover": "#2a3154",
+ "surface-raised-base-hover": "#272e49",
+ "surface-raised-base-active": "#2c3353",
+ "surface-raised-strong": "#31385a",
+ "surface-raised-strong-hover": "#373f6b",
+ "surface-raised-stronger": "#3b4261",
+ "surface-raised-stronger-hover": "#444c82",
+ "surface-weak": "#1b2033",
+ "surface-weaker": "#181d2d",
+ "surface-strong": "#323858",
+ "surface-raised-stronger-non-alpha": "#2b3150",
+ "surface-diff-add-base": "#1c2a38",
+ "surface-diff-delete-base": "#2a1f32",
+ "surface-diff-hidden-base": "#24283b",
+ "text-base": "#c0caf5",
+ "text-weak": "#7a88cf",
+ "text-strong": "#eaeaff",
+ "syntax-string": "#9ece6a",
+ "syntax-primitive": "#ff9e64",
+ "syntax-property": "#bb9af7",
+ "syntax-type": "#e0af68",
+ "syntax-constant": "#7dcfff",
+ "syntax-info": "#7dcfff",
+ "markdown-heading": "#bb9af7",
+ "markdown-text": "#c0caf5",
+ "markdown-link": "#7aa2f7",
+ "markdown-link-text": "#7dcfff",
+ "markdown-code": "#9ece6a",
+ "markdown-block-quote": "#e0af68",
+ "markdown-emph": "#e0af68",
+ "markdown-strong": "#ff9e64",
+ "markdown-horizontal-rule": "#3b4261",
+ "markdown-list-item": "#7aa2f7",
+ "markdown-list-enumeration": "#7dcfff",
+ "markdown-image": "#7aa2f7",
+ "markdown-image-text": "#7dcfff",
+ "markdown-code-block": "#c0caf5"
+ }
+ }
+}
diff --git a/packages/ui/src/theme/types.ts b/packages/ui/src/theme/types.ts
new file mode 100644
index 00000000000..73bd372b45b
--- /dev/null
+++ b/packages/ui/src/theme/types.ts
@@ -0,0 +1,53 @@
+export type HexColor = `#${string}`
+
+export interface OklchColor {
+ l: number // Lightness 0-1
+ c: number // Chroma 0-0.4+
+ h: number // Hue 0-360
+}
+
+export interface ThemeSeedColors {
+ neutral: HexColor
+ primary: HexColor
+ success: HexColor
+ warning: HexColor
+ error: HexColor
+ info: HexColor
+ interactive: HexColor
+ diffAdd: HexColor
+ diffDelete: HexColor
+}
+
+export interface ThemeVariant {
+ seeds: ThemeSeedColors
+ overrides?: Record
+}
+
+export interface DesktopTheme {
+ $schema?: string
+ name: string
+ id: string
+ light: ThemeVariant
+ dark: ThemeVariant
+}
+
+export type TokenCategory =
+ | "background"
+ | "surface"
+ | "text"
+ | "border"
+ | "icon"
+ | "input"
+ | "button"
+ | "syntax"
+ | "markdown"
+ | "diff"
+ | "avatar"
+
+export type ThemeToken = string
+
+export type CssVarRef = `var(--${string})`
+
+export type ColorValue = HexColor | CssVarRef
+
+export type ResolvedTheme = Record
diff --git a/packages/util/package.json b/packages/util/package.json
index f558fdc01c0..a91b5e288ac 100644
--- a/packages/util/package.json
+++ b/packages/util/package.json
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/util",
- "version": "1.0.204",
+ "version": "1.0.207",
"private": true,
"type": "module",
"exports": {
diff --git a/packages/web/package.json b/packages/web/package.json
index 866eaab394a..bd287b7fe60 100644
--- a/packages/web/package.json
+++ b/packages/web/package.json
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/web",
"type": "module",
- "version": "1.0.204",
+ "version": "1.0.207",
"scripts": {
"dev": "astro dev",
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
diff --git a/packages/web/src/content/docs/cli.mdx b/packages/web/src/content/docs/cli.mdx
index 4a826e5b3ff..1553dc80ee9 100644
--- a/packages/web/src/content/docs/cli.mdx
+++ b/packages/web/src/content/docs/cli.mdx
@@ -57,6 +57,33 @@ opencode agent [command]
---
+### attach
+
+Attach a terminal to an already running OpenCode backend server started via `serve` or `web` commands.
+
+```bash
+opencode attach [url]
+```
+
+This allows using the TUI with a remote OpenCode backend. For example:
+
+```bash
+# Start the backend server for web/mobile access
+opencode web --port 4096 --hostname 0.0.0.0
+
+# In another terminal, attach the TUI to the running backend
+opencode attach http://10.20.30.40:4096
+```
+
+#### Flags
+
+| Flag | Short | Description |
+| ----------- | ----- | --------------------------------- |
+| `--dir` | | Working directory to start TUI in |
+| `--session` | `-s` | Session ID to continue |
+
+---
+
#### create
Create a new agent with custom configuration.
@@ -325,7 +352,7 @@ opencode run --attach http://localhost:4096 "Explain async/await in JavaScript"
### serve
-Start a headless opencode server for API access. Check out the [server docs](/docs/server) for the full HTTP interface.
+Start a headless OpenCode server for API access. Check out the [server docs](/docs/server) for the full HTTP interface.
```bash
opencode serve
diff --git a/packages/web/src/content/docs/config.mdx b/packages/web/src/content/docs/config.mdx
index d7f8031782c..751bb2b2146 100644
--- a/packages/web/src/content/docs/config.mdx
+++ b/packages/web/src/content/docs/config.mdx
@@ -378,6 +378,23 @@ You can configure MCP servers you want to use through the `mcp` option.
---
+### Plugins
+
+[Plugins](/docs/plugins) extend OpenCode with custom tools, hooks, and integrations.
+
+Place plugin files in `.opencode/plugin/` or `~/.config/opencode/plugin/`. You can also load plugins from npm through the `plugin` option.
+
+```json title="opencode.json"
+{
+ "$schema": "https://opencode.ai/config.json",
+ "plugin": ["opencode-helicone-session", "@my-org/custom-plugin"]
+}
+```
+
+[Learn more here](/docs/plugins).
+
+---
+
### Instructions
You can configure the instructions for the model you're using through the `instructions` option.
diff --git a/packages/web/src/content/docs/ecosystem.mdx b/packages/web/src/content/docs/ecosystem.mdx
index 9c772b99305..222a77e151a 100644
--- a/packages/web/src/content/docs/ecosystem.mdx
+++ b/packages/web/src/content/docs/ecosystem.mdx
@@ -32,6 +32,8 @@ You can also check out [awesome-opencode](https://github.com/awesome-opencode/aw
| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Clean up markdown tables produced by LLMs |
| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x faster code editing with Morph Fast Apply API and lazy edit markers |
| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Background agents, pre-built LSP/AST/MCP tools, curated agents, Claude Code compatible |
+| [opencode-notificator](https://github.com/panta/opencode-notificator) | Desktop notifications and sound alerts for OpenCode sessions |
+| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Desktop notifications and sound alerts for permission, completion, and error events |
| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | AI-powered automatic Zellij session naming based on OpenCode context |
---
diff --git a/packages/web/src/content/docs/plugins.mdx b/packages/web/src/content/docs/plugins.mdx
index 6be545482c1..e806046c1e0 100644
--- a/packages/web/src/content/docs/plugins.mdx
+++ b/packages/web/src/content/docs/plugins.mdx
@@ -9,19 +9,65 @@ For examples, check out the [plugins](/docs/ecosystem#plugins) created by the co
---
-## Create a plugin
+## Use a plugin
-A plugin is a **JavaScript/TypeScript module** that exports one or more plugin
-functions. Each function receives a context object and returns a hooks object.
+There are two ways to load plugins.
+
+---
+
+### From local files
+
+Place JavaScript or TypeScript files in the plugin directory.
+
+- `.opencode/plugin/` - Project-level plugins
+- `~/.config/opencode/plugin/` - Global plugins
+
+Files in these directories are automatically loaded at startup.
---
-### Location
+### From npm
+
+Specify npm packages in your config file.
+
+```json title="opencode.json"
+{
+ "$schema": "https://opencode.ai/config.json",
+ "plugin": ["opencode-helicone-session", "opencode-wakatime", "@my-org/custom-plugin"]
+}
+```
+
+Both regular and scoped npm packages are supported.
-Plugins are loaded from:
+Browse available plugins in the [ecosystem](/docs/ecosystem#plugins).
+
+---
-1. `.opencode/plugin` directory either in your project
-2. Or, globally in `~/.config/opencode/plugin`
+### How plugins are installed
+
+**npm plugins** are installed automatically using Bun at startup. Packages and their dependencies are cached in `~/.cache/opencode/node_modules/`.
+
+**Local plugins** are loaded directly from the plugin directory. Dependencies are not installed automatically. If your local plugin requires external packages, publish it to npm instead and add it to your config.
+
+---
+
+### Load order
+
+Plugins are loaded from all sources and all hooks run in sequence. The load order is:
+
+1. Global config (`~/.config/opencode/opencode.json`)
+2. Project config (`opencode.json`)
+3. Global plugin directory (`~/.config/opencode/plugin/`)
+4. Project plugin directory (`.opencode/plugin/`)
+
+Duplicate npm packages with the same name and version are loaded once. However, a local plugin and an npm plugin with similar names are both loaded separately.
+
+---
+
+## Create a plugin
+
+A plugin is a **JavaScript/TypeScript module** that exports one or more plugin
+functions. Each function receives a context object and returns a hooks object.
---
diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json
index 5d15e76c2eb..13eaad66aa4 100644
--- a/sdks/vscode/package.json
+++ b/sdks/vscode/package.json
@@ -2,7 +2,7 @@
"name": "opencode",
"displayName": "opencode",
"description": "opencode for VS Code",
- "version": "1.0.204",
+ "version": "1.0.207",
"publisher": "sst-dev",
"repository": {
"type": "git",