Skip to content

Commit 1120af0

Browse files
committed
Fixing theme regression bug
1 parent fd584f7 commit 1120af0

18 files changed

Lines changed: 419 additions & 251 deletions

docs/config-reference.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ See [Workspace Safety](./workspace-safety.md) for full details.
343343

344344
| Field | Type | Default | Description |
345345
| ---------------------------- | ------ | ------- | ---------------------------------------------------------------------------------------------- |
346-
| `theme` | string | `"dark"` | Color theme for terminal output. Built-ins include `dark`, `light`, `dracula`, `sandy`, `tui`, `github-dark`, `turkey`, `brazil`, and `australia`. |
346+
| `theme` | string | `"dark"` | Color theme for terminal output. Built-ins include `dark`, `light`, `dracula`, `sandy`, `tui`, `github-dark`, `cappadocia`, `rio`, and `australia`. Legacy `turkey` and `brazil` values still load as aliases. |
347347
| `customThemes` | object | `{}` | Inline custom theme definitions keyed by theme name. Set `theme` to the same key to use one. |
348348
| `autoConfirm` | boolean | `false` | Skip confirmation prompts for safe operations |
349349
| `readFileCharLimit` | number | `300` | Max characters to display from read/find tool output (full content is still sent to the model) |

src/commands/about.ts

Lines changed: 16 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66
import { execSync } from 'node:child_process';
7-
import chalk from 'chalk';
87
import terminalLink from 'terminal-link';
98
import { t } from '../i18n/index.js';
10-
import { getTheme, isThemeInitialized } from '../ui/theme/Theme.js';
9+
import { createCommandTheme } from './commandTheme.js';
1110
import { ASCII_FRIEND } from '../utils/asciiArt.js';
1211
import packageJson from '../../package.json' with { type: 'json' };
1312

@@ -49,53 +48,37 @@ function getVersionString(): string {
4948
* About command - shows information about Autohand
5049
*/
5150
export async function about(): Promise<string | null> {
52-
// Use theme if initialized, otherwise use fallback chalk colors
53-
let accent: (text: string) => string;
54-
let muted: (text: string) => string;
55-
let text: (text: string) => string;
56-
57-
if (isThemeInitialized()) {
58-
const theme = getTheme();
59-
accent = (text: string) => chalk.hex(theme.colors.accent)(text);
60-
muted = (text: string) => chalk.hex(theme.colors.muted)(text);
61-
text = (str: string) => chalk.hex(theme.colors.text)(str);
62-
} else {
63-
// Fallback colors when theme not initialized
64-
accent = (text: string) => chalk.cyan(text);
65-
muted = (text: string) => chalk.gray(text);
66-
text = (text: string) => chalk.white(text);
67-
}
51+
const theme = createCommandTheme();
6852

6953
const lines: string[] = [
70-
chalk.gray(ASCII_FRIEND),
54+
theme.muted(ASCII_FRIEND),
7155
'',
72-
accent(`${t('commands.about.title')} v${getVersionString()}`),
73-
muted(t('commands.about.subtitle')),
56+
theme.accent(`${t('commands.about.title')} v${getVersionString()}`),
57+
theme.muted(t('commands.about.subtitle')),
7458
'',
7559
];
7660

77-
// Links section - make them underlined and cyan to look clickable
7861
const websiteUrl = 'https://autohand.ai';
7962
const githubUrl = 'https://github.com/autohandai/';
8063
const docsUrl = 'https://docs.autohand.ai';
8164

82-
const websiteLink = terminalLink(chalk.cyan.underline('autohand.ai'), websiteUrl);
83-
const githubLink = terminalLink(chalk.cyan.underline('github.com/autohandai/'), githubUrl);
84-
const docsLink = terminalLink(chalk.cyan.underline('docs.autohand.ai'), docsUrl);
65+
const websiteLink = terminalLink(theme.link('autohand.ai'), websiteUrl);
66+
const githubLink = terminalLink(theme.link('github.com/autohandai/'), githubUrl);
67+
const docsLink = terminalLink(theme.link('docs.autohand.ai'), docsUrl);
8568

86-
lines.push(`${text('🌐')} ${text(t('commands.about.website') + ':')} ${websiteLink}`);
87-
lines.push(`${text('📦')} ${text(t('commands.about.github') + ':')} ${githubLink}`);
88-
lines.push(`${text('📚')} ${text(t('commands.about.docs') + ':')} ${docsLink}`);
69+
lines.push(`${theme.text('🌐')} ${theme.text(t('commands.about.website') + ':')} ${websiteLink}`);
70+
lines.push(`${theme.text('📦')} ${theme.text(t('commands.about.github') + ':')} ${githubLink}`);
71+
lines.push(`${theme.text('📚')} ${theme.text(t('commands.about.docs') + ':')} ${docsLink}`);
8972
lines.push('');
9073

9174
// Contribution section
92-
lines.push(text(`💡 ${t('commands.about.contribute')}`));
93-
lines.push(text(` • ${t('commands.about.feedback')}: ${accent('/feedback')}`));
94-
lines.push(text(` • ${t('commands.about.submitPR')}: ${accent('gh pr create')}`));
75+
lines.push(theme.text(`💡 ${t('commands.about.contribute')}`));
76+
lines.push(theme.text(` • ${t('commands.about.feedback')}: ${theme.accent('/feedback')}`));
77+
lines.push(theme.text(` • ${t('commands.about.submitPR')}: ${theme.accent('gh pr create')}`));
9578

9679
const issuesUrl = 'https://github.com/autohandai/code-cli/issues';
97-
const issuesLink = terminalLink(chalk.cyan.underline('github.com/autohandai/code-cli/issues'), issuesUrl);
98-
lines.push(text(` • ${t('commands.about.reportIssues')}: ${issuesLink}`));
80+
const issuesLink = terminalLink(theme.link('github.com/autohandai/code-cli/issues'), issuesUrl);
81+
lines.push(theme.text(` • ${t('commands.about.reportIssues')}: ${issuesLink}`));
9982

10083
return lines.join('\n');
10184
}

src/commands/commandTheme.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* @license
3+
* Copyright 2026 Autohand AI LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import chalk from 'chalk';
8+
import { getTheme, isThemeInitialized } from '../ui/theme/Theme.js';
9+
import type { ColorToken } from '../ui/theme/types.js';
10+
11+
type Styler = (text: string) => string;
12+
13+
export interface CommandTheme {
14+
accent: Styler;
15+
muted: Styler;
16+
text: Styler;
17+
success: Styler;
18+
warning: Styler;
19+
error: Styler;
20+
bold: Styler;
21+
heading: Styler;
22+
link: Styler;
23+
tab: Styler;
24+
selectedTab: Styler;
25+
progressFilled: Styler;
26+
progressEmpty: Styler;
27+
}
28+
29+
export function createCommandTheme(): CommandTheme {
30+
const theme = isThemeInitialized() ? getTheme() : null;
31+
32+
const fg = (token: ColorToken, fallback: Styler): Styler => {
33+
return (value: string) => theme ? theme.fg(token, value) : fallback(value);
34+
};
35+
36+
const accent = fg('accent', chalk.cyan);
37+
const muted = fg('muted', chalk.gray);
38+
const text = fg('text', chalk.white);
39+
const success = fg('success', chalk.green);
40+
const warning = fg('warning', chalk.yellow);
41+
const error = fg('error', chalk.red);
42+
const bold: Styler = (value) => theme ? theme.bold(value) : chalk.bold(value);
43+
44+
return {
45+
accent,
46+
muted,
47+
text,
48+
success,
49+
warning,
50+
error,
51+
bold,
52+
heading: (value) => bold(accent(value)),
53+
link: (value) => theme ? theme.underline(accent(value)) : chalk.cyan.underline(value),
54+
tab: (value) => muted(` ${value} `),
55+
selectedTab: (value) => theme
56+
? theme.fgBg('userMessageText', 'accent', ` ${value} `)
57+
: chalk.bgWhite.black(` ${value} `),
58+
progressFilled: accent,
59+
progressEmpty: muted,
60+
};
61+
}

src/commands/status.ts

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
* Copyright 2025 Autohand AI LLC
44
* SPDX-License-Identifier: Apache-2.0
55
*/
6-
import chalk from 'chalk';
76
import readline from 'node:readline';
87
import { t } from '../i18n/index.js';
98
import type { SlashCommandContext } from '../core/slashCommandTypes.js';
109
import type { AutohandConfig } from '../types.js';
1110
import { cleanupModalRender, prepareModalRender } from '../ui/ink/components/Modal.js';
11+
import { createCommandTheme } from './commandTheme.js';
1212
import packageJson from '../../package.json' with { type: 'json' };
1313

1414
export const metadata = {
@@ -104,12 +104,13 @@ function renderStatusUI(data: StatusData): Promise<void> {
104104
}
105105

106106
const render = () => {
107+
const theme = createCommandTheme();
107108
// Clear screen and move cursor to top
108109
process.stdout.write('\x1B[2J\x1B[H');
109110

110111
renderTabHeader(tabs, currentTab);
111112
renderTabContent(tabs[currentTab], data);
112-
console.log(chalk.gray('\nEsc to exit'));
113+
console.log(theme.muted('\nEsc to exit'));
113114
};
114115

115116
let buffer = '';
@@ -205,13 +206,14 @@ function renderStatusUI(data: StatusData): Promise<void> {
205206
}
206207

207208
function renderTabHeader(tabs: TabName[], currentIndex: number): void {
209+
const theme = createCommandTheme();
208210
const header = tabs.map((tab, i) => {
209211
return i === currentIndex
210-
? chalk.bgWhite.black(` ${tab} `)
211-
: chalk.gray(` ${tab} `);
212+
? theme.selectedTab(tab)
213+
: theme.tab(tab);
212214
}).join(' ');
213215

214-
console.log(`Settings: ${header} ${chalk.gray('(tab to cycle)')}\n`);
216+
console.log(`Settings: ${header} ${theme.muted('(tab to cycle)')}\n`);
215217
}
216218

217219
function renderTabContent(tab: TabName, data: StatusData): void {
@@ -229,28 +231,30 @@ function renderTabContent(tab: TabName, data: StatusData): void {
229231
}
230232

231233
function renderStatusTab(data: StatusData): void {
232-
console.log(chalk.bold(`${t('commands.status.version')}:`), data.version);
233-
console.log(chalk.bold(`${t('commands.status.sessionId')}:`), data.sessionId ?? chalk.gray('none'));
234-
console.log(chalk.bold(`${t('commands.status.cwd')}:`), data.cwd);
235-
console.log(chalk.bold(`${t('commands.status.provider')}:`), data.provider);
236-
console.log(chalk.bold(`${t('commands.status.model')}:`), data.model);
234+
const theme = createCommandTheme();
235+
console.log(theme.bold(`${t('commands.status.version')}:`), data.version);
236+
console.log(theme.bold(`${t('commands.status.sessionId')}:`), data.sessionId ?? theme.muted('none'));
237+
console.log(theme.bold(`${t('commands.status.cwd')}:`), data.cwd);
238+
console.log(theme.bold(`${t('commands.status.provider')}:`), data.provider);
239+
console.log(theme.bold(`${t('commands.status.model')}:`), data.model);
237240
console.log(
238-
chalk.bold('Context Compaction:'),
239-
data.contextCompactionEnabled ? chalk.green('ON') : chalk.yellow('OFF')
241+
theme.bold('Context Compaction:'),
242+
data.contextCompactionEnabled ? theme.success('ON') : theme.warning('OFF')
240243
);
241244
console.log();
242245
console.log(
243-
chalk.bold(`${t('commands.status.apiStatus')}:`),
244-
data.apiConnected ? chalk.green(t('commands.status.connected')) : chalk.red(t('commands.status.disconnected'))
246+
theme.bold(`${t('commands.status.apiStatus')}:`),
247+
data.apiConnected ? theme.success(t('commands.status.connected')) : theme.error(t('commands.status.disconnected'))
245248
);
246-
console.log(chalk.bold(`${t('commands.status.sessions')}:`), t('commands.status.total', { count: String(data.sessionsCount) }));
247-
console.log(chalk.bold('Memory:'), 'user (~/.autohand/memory/), project (.autohand/memory/)');
249+
console.log(theme.bold(`${t('commands.status.sessions')}:`), t('commands.status.total', { count: String(data.sessionsCount) }));
250+
console.log(theme.bold('Memory:'), 'user (~/.autohand/memory/), project (.autohand/memory/)');
248251
}
249252

250253
function renderConfigTab(data: StatusData): void {
254+
const theme = createCommandTheme();
251255
const config = data.config;
252256

253-
console.log(chalk.bold('Autohand preferences\n'));
257+
console.log(theme.bold('Autohand preferences\n'));
254258

255259
const settings: Array<[string, string]> = [
256260
['Theme', config?.ui?.theme ?? 'dark'],
@@ -264,26 +268,28 @@ function renderConfigTab(data: StatusData): void {
264268
];
265269

266270
for (const [name, value] of settings) {
267-
console.log(` ${chalk.cyan(name.padEnd(30))} ${value}`);
271+
console.log(` ${theme.accent(name.padEnd(30))} ${value}`);
268272
}
269273
}
270274

271275
function renderUsageTab(data: StatusData): void {
276+
const theme = createCommandTheme();
272277
const contextUsed = 100 - data.contextPercentLeft;
273278

274-
console.log(chalk.bold('Current session\n'));
279+
console.log(theme.bold('Current session\n'));
275280

276281
renderProgressBar('Context used', contextUsed, 100);
277282
console.log();
278283

279-
console.log(chalk.bold('Tokens used:'), formatTokens(data.totalTokensUsed));
284+
console.log(theme.bold('Tokens used:'), formatTokens(data.totalTokensUsed));
280285
}
281286

282287
function renderProgressBar(label: string, value: number, max: number): void {
288+
const theme = createCommandTheme();
283289
const width = 30;
284290
const filled = Math.round((value / max) * width);
285291
const empty = width - filled;
286-
const bar = chalk.cyan('\u2588'.repeat(filled)) + chalk.gray('\u2591'.repeat(empty));
292+
const bar = theme.progressFilled('\u2588'.repeat(filled)) + theme.progressEmpty('\u2591'.repeat(empty));
287293
const percent = Math.round((value / max) * 100);
288294

289295
console.log(label);

0 commit comments

Comments
 (0)