diff --git a/docs/cli/themes.md b/docs/cli/themes.md
index ee708d29ed..14697be5da 100644
--- a/docs/cli/themes.md
+++ b/docs/cli/themes.md
@@ -88,6 +88,11 @@ color keys. For example:
- `DiffRemoved` (optional, for removed lines in diffs)
- `DiffModified` (optional, for modified lines in diffs)
+You can also override individual UI text roles by adding a nested `text` object.
+This object supports the keys `primary`, `secondary`, `link`, `accent`, and
+`response`. When `text.response` is provided it takes precedence over
+`text.primary` for rendering model responses in chat.
+
**Required Properties:**
- `name` (must match the key in the `customThemes` object and be a string)
diff --git a/packages/cli/src/ui/components/ShellConfirmationDialog.tsx b/packages/cli/src/ui/components/ShellConfirmationDialog.tsx
index 18613d888f..fb3ebace6e 100644
--- a/packages/cli/src/ui/components/ShellConfirmationDialog.tsx
+++ b/packages/cli/src/ui/components/ShellConfirmationDialog.tsx
@@ -93,7 +93,7 @@ export const ShellConfirmationDialog: React.FC<
>
{commands.map((cmd) => (
-
+
))}
diff --git a/packages/cli/src/ui/components/messages/InfoMessage.tsx b/packages/cli/src/ui/components/messages/InfoMessage.tsx
index e8d09d637e..b8da1a4e20 100644
--- a/packages/cli/src/ui/components/messages/InfoMessage.tsx
+++ b/packages/cli/src/ui/components/messages/InfoMessage.tsx
@@ -23,8 +23,8 @@ export const InfoMessage: React.FC = ({ text }) => {
{prefix}
-
-
+
+
diff --git a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx
index 612ba40076..61880deb67 100644
--- a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx
+++ b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx
@@ -249,9 +249,7 @@ export const ToolConfirmationMessage: React.FC<
bodyContent = (
-
-
-
+
{displayUrls && infoProps.urls && infoProps.urls.length > 0 && (
URLs to fetch:
diff --git a/packages/cli/src/ui/components/messages/WarningMessage.tsx b/packages/cli/src/ui/components/messages/WarningMessage.tsx
index adc86b6f12..c255bc1a47 100644
--- a/packages/cli/src/ui/components/messages/WarningMessage.tsx
+++ b/packages/cli/src/ui/components/messages/WarningMessage.tsx
@@ -6,7 +6,7 @@
import type React from 'react';
import { Box, Text } from 'ink';
-import { Colors } from '../../colors.js';
+import { theme } from '../../semantic-colors.js';
import { RenderInline } from '../../utils/InlineMarkdownRenderer.js';
interface WarningMessageProps {
@@ -20,11 +20,11 @@ export const WarningMessage: React.FC = ({ text }) => {
return (
- {prefix}
+ {prefix}
-
-
+
+
diff --git a/packages/cli/src/ui/themes/no-color.ts b/packages/cli/src/ui/themes/no-color.ts
index 9accca8851..7c22e68b9a 100644
--- a/packages/cli/src/ui/themes/no-color.ts
+++ b/packages/cli/src/ui/themes/no-color.ts
@@ -32,6 +32,7 @@ const noColorSemanticColors: SemanticColors = {
secondary: '',
link: '',
accent: '',
+ response: '',
},
background: {
primary: '',
diff --git a/packages/cli/src/ui/themes/semantic-tokens.ts b/packages/cli/src/ui/themes/semantic-tokens.ts
index 75059d2e0c..794ce745b6 100644
--- a/packages/cli/src/ui/themes/semantic-tokens.ts
+++ b/packages/cli/src/ui/themes/semantic-tokens.ts
@@ -12,6 +12,7 @@ export interface SemanticColors {
secondary: string;
link: string;
accent: string;
+ response: string;
};
background: {
primary: string;
@@ -43,6 +44,7 @@ export const lightSemanticColors: SemanticColors = {
secondary: lightTheme.Gray,
link: lightTheme.AccentBlue,
accent: lightTheme.AccentPurple,
+ response: lightTheme.Foreground,
},
background: {
primary: lightTheme.Background,
@@ -74,6 +76,7 @@ export const darkSemanticColors: SemanticColors = {
secondary: darkTheme.Gray,
link: darkTheme.AccentBlue,
accent: darkTheme.AccentPurple,
+ response: darkTheme.Foreground,
},
background: {
primary: darkTheme.Background,
@@ -105,6 +108,7 @@ export const ansiSemanticColors: SemanticColors = {
secondary: ansiTheme.Gray,
link: ansiTheme.AccentBlue,
accent: ansiTheme.AccentPurple,
+ response: ansiTheme.Foreground,
},
background: {
primary: ansiTheme.Background,
diff --git a/packages/cli/src/ui/themes/theme.ts b/packages/cli/src/ui/themes/theme.ts
index 5dc5cfaae4..e2762773dc 100644
--- a/packages/cli/src/ui/themes/theme.ts
+++ b/packages/cli/src/ui/themes/theme.ts
@@ -38,6 +38,7 @@ export interface CustomTheme {
secondary?: string;
link?: string;
accent?: string;
+ response?: string;
};
background?: {
primary?: string;
@@ -166,6 +167,7 @@ export class Theme {
secondary: this.colors.Gray,
link: this.colors.AccentBlue,
accent: this.colors.AccentPurple,
+ response: this.colors.Foreground,
},
background: {
primary: this.colors.Background,
@@ -427,6 +429,10 @@ export function createCustomTheme(customTheme: CustomTheme): Theme {
secondary: customTheme.text?.secondary ?? colors.Gray,
link: customTheme.text?.link ?? colors.AccentBlue,
accent: customTheme.text?.accent ?? colors.AccentPurple,
+ response:
+ customTheme.text?.response ??
+ customTheme.text?.primary ??
+ colors.Foreground,
},
background: {
primary: customTheme.background?.primary ?? colors.Background,
diff --git a/packages/cli/src/ui/utils/InlineMarkdownRenderer.tsx b/packages/cli/src/ui/utils/InlineMarkdownRenderer.tsx
index 4320c51967..1b35c62bcc 100644
--- a/packages/cli/src/ui/utils/InlineMarkdownRenderer.tsx
+++ b/packages/cli/src/ui/utils/InlineMarkdownRenderer.tsx
@@ -19,12 +19,17 @@ const UNDERLINE_TAG_END_LENGTH = 4; // For ""
interface RenderInlineProps {
text: string;
+ defaultColor?: string;
}
-const RenderInlineInternal: React.FC = ({ text }) => {
+const RenderInlineInternal: React.FC = ({
+ text,
+ defaultColor,
+}) => {
+ const baseColor = defaultColor ?? theme.text.primary;
// Early return for plain text without markdown or URLs
if (!/[*_~`<[https?:]/.test(text)) {
- return {text};
+ return {text};
}
const nodes: React.ReactNode[] = [];
@@ -36,7 +41,7 @@ const RenderInlineInternal: React.FC = ({ text }) => {
while ((match = inlineRegex.exec(text)) !== null) {
if (match.index > lastIndex) {
nodes.push(
-
+
{text.slice(lastIndex, match.index)}
,
);
@@ -53,7 +58,7 @@ const RenderInlineInternal: React.FC = ({ text }) => {
fullMatch.length > BOLD_MARKER_LENGTH * 2
) {
renderedNode = (
-
+
{fullMatch.slice(BOLD_MARKER_LENGTH, -BOLD_MARKER_LENGTH)}
);
@@ -71,7 +76,7 @@ const RenderInlineInternal: React.FC = ({ text }) => {
)
) {
renderedNode = (
-
+
{fullMatch.slice(ITALIC_MARKER_LENGTH, -ITALIC_MARKER_LENGTH)}
);
@@ -81,7 +86,7 @@ const RenderInlineInternal: React.FC = ({ text }) => {
fullMatch.length > STRIKETHROUGH_MARKER_LENGTH * 2
) {
renderedNode = (
-
+
{fullMatch.slice(
STRIKETHROUGH_MARKER_LENGTH,
-STRIKETHROUGH_MARKER_LENGTH,
@@ -111,7 +116,7 @@ const RenderInlineInternal: React.FC = ({ text }) => {
const linkText = linkMatch[1];
const url = linkMatch[2];
renderedNode = (
-
+
{linkText}
({url})
@@ -124,7 +129,7 @@ const RenderInlineInternal: React.FC = ({ text }) => {
UNDERLINE_TAG_START_LENGTH + UNDERLINE_TAG_END_LENGTH - 1 // -1 because length is compared to combined length of start and end tags
) {
renderedNode = (
-
+
{fullMatch.slice(
UNDERLINE_TAG_START_LENGTH,
-UNDERLINE_TAG_END_LENGTH,
@@ -143,12 +148,22 @@ const RenderInlineInternal: React.FC = ({ text }) => {
renderedNode = null;
}
- nodes.push(renderedNode ?? {fullMatch});
+ nodes.push(
+ renderedNode ?? (
+
+ {fullMatch}
+
+ ),
+ );
lastIndex = inlineRegex.lastIndex;
}
if (lastIndex < text.length) {
- nodes.push({text.slice(lastIndex)});
+ nodes.push(
+
+ {text.slice(lastIndex)}
+ ,
+ );
}
return <>{nodes.filter((node) => node !== null)}>;
diff --git a/packages/cli/src/ui/utils/MarkdownDisplay.tsx b/packages/cli/src/ui/utils/MarkdownDisplay.tsx
index 7a95157521..6d7197ea89 100644
--- a/packages/cli/src/ui/utils/MarkdownDisplay.tsx
+++ b/packages/cli/src/ui/utils/MarkdownDisplay.tsx
@@ -35,6 +35,7 @@ const MarkdownDisplayInternal: React.FC = ({
renderMarkdown = true,
}) => {
const settings = useSettings();
+ const responseColor = theme.text.response ?? theme.text.primary;
if (!text) return <>>;
@@ -138,8 +139,8 @@ const MarkdownDisplayInternal: React.FC = ({
// Not a table, treat as regular text
addContentBlock(
-
-
+
+
,
);
@@ -177,8 +178,8 @@ const MarkdownDisplayInternal: React.FC = ({
if (line.trim().length > 0) {
addContentBlock(
-
-
+
+
,
);
@@ -197,35 +198,38 @@ const MarkdownDisplayInternal: React.FC = ({
case 1:
headerNode = (
-
+
);
break;
case 2:
headerNode = (
-
+
);
break;
case 3:
headerNode = (
-
-
+
+
);
break;
case 4:
headerNode = (
-
+
);
break;
default:
headerNode = (
-
-
+
+
);
break;
@@ -268,8 +272,8 @@ const MarkdownDisplayInternal: React.FC = ({
} else {
addContentBlock(
-
-
+
+
,
);
@@ -401,6 +405,7 @@ const RenderListItemInternal: React.FC = ({
const prefix = type === 'ol' ? `${marker}. ` : `${marker} `;
const prefixWidth = prefix.length;
const indentation = leadingWhitespace.length;
+ const listResponseColor = theme.text.response ?? theme.text.primary;
return (
= ({
flexDirection="row"
>
- {prefix}
+ {prefix}
-
-
+
+