diff --git a/packages/tui/src/component/logo.tsx b/packages/tui/src/component/logo.tsx
index db8e8dc71697..1aff29efa400 100644
--- a/packages/tui/src/component/logo.tsx
+++ b/packages/tui/src/component/logo.tsx
@@ -1,61 +1,40 @@
-import { RGBA, TextAttributes } from "@opentui/core"
-import { For, type JSX } from "solid-js"
-import { tint, useTheme } from "../context/theme"
+import { TextAttributes } from "@opentui/core"
+import { For } from "solid-js"
+import { useTheme } from "../context/theme"
+import { useDirectory } from "../context/directory"
import { logo } from "../logo"
export function Logo() {
const { theme } = useTheme()
-
- const renderLine = (line: string, fg: RGBA, bold: boolean): JSX.Element[] => {
- const shadow = tint(theme.background, fg, 0.25)
- const attrs = bold ? TextAttributes.BOLD : undefined
- return Array.from(line).map((char) => {
- if (char === "_") {
- return (
-
- {" "}
-
- )
- }
- if (char === "^") {
- return (
-
- ▀
-
- )
- }
- if (char === "~") {
- return (
-
- ▀
-
- )
- }
- if (char === ",") {
- return (
-
- ▄
-
- )
- }
- return (
-
- {char}
-
- )
- })
- }
+ const directory = useDirectory()
return (
{(line, index) => (
- {renderLine(line, theme.textMuted, false)}
- {renderLine(logo.right[index()], theme.text, true)}
+
+ {line}
+
+
+ {logo.right[index()]}
+
)}
+
+
+ ━━━━━━━━
+
+
+
+ decode runs commands on your behalf to help you build.
+
+
+ Directory {directory()}
+
+
+
)
}
diff --git a/packages/tui/src/component/prompt/index.tsx b/packages/tui/src/component/prompt/index.tsx
index 115c10296ef4..02f6342605c8 100644
--- a/packages/tui/src/component/prompt/index.tsx
+++ b/packages/tui/src/component/prompt/index.tsx
@@ -15,8 +15,7 @@ import path from "path"
import { fileURLToPath } from "url"
import { useLocal } from "../../context/local"
import { Flag } from "@opencode-ai/core/flag/flag"
-import { tint, useTheme } from "../../context/theme"
-import { EmptyBorder, SplitBorder } from "../../ui/border"
+import { useTheme } from "../../context/theme"
import { useTuiPaths, useTuiTerminalEnvironment } from "../../context/runtime"
import { useClipboard } from "../../context/clipboard"
import { Spinner } from "../spinner"
@@ -247,8 +246,7 @@ export function Prompt(props: PromptProps) {
createEffect(() => {
if (!input || input.isDestroyed) return
- if (props.disabled) input.cursorColor = theme.backgroundElement
- if (!props.disabled) input.cursorColor = theme.text
+ input.cursorColor = props.disabled ? theme.backgroundElement : theme.primary
})
const lastUserMessage = createMemo(() => {
@@ -1303,7 +1301,6 @@ export function Prompt(props: PromptProps) {
() => !!local.agent.current() && store.mode === "normal" && showVariant(),
animationsEnabled,
)
- const borderHighlight = createMemo(() => tint(theme.border, highlight(), agentMetaAlpha()))
const placeholderText = createMemo(() => {
if (props.showPlaceholder === false) return undefined
@@ -1345,166 +1342,140 @@ export function Prompt(props: PromptProps) {
return (
<>
(anchor = r)} visible={props.visible !== false} width="100%">
-
+
-
-
-
-
diff --git a/packages/tui/src/context/theme.tsx b/packages/tui/src/context/theme.tsx
index 909dd69ac249..75c713b3a180 100644
--- a/packages/tui/src/context/theme.tsx
+++ b/packages/tui/src/context/theme.tsx
@@ -23,7 +23,6 @@ import { createEffect, createMemo, onCleanup, onMount } from "solid-js"
import { createStore, produce } from "solid-js/store"
import { createSimpleContext } from "./helper"
import { useKV } from "./kv"
-import { useTuiConfig } from "../config"
import { Global } from "@opencode-ai/core/global"
import { Glob } from "@opencode-ai/core/util/glob"
import { readFile } from "node:fs/promises"
@@ -89,11 +88,13 @@ type State = {
ready: boolean
}
+const DEFAULT_THEME = "carbon"
+
const [store, setStore] = createStore({
themes: allThemes(),
mode: "dark",
lock: undefined,
- active: "opencode",
+ active: DEFAULT_THEME,
ready: false,
})
@@ -103,7 +104,6 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
name: "Theme",
init: (props: { mode: "dark" | "light"; source?: ThemeSource }) => {
const renderer = useRenderer()
- const config = useTuiConfig()
const kv = useKV()
const themes = props.source ?? themeSource
const pick = (value: unknown) => {
@@ -118,17 +118,15 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
if (!lock && pick(kv.get("theme_mode")) !== undefined) kv.set("theme_mode", undefined)
draft.mode = mode
draft.lock = lock
- const active = config.theme ?? kv.get("theme", "opencode")
- draft.active = typeof active === "string" ? active : "opencode"
+ // Carbon is decode's identity. Ignore inherited opencode config themes and
+ // legacy "theme" KV state; only an explicit /theme choice made in decode
+ // (stored under "decode_theme") overrides the default.
+ const active = kv.get("decode_theme", DEFAULT_THEME)
+ draft.active = typeof active === "string" ? active : DEFAULT_THEME
draft.ready = false
}),
)
- createEffect(() => {
- const theme = config.theme
- if (theme) setStore("active", theme)
- })
-
function syncCustomThemes() {
return themes
.discover()
@@ -140,7 +138,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
}, {}),
)
})
- .catch(() => setStore("active", "opencode"))
+ .catch(() => setStore("active", DEFAULT_THEME))
}
onMount(() => {
@@ -159,7 +157,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
if (!colors.palette[0]) {
if (hasResolvedSystemTheme) return
setSystemTheme(undefined)
- if (store.active === "system") setStore("active", "opencode")
+ if (store.active === "system") setStore("active", DEFAULT_THEME)
return
}
const next = store.lock ?? terminalMode(colors) ?? mode
@@ -174,7 +172,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
.catch(() => {
if (hasResolvedSystemTheme) return
setSystemTheme(undefined)
- if (store.active === "system") setStore("active", "opencode")
+ if (store.active === "system") setStore("active", DEFAULT_THEME)
})
}
@@ -256,14 +254,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
const values = createMemo(() => {
const active = store.themes[store.active]
if (active) return resolveTheme(active, store.mode)
-
- const saved = kv.get("theme")
- if (typeof saved === "string") {
- const theme = store.themes[saved]
- if (theme) return resolveTheme(theme, store.mode)
- }
-
- return resolveTheme(store.themes.opencode, store.mode)
+ return resolveTheme(store.themes[DEFAULT_THEME], store.mode)
})
createEffect(() => renderer.setBackgroundColor(values().background))
@@ -293,7 +284,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
set(theme: string) {
if (!hasTheme(theme)) return false
setStore("active", theme)
- kv.set("theme", theme)
+ kv.set("decode_theme", theme)
return true
},
get ready() {
diff --git a/packages/tui/src/feature-plugins/sidebar/context.tsx b/packages/tui/src/feature-plugins/sidebar/context.tsx
index f1c99d9679ce..a0554791e911 100644
--- a/packages/tui/src/feature-plugins/sidebar/context.tsx
+++ b/packages/tui/src/feature-plugins/sidebar/context.tsx
@@ -10,6 +10,8 @@ const money = new Intl.NumberFormat("en-US", {
currency: "USD",
})
+const BAR_WIDTH = 24
+
function View(props: { api: TuiPluginApi; session_id: string }) {
const theme = () => props.api.theme.current
const msg = createMemo(() => props.api.state.session.messages(props.session_id))
@@ -34,14 +36,29 @@ function View(props: { api: TuiPluginApi; session_id: string }) {
}
})
+ const bar = createMemo(() => {
+ const filled = Math.min(BAR_WIDTH, Math.round(((state().percent ?? 0) / 100) * BAR_WIDTH))
+ return { filled, rest: BAR_WIDTH - filled }
+ })
+
return (
-
- Context
+
+ CONTEXT
+ {state().percent ?? 0}%
+
+
+ tokens
+ {state().tokens.toLocaleString()}
+
+
+ spent
+ {money.format(cost())}
+
+
+ {"━".repeat(bar().filled)}
+ {"━".repeat(bar().rest)}
- {state().tokens.toLocaleString()} tokens
- {state().percent ?? 0}% used
- {money.format(cost())} spent
)
}
diff --git a/packages/tui/src/feature-plugins/sidebar/files.tsx b/packages/tui/src/feature-plugins/sidebar/files.tsx
index 01f33f647d69..394b664e6a39 100644
--- a/packages/tui/src/feature-plugins/sidebar/files.tsx
+++ b/packages/tui/src/feature-plugins/sidebar/files.tsx
@@ -19,13 +19,19 @@ function View(props: { api: TuiPluginApi; session_id: string }) {
return (
0}>
- list().length > 2 && setOpen((x) => !x)}>
- 2}>
- {open() ? "▼" : "▶"}
-
-
- Modified Files
-
+ list().length > 2 && setOpen((x) => !x)}
+ >
+
+ 2}>
+ {open() ? "▼" : "▶"}
+
+ MODIFIED FILES
+
+ {list().length}
diff --git a/packages/tui/src/feature-plugins/sidebar/footer.tsx b/packages/tui/src/feature-plugins/sidebar/footer.tsx
index cad71a2f752f..c90281550985 100644
--- a/packages/tui/src/feature-plugins/sidebar/footer.tsx
+++ b/packages/tui/src/feature-plugins/sidebar/footer.tsx
@@ -53,7 +53,7 @@ function View(props: { api: TuiPluginApi; sessionID: string }) {
✕
- DCode includes free models so you can start immediately.
+ decode includes free models so you can start immediately.
Connect from 75+ providers to use other models, including Claude, GPT, Gemini etc
@@ -69,9 +69,11 @@ function View(props: { api: TuiPluginApi; sessionID: string }) {
{path().name}
- • D
- Code
+ de
+
+
+ code
{" "}
{props.api.app.version}
diff --git a/packages/tui/src/feature-plugins/sidebar/lsp.tsx b/packages/tui/src/feature-plugins/sidebar/lsp.tsx
index c260cfbd1786..dd452fc416cb 100644
--- a/packages/tui/src/feature-plugins/sidebar/lsp.tsx
+++ b/packages/tui/src/feature-plugins/sidebar/lsp.tsx
@@ -12,13 +12,21 @@ function View(props: { api: TuiPluginApi }) {
return (
- list().length > 2 && setOpen((x) => !x)}>
- 2}>
- {open() ? "▼" : "▶"}
+ list().length > 2 && setOpen((x) => !x)}
+ >
+
+ 2}>
+ {open() ? "▼" : "▶"}
+
+ LSP
+
+ 0} fallback={{off() ? "off" : "idle"}}>
+ {list().filter((item) => item.status === "connected").length} active
-
- LSP
-
diff --git a/packages/tui/src/feature-plugins/sidebar/mcp.tsx b/packages/tui/src/feature-plugins/sidebar/mcp.tsx
index 99d8567ea73c..32c7701660d4 100644
--- a/packages/tui/src/feature-plugins/sidebar/mcp.tsx
+++ b/packages/tui/src/feature-plugins/sidebar/mcp.tsx
@@ -29,17 +29,22 @@ function View(props: { api: TuiPluginApi }) {
return (
0}>
- list().length > 2 && setOpen((x) => !x)}>
- 2}>
- {open() ? "▼" : "▶"}
-
-
- MCP
-
-
- {" "}
- ({on()} active{bad() > 0 ? `, ${bad()} error${bad() > 1 ? "s" : ""}` : ""})
-
+ list().length > 2 && setOpen((x) => !x)}
+ >
+
+ 2}>
+ {open() ? "▼" : "▶"}
+
+ MCP
+
+
+ {on()} connected
+ 0}>
+ · {bad()} failed
diff --git a/packages/tui/src/feature-plugins/sidebar/todo.tsx b/packages/tui/src/feature-plugins/sidebar/todo.tsx
index 15a78b0ebbf8..66e4bf7d32b0 100644
--- a/packages/tui/src/feature-plugins/sidebar/todo.tsx
+++ b/packages/tui/src/feature-plugins/sidebar/todo.tsx
@@ -10,16 +10,25 @@ function View(props: { api: TuiPluginApi; session_id: string }) {
const theme = () => props.api.theme.current
const list = createMemo(() => props.api.state.session.todo(props.session_id))
const show = createMemo(() => list().length > 0 && list().some((item) => item.status !== "completed"))
+ const done = createMemo(() => list().filter((item) => item.status === "completed").length)
return (
- list().length > 2 && setOpen((x) => !x)}>
- 2}>
- {open() ? "▼" : "▶"}
-
-
- Todo
+ list().length > 2 && setOpen((x) => !x)}
+ >
+
+ 2}>
+ {open() ? "▼" : "▶"}
+
+ TODOS
+
+
+ {done()}/{list().length}
diff --git a/packages/tui/src/logo.ts b/packages/tui/src/logo.ts
index aa47986598a2..9ebf3f8edc27 100644
--- a/packages/tui/src/logo.ts
+++ b/packages/tui/src/logo.ts
@@ -1,6 +1,6 @@
export const logo = {
- left: [" ", "█▀▀▄ ", "█__█ ", "▀▀▀ "],
- right: [" ", "█▀▀▀ █▀▀█ █▀▀▄ █▀▀▀", "█___ █__█ █__█ █^^^", "▀▀▀▀ ▀▀▀▀ ▀~~▀ ▀▀▀▀"],
+ left: ["█▀▀▄ █▀▀▀", "█ █ █▀▀ ", "█▄▄▀ █▄▄▄"],
+ right: ["▄▀▀▀ ▄▀▀▄ █▀▀▄ █▀▀▀", "█ █ █ █ █ █▀▀ ", "▀▄▄▄ ▀▄▄▀ █▄▄▀ █▄▄▄"],
}
export const go = {
diff --git a/packages/tui/src/routes/home.tsx b/packages/tui/src/routes/home.tsx
index d4145e2d87be..67f503c40730 100644
--- a/packages/tui/src/routes/home.tsx
+++ b/packages/tui/src/routes/home.tsx
@@ -33,7 +33,8 @@ export function Home() {
const promptMaxWidth = createMemo(() => {
const configured = tuiConfig.prompt?.max_width
if (configured === "auto") return Math.max(75, Math.floor(dimensions().width * 0.7))
- return configured ?? 75
+ // Full-width field by default, matching the carbon home layout.
+ return configured ?? dimensions().width
})
let sent = false
@@ -69,7 +70,7 @@ export function Home() {
return (
-
+
diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx
index a02bbab222e9..de6a38fafa08 100644
--- a/packages/tui/src/routes/session/index.tsx
+++ b/packages/tui/src/routes/session/index.tsx
@@ -245,6 +245,21 @@ export function Session() {
return messages().findLast((x) => x.role === "assistant")
})
+ const budget = createMemo(() => {
+ const last = messages().findLast(
+ (item): item is AssistantMessage => item.role === "assistant" && item.tokens.output > 0,
+ )
+ if (!last) return
+ const tokens =
+ last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write
+ if (tokens <= 0) return
+ const model = sync.data.provider.find((item) => item.id === last.providerID)?.models[last.modelID]
+ return {
+ tokens: Locale.number(tokens),
+ limit: model?.limit.context ? Locale.number(model.limit.context) : undefined,
+ }
+ })
+
const dimensions = useTerminalDimensions()
const [sidebar, setSidebar] = kv.signal<"auto" | "hide">("sidebar", "auto")
const [sidebarOpen, setSidebarOpen] = createSignal(false)
@@ -1162,185 +1177,219 @@ export function Session() {
tui: tuiConfig,
}}
>
-
-
-
- (scroll = r)}
- viewportOptions={{
- paddingRight: showScrollbar() ? 1 : 0,
- }}
- verticalScrollbarOptions={{
- paddingLeft: 1,
- visible: showScrollbar(),
- trackOptions: {
- backgroundColor: theme.backgroundElement,
- foregroundColor: theme.border,
- },
- }}
- stickyScroll={true}
- stickyStart="bottom"
- flexGrow={1}
- scrollAcceleration={scrollAcceleration()}
- >
-
-
- {(message, index) => (
-
-
- {(function () {
- const redoShortcut = useCommandShortcut("session.redo")
- const [hover, setHover] = createSignal(false)
- const dialog = useDialog()
-
- const handleUnrevert = async () => {
- const confirmed = await DialogConfirm.show(
- dialog,
- "Confirm Redo",
- "Are you sure you want to restore the reverted messages?",
- )
- if (confirmed) {
- keymap.dispatchCommand("session.redo")
+
+
+
+ de
+ code
+ │
+
+ {Locale.truncate(session()?.title ?? "", Math.max(10, Math.min(60, dimensions().width - 55)))}
+
+ · {route.sessionID.slice(0, 10)}
+
+
+ {(usage) => (
+
+ {usage().tokens}
+
+ / {usage().limit}
+
+ 0}>
+ · ${(session()?.cost ?? 0).toFixed(2)}
+
+
+ )}
+
+
+
+
+
+ (scroll = r)}
+ viewportOptions={{
+ paddingRight: showScrollbar() ? 1 : 0,
+ }}
+ verticalScrollbarOptions={{
+ paddingLeft: 1,
+ visible: showScrollbar(),
+ trackOptions: {
+ backgroundColor: theme.backgroundElement,
+ foregroundColor: theme.border,
+ },
+ }}
+ stickyScroll={true}
+ stickyStart="bottom"
+ flexGrow={1}
+ scrollAcceleration={scrollAcceleration()}
+ >
+
+
+ {(message, index) => (
+
+
+ {(function () {
+ const redoShortcut = useCommandShortcut("session.redo")
+ const [hover, setHover] = createSignal(false)
+ const dialog = useDialog()
+
+ const handleUnrevert = async () => {
+ const confirmed = await DialogConfirm.show(
+ dialog,
+ "Confirm Redo",
+ "Are you sure you want to restore the reverted messages?",
+ )
+ if (confirmed) {
+ keymap.dispatchCommand("session.redo")
+ }
}
- }
-
- return (
- setHover(true)}
- onMouseOut={() => setHover(false)}
- onMouseUp={handleUnrevert}
- marginTop={1}
- flexShrink={0}
- border={["left"]}
- customBorderChars={SplitBorder.customBorderChars}
- borderColor={theme.backgroundPanel}
- >
+
+ return (
setHover(true)}
+ onMouseOut={() => setHover(false)}
+ onMouseUp={handleUnrevert}
+ marginTop={1}
+ flexShrink={0}
+ border={["left"]}
+ customBorderChars={SplitBorder.customBorderChars}
+ borderColor={theme.backgroundPanel}
>
- {revert()!.reverted.length} message reverted
-
- {redoShortcut()} or /redo to restore
-
-
-
-
- {(file) => (
-
- {file.filename}
- 0}>
- +{file.additions}
-
- 0}>
- -{file.deletions}
-
-
- )}
-
-
-
+
+ {revert()!.reverted.length} message reverted
+
+ {redoShortcut()} or /redo to restore
+
+
+
+
+ {(file) => (
+
+ {file.filename}
+ 0}>
+ +{file.additions}
+
+ 0}>
+ -{file.deletions}
+
+
+ )}
+
+
+
+
-
- )
- })()}
-
- = revert()!.messageID}>
- <>>
-
-
- {
- if (renderer.getSelection()?.getSelectedText()) return
- dialog.replace(() => (
- prompt?.set(promptInfo)}
- />
- ))
- }}
- message={message as UserMessage}
- parts={sync.data.part[message.id] ?? []}
- pending={pending()}
- />
-
-
-
-
-
- )}
-
-
-
- 0}>
-
-
- 0}>
-
-
-
-
-
-
-
-
+ = revert()!.messageID}>
+ <>>
+
+
+ {
+ if (renderer.getSelection()?.getSelectedText()) return
+ dialog.replace(() => (
+ prompt?.set(promptInfo)}
+ />
+ ))
+ }}
+ message={message as UserMessage}
+ parts={sync.data.part[message.id] ?? []}
+ pending={pending()}
+ />
+
+
+
+
+
+ )}
+
+
+
+ 0}>
+
+
+ 0}>
+
+
+
+
+
+
+ {
- toBottom()
- }}
- sessionID={route.sessionID}
- right={}
- />
-
-
-
+ on_submit={toBottom}
+ ref={bind}
+ >
+ {
+ toBottom()
+ }}
+ sessionID={route.sessionID}
+ right={}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -2006,7 +2055,7 @@ function BlockTool(props: {
gap={1}
backgroundColor={hover() ? theme.backgroundMenu : theme.backgroundPanel}
customBorderChars={SplitBorder.customBorderChars}
- borderColor={theme.background}
+ borderColor={theme.border}
onMouseOver={() => props.onClick && setHover(true)}
onMouseOut={() => setHover(false)}
onMouseUp={() => {
diff --git a/packages/tui/src/theme/assets/carbon.json b/packages/tui/src/theme/assets/carbon.json
new file mode 100644
index 000000000000..a1538e363cd6
--- /dev/null
+++ b/packages/tui/src/theme/assets/carbon.json
@@ -0,0 +1,255 @@
+{
+ "$schema": "https://opencode.ai/theme.json",
+ "defs": {
+ "darkBg": "#030403",
+ "darkBgPanel": "#080b09",
+ "darkBgElement": "#0d120f",
+ "darkBgMenu": "#101711",
+ "darkGreen": "#00e676",
+ "darkGreenHi": "#48ffa0",
+ "darkGreenDeep": "#00a854",
+ "darkWhite": "#f2f6f3",
+ "darkText": "#dce5df",
+ "darkMuted": "#5d6c63",
+ "darkFaint": "#313d36",
+ "darkLine": "#1c2620",
+ "darkLineSubtle": "#121814",
+ "darkTeal": "#6fd6b8",
+ "darkWarn": "#e8c268",
+ "darkErr": "#f26d76",
+ "darkAdd": "#35d97c",
+ "darkAddBg": "#0a2416",
+ "darkDelBg": "#26141a",
+ "darkInk": "#02180c",
+ "lightBg": "#fafcfa",
+ "lightBgPanel": "#f1f5f2",
+ "lightBgElement": "#e6ede8",
+ "lightBgMenu": "#dfe8e2",
+ "lightGreen": "#067d4e",
+ "lightGreenHi": "#0aa864",
+ "lightGreenDeep": "#0a5c3c",
+ "lightWhite": "#0b1410",
+ "lightText": "#25332c",
+ "lightMuted": "#64756c",
+ "lightFaint": "#a9b8af",
+ "lightLine": "#d7e1da",
+ "lightLineSubtle": "#e4ece6",
+ "lightTeal": "#0f7a63",
+ "lightWarn": "#a3781a",
+ "lightErr": "#c23540",
+ "lightAdd": "#12824a",
+ "lightAddBg": "#ddf2e4",
+ "lightDelBg": "#f9e0e3"
+ },
+ "theme": {
+ "primary": {
+ "dark": "darkGreen",
+ "light": "lightGreen"
+ },
+ "secondary": {
+ "dark": "darkGreenDeep",
+ "light": "lightGreenDeep"
+ },
+ "accent": {
+ "dark": "darkGreenHi",
+ "light": "lightGreenHi"
+ },
+ "error": {
+ "dark": "darkErr",
+ "light": "lightErr"
+ },
+ "warning": {
+ "dark": "darkWarn",
+ "light": "lightWarn"
+ },
+ "success": {
+ "dark": "darkAdd",
+ "light": "lightAdd"
+ },
+ "info": {
+ "dark": "darkTeal",
+ "light": "lightTeal"
+ },
+ "text": {
+ "dark": "darkText",
+ "light": "lightText"
+ },
+ "textMuted": {
+ "dark": "darkMuted",
+ "light": "lightMuted"
+ },
+ "selectedListItemText": {
+ "dark": "darkInk",
+ "light": "lightBg"
+ },
+ "background": {
+ "dark": "darkBg",
+ "light": "lightBg"
+ },
+ "backgroundPanel": {
+ "dark": "darkBgPanel",
+ "light": "lightBgPanel"
+ },
+ "backgroundElement": {
+ "dark": "darkBgElement",
+ "light": "lightBgElement"
+ },
+ "backgroundMenu": {
+ "dark": "darkBgMenu",
+ "light": "lightBgMenu"
+ },
+ "border": {
+ "dark": "darkLine",
+ "light": "lightLine"
+ },
+ "borderActive": {
+ "dark": "darkGreen",
+ "light": "lightGreen"
+ },
+ "borderSubtle": {
+ "dark": "darkLineSubtle",
+ "light": "lightLineSubtle"
+ },
+ "diffAdded": {
+ "dark": "darkAdd",
+ "light": "lightAdd"
+ },
+ "diffRemoved": {
+ "dark": "darkErr",
+ "light": "lightErr"
+ },
+ "diffContext": {
+ "dark": "darkMuted",
+ "light": "lightMuted"
+ },
+ "diffHunkHeader": {
+ "dark": "darkGreenDeep",
+ "light": "lightGreenDeep"
+ },
+ "diffHighlightAdded": {
+ "dark": "darkGreenHi",
+ "light": "lightGreenHi"
+ },
+ "diffHighlightRemoved": {
+ "dark": "#ff8f98",
+ "light": "#a52833"
+ },
+ "diffAddedBg": {
+ "dark": "darkAddBg",
+ "light": "lightAddBg"
+ },
+ "diffRemovedBg": {
+ "dark": "darkDelBg",
+ "light": "lightDelBg"
+ },
+ "diffContextBg": {
+ "dark": "darkBgPanel",
+ "light": "lightBgPanel"
+ },
+ "diffLineNumber": {
+ "dark": "darkFaint",
+ "light": "lightFaint"
+ },
+ "diffAddedLineNumberBg": {
+ "dark": "#0c2b1a",
+ "light": "#cdeada"
+ },
+ "diffRemovedLineNumberBg": {
+ "dark": "#2b181d",
+ "light": "#f3d3d7"
+ },
+ "markdownText": {
+ "dark": "darkText",
+ "light": "lightText"
+ },
+ "markdownHeading": {
+ "dark": "darkGreen",
+ "light": "lightGreen"
+ },
+ "markdownLink": {
+ "dark": "darkGreenHi",
+ "light": "lightGreenHi"
+ },
+ "markdownLinkText": {
+ "dark": "darkTeal",
+ "light": "lightTeal"
+ },
+ "markdownCode": {
+ "dark": "darkGreenHi",
+ "light": "lightGreenHi"
+ },
+ "markdownBlockQuote": {
+ "dark": "darkMuted",
+ "light": "lightMuted"
+ },
+ "markdownEmph": {
+ "dark": "darkWarn",
+ "light": "lightWarn"
+ },
+ "markdownStrong": {
+ "dark": "darkWhite",
+ "light": "lightWhite"
+ },
+ "markdownHorizontalRule": {
+ "dark": "darkLine",
+ "light": "lightLine"
+ },
+ "markdownListItem": {
+ "dark": "darkGreen",
+ "light": "lightGreen"
+ },
+ "markdownListEnumeration": {
+ "dark": "darkGreenDeep",
+ "light": "lightGreenDeep"
+ },
+ "markdownImage": {
+ "dark": "darkGreenHi",
+ "light": "lightGreenHi"
+ },
+ "markdownImageText": {
+ "dark": "darkTeal",
+ "light": "lightTeal"
+ },
+ "markdownCodeBlock": {
+ "dark": "darkText",
+ "light": "lightText"
+ },
+ "syntaxComment": {
+ "dark": "darkMuted",
+ "light": "lightMuted"
+ },
+ "syntaxKeyword": {
+ "dark": "darkGreen",
+ "light": "lightGreen"
+ },
+ "syntaxFunction": {
+ "dark": "darkGreenHi",
+ "light": "lightGreenHi"
+ },
+ "syntaxVariable": {
+ "dark": "darkText",
+ "light": "lightText"
+ },
+ "syntaxString": {
+ "dark": "darkAdd",
+ "light": "lightAdd"
+ },
+ "syntaxNumber": {
+ "dark": "darkWarn",
+ "light": "lightWarn"
+ },
+ "syntaxType": {
+ "dark": "darkTeal",
+ "light": "lightTeal"
+ },
+ "syntaxOperator": {
+ "dark": "darkGreenDeep",
+ "light": "lightGreenDeep"
+ },
+ "syntaxPunctuation": {
+ "dark": "#93a49a",
+ "light": "#4d5c54"
+ },
+ "thinkingOpacity": 0.6
+ }
+}
diff --git a/packages/tui/src/theme/index.ts b/packages/tui/src/theme/index.ts
index e8a5f2c5a973..3a50d737fece 100644
--- a/packages/tui/src/theme/index.ts
+++ b/packages/tui/src/theme/index.ts
@@ -1,6 +1,7 @@
import { SyntaxStyle, RGBA, type TerminalColors } from "@opentui/core"
import aura from "./assets/aura.json" with { type: "json" }
import ayu from "./assets/ayu.json" with { type: "json" }
+import carbon from "./assets/carbon.json" with { type: "json" }
import carbonfox from "./assets/carbonfox.json" with { type: "json" }
import catppuccinFrappe from "./assets/catppuccin-frappe.json" with { type: "json" }
import catppuccinMacchiato from "./assets/catppuccin-macchiato.json" with { type: "json" }
@@ -130,6 +131,7 @@ export type ThemeJson = {
export const DEFAULT_THEMES: Record = {
aura,
ayu,
+ carbon,
catppuccin,
["catppuccin-frappe"]: catppuccinFrappe,
["catppuccin-macchiato"]: catppuccinMacchiato,
diff --git a/packages/tui/src/util/presentation.ts b/packages/tui/src/util/presentation.ts
index 98f517cbdf38..5f51f702a87b 100644
--- a/packages/tui/src/util/presentation.ts
+++ b/packages/tui/src/util/presentation.ts
@@ -1,35 +1,22 @@
-const logo = {
- left: [" ", "█▀▀▄ ", "█__█ ", "▀▀▀ "],
- right: [" ", "█▀▀▀ █▀▀█ █▀▀▄ █▀▀▀", "█___ █__█ █__█ █^^^", "▀▀▀▀ ▀▀▀▀ ▀~~▀ ▀▀▀▀"],
-}
+import { logo } from "../logo"
const reset = "\x1b[0m"
const bold = "\x1b[1m"
const dim = "\x1b[90m"
+const white = "\x1b[97m"
+const green = "\x1b[38;2;0;230;118m"
function wordmark(pad = "") {
- const draw = (line: string, fg: string, shadow: string, bg: string) =>
- [...line]
- .map((char) => {
- if (char === "_") return `${bg} ${reset}`
- if (char === "^") return `${fg}${bg}▀${reset}`
- if (char === "~") return `${shadow}▀${reset}`
- if (char === " ") return " "
- return `${fg}${char}${reset}`
- })
- .join("")
-
- return logo.left.map((line, index) => {
- const left = draw(line, dim, "\x1b[38;5;235m", "\x1b[48;5;235m")
- const right = draw(logo.right[index] ?? "", reset, "\x1b[38;5;238m", "\x1b[48;5;238m")
- return `${pad}${left} ${right}`
- })
+ return logo.left.map(
+ (line, index) => `${pad}${bold}${white}${line}${reset} ${bold}${green}${logo.right[index] ?? ""}${reset}`,
+ )
}
export function sessionEpilogue(input: { title: string; sessionID?: string }) {
const weak = (text: string) => `${dim}${text.padEnd(10, " ")}${reset}`
return [
...wordmark(" "),
+ ` ${green}━━━━━━━━${reset}`,
"",
` ${weak("Session")}${bold}${input.title}${reset}`,
` ${weak("Continue")}${bold}dcode -s ${input.sessionID}${reset}`,