Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tips-banner-toggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Add a TUI preference to disable the startup tips banner fetch.
5 changes: 4 additions & 1 deletion apps/kimi-code/src/tui/commands/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ async function applyEditorChoice(host: SlashCommandHost, value: string): Promise
try {
await saveTuiConfig({
theme: host.state.appState.theme,
showTipsBanner: host.state.appState.showTipsBanner,
editorCommand,
notifications: host.state.appState.notifications,
upgrade: host.state.appState.upgrade,
Expand Down Expand Up @@ -424,6 +425,7 @@ async function applyThemeChoice(host: SlashCommandHost, theme: ThemeName): Promi
try {
await saveTuiConfig({
theme,
showTipsBanner: host.state.appState.showTipsBanner,
editorCommand: host.state.appState.editorCommand,
notifications: host.state.appState.notifications,
upgrade: host.state.appState.upgrade,
Expand Down Expand Up @@ -546,7 +548,7 @@ type UpdatePreferenceHost = {
readonly state: {
readonly appState: Pick<
SlashCommandHost['state']['appState'],
'theme' | 'editorCommand' | 'notifications' | 'upgrade'
'theme' | 'showTipsBanner' | 'editorCommand' | 'notifications' | 'upgrade'
>;
};
setAppState(patch: Pick<SlashCommandHost['state']['appState'], 'upgrade'>): void;
Expand All @@ -567,6 +569,7 @@ export async function applyUpdatePreferenceChoice(
try {
await saveTuiConfig({
theme: host.state.appState.theme,
showTipsBanner: host.state.appState.showTipsBanner,
editorCommand: host.state.appState.editorCommand,
notifications: host.state.appState.notifications,
upgrade,
Expand Down
1 change: 1 addition & 0 deletions apps/kimi-code/src/tui/commands/reload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export async function applyReloadedTuiConfig(
await host.applyTheme(config.theme, resolved);
host.refreshTerminalThemeTracking();
host.setAppState({
showTipsBanner: config.showTipsBanner,
editorCommand: config.editorCommand,
notifications: config.notifications,
upgrade: config.upgrade,
Expand Down
5 changes: 5 additions & 0 deletions apps/kimi-code/src/tui/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const UpgradePreferencesSchema = z.object({

export const TuiConfigFileSchema = z.object({
theme: TuiThemeSchema.optional(),
show_tips_banner: z.boolean().optional(),
editor: z
.object({
command: z.string().optional(),
Expand All @@ -52,6 +53,7 @@ export const TuiConfigFileSchema = z.object({

export const TuiConfigSchema = z.object({
theme: TuiThemeSchema,
showTipsBanner: z.boolean(),
editorCommand: z.string().nullable(),
notifications: NotificationsConfigSchema,
upgrade: UpgradePreferencesSchema,
Expand All @@ -73,6 +75,7 @@ export const DEFAULT_UPGRADE_PREFERENCES: UpgradePreferences = {

export const DEFAULT_TUI_CONFIG: TuiConfig = TuiConfigSchema.parse({
theme: 'auto',
showTipsBanner: true,
editorCommand: null,
notifications: DEFAULT_NOTIFICATIONS_CONFIG,
upgrade: DEFAULT_UPGRADE_PREFERENCES,
Expand Down Expand Up @@ -132,6 +135,7 @@ export function normalizeTuiConfig(config: TuiConfigFileShape): TuiConfig {
const command = config.editor?.command?.trim();
return TuiConfigSchema.parse({
theme: config.theme ?? DEFAULT_TUI_CONFIG.theme,
showTipsBanner: config.show_tips_banner ?? DEFAULT_TUI_CONFIG.showTipsBanner,
editorCommand: command === undefined || command.length === 0 ? null : command,
notifications: {
enabled: config.notifications?.enabled ?? DEFAULT_NOTIFICATIONS_CONFIG.enabled,
Expand All @@ -150,6 +154,7 @@ export function renderTuiConfig(config: TuiConfig): string {
# Agent/runtime settings stay in ~/.kimi-code/config.toml.

theme = "${escapeTomlBasicString(config.theme)}" # "auto" | "dark" | "light" | custom theme name
show_tips_banner = ${String(config.showTipsBanner)} # true | false

[editor]
command = "${escapeTomlBasicString(config.editorCommand ?? '')}" # Empty uses $VISUAL / $EDITOR
Expand Down
7 changes: 6 additions & 1 deletion apps/kimi-code/src/tui/kimi-tui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ function createInitialAppState(input: KimiTUIStartupInput): AppState {
streamingPhase: 'idle',
streamingStartTime: 0,
theme: input.tuiConfig.theme,
showTipsBanner: input.tuiConfig.showTipsBanner,
version: input.version,
editorCommand: input.tuiConfig.editorCommand,
notifications: input.tuiConfig.notifications,
Expand Down Expand Up @@ -466,7 +467,11 @@ export class KimiTUI {
// Mount only after init() succeeds; see mountFooter().
this.mountFooter();
this.renderWelcome();
void this.loadBanner();
if (this.state.appState.showTipsBanner) {
void this.loadBanner();
} else {
this.state.appState.banner = null;
}
this.setupAutocomplete();
void this.loadPersistedInputHistory();
this.state.editorContainer.clear();
Expand Down
1 change: 1 addition & 0 deletions apps/kimi-code/src/tui/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export interface AppState {
streamingPhase: 'idle' | 'waiting' | 'thinking' | 'composing';
streamingStartTime: number;
theme: ThemeName;
showTipsBanner: boolean;
version: string;
editorCommand: string | null;
notifications: NotificationsConfig;
Expand Down
26 changes: 26 additions & 0 deletions apps/kimi-code/test/cli/run-shell.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ type CreateKimiDeviceId = typeof createKimiDeviceIdFn;
const mocks = vi.hoisted(() => {
type TuiConfigFallback = {
theme: 'dark' | 'light' | 'auto';
showTipsBanner: boolean;
editorCommand: string | null;
notifications: { enabled: boolean; condition: 'unfocused' | 'always' };
upgrade: { autoInstall: boolean };
};

class TuiConfigParseError extends Error {
Expand Down Expand Up @@ -164,8 +166,10 @@ describe('runShell', () => {
it('constructs KimiHarness and KimiTUI with startup input', async () => {
mocks.loadTuiConfig.mockResolvedValue({
theme: 'dark',
showTipsBanner: true,
editorCommand: null,
notifications: { enabled: true, condition: 'unfocused' },
upgrade: { autoInstall: true },
});
mocks.tuiStart.mockResolvedValue(undefined);
mocks.tuiGetStartupMcpMs.mockResolvedValue(47);
Expand Down Expand Up @@ -222,6 +226,7 @@ describe('runShell', () => {
cliOptions,
tuiConfig: {
theme: 'dark',
showTipsBanner: true,
editorCommand: null,
notifications: { enabled: true, condition: 'unfocused' },
},
Expand All @@ -241,8 +246,10 @@ describe('runShell', () => {
it('tracks first launch when device id creation reports first launch', async () => {
mocks.loadTuiConfig.mockResolvedValue({
theme: 'dark',
showTipsBanner: true,
editorCommand: null,
notifications: { enabled: true, condition: 'unfocused' },
upgrade: { autoInstall: true },
});
mocks.tuiStart.mockResolvedValue(undefined);
mocks.createKimiDeviceId.mockImplementationOnce((homeDir, options) => {
Expand Down Expand Up @@ -276,8 +283,10 @@ describe('runShell', () => {
it('registers first launch before harness construction can create the device id', async () => {
mocks.loadTuiConfig.mockResolvedValue({
theme: 'dark',
showTipsBanner: true,
editorCommand: null,
notifications: { enabled: true, condition: 'unfocused' },
upgrade: { autoInstall: true },
});
mocks.tuiStart.mockResolvedValue(undefined);
mocks.harnessCreatesDeviceIdOnConstruction = true;
Expand Down Expand Up @@ -323,8 +332,10 @@ describe('runShell', () => {
it('binds startup_perf to the session captured before MCP metrics resolve', async () => {
mocks.loadTuiConfig.mockResolvedValue({
theme: 'dark',
showTipsBanner: true,
editorCommand: null,
notifications: { enabled: true, condition: 'unfocused' },
upgrade: { autoInstall: true },
});
mocks.tuiStart.mockResolvedValue(undefined);
let currentSessionId = 'ses-startup';
Expand Down Expand Up @@ -362,8 +373,10 @@ describe('runShell', () => {
it('bridges OAuth refresh outcomes to telemetry', async () => {
mocks.loadTuiConfig.mockResolvedValue({
theme: 'dark',
showTipsBanner: true,
editorCommand: null,
notifications: { enabled: true, condition: 'unfocused' },
upgrade: { autoInstall: true },
});
mocks.tuiStart.mockResolvedValue(undefined);

Expand Down Expand Up @@ -411,8 +424,10 @@ describe('runShell', () => {
mocks.loadTuiConfig.mockRejectedValue(
new mocks.TuiConfigParseError({
theme: 'auto',
showTipsBanner: true,
editorCommand: 'vim',
notifications: { enabled: true, condition: 'always' },
upgrade: { autoInstall: true },
}),
);
mocks.detectTerminalTheme.mockResolvedValue('light');
Expand All @@ -439,6 +454,7 @@ describe('runShell', () => {
startupNotice: 'Invalid TUI config in ~/.kimi-code/tui.toml; using defaults.',
tuiConfig: {
theme: 'auto',
showTipsBanner: true,
editorCommand: 'vim',
notifications: { enabled: true, condition: 'always' },
},
Expand All @@ -448,8 +464,10 @@ describe('runShell', () => {
it('forwards config.toml diagnostics as startup notices', async () => {
mocks.loadTuiConfig.mockResolvedValue({
theme: 'dark',
showTipsBanner: true,
editorCommand: null,
notifications: { enabled: true, condition: 'unfocused' },
upgrade: { autoInstall: true },
});
mocks.harnessGetConfigDiagnostics.mockResolvedValue({
warnings: ['Ignored invalid config in config.toml: loop_control.'],
Expand Down Expand Up @@ -480,8 +498,10 @@ describe('runShell', () => {
it('closes the harness when TUI startup fails', async () => {
mocks.loadTuiConfig.mockResolvedValue({
theme: 'dark',
showTipsBanner: true,
editorCommand: null,
notifications: { enabled: true, condition: 'unfocused' },
upgrade: { autoInstall: true },
});
mocks.tuiStart.mockRejectedValue(new Error('boom'));

Expand Down Expand Up @@ -511,8 +531,10 @@ describe('runShell', () => {
it('tracks exit and prints resume instructions from the TUI exit handler', async () => {
mocks.loadTuiConfig.mockResolvedValue({
theme: 'dark',
showTipsBanner: true,
editorCommand: null,
notifications: { enabled: true, condition: 'unfocused' },
upgrade: { autoInstall: true },
});
mocks.tuiStart.mockResolvedValue(undefined);
mocks.tuiGetCurrentSessionId.mockReturnValue('ses-1');
Expand Down Expand Up @@ -565,8 +587,10 @@ describe('runShell', () => {
it('prints the opened web URL from the TUI exit handler when set', async () => {
mocks.loadTuiConfig.mockResolvedValue({
theme: 'dark',
showTipsBanner: true,
editorCommand: null,
notifications: { enabled: true, condition: 'unfocused' },
upgrade: { autoInstall: true },
});
mocks.tuiStart.mockResolvedValue(undefined);
mocks.tuiGetCurrentSessionId.mockReturnValue('ses-1');
Expand Down Expand Up @@ -612,8 +636,10 @@ describe('runShell', () => {
it('surfaces an invalid target config as an error for kimi migrate, not silently', async () => {
mocks.loadTuiConfig.mockResolvedValue({
theme: 'dark',
showTipsBanner: true,
editorCommand: null,
notifications: { enabled: true, condition: 'unfocused' },
upgrade: { autoInstall: true },
});
mocks.detectPendingMigration.mockResolvedValue({ totalSessions: 1 });
mocks.harnessGetConfig.mockRejectedValue(
Expand Down
1 change: 1 addition & 0 deletions apps/kimi-code/test/cli/update/preflight.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ function installState(overrides: Partial<UpdateInstallState> = {}): UpdateInstal
function tuiConfig(overrides: Partial<TuiConfig> = {}): TuiConfig {
return {
theme: 'auto',
showTipsBanner: true,
editorCommand: null,
notifications: { enabled: true, condition: 'unfocused' },
upgrade: { autoInstall: true },
Expand Down
1 change: 1 addition & 0 deletions apps/kimi-code/test/tui/activity-pane.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ function makeStartupInput(): KimiTUIStartupInput {
},
tuiConfig: {
theme: 'dark',
showTipsBanner: true,
editorCommand: null,
notifications: { enabled: true, condition: 'unfocused' },
upgrade: { autoInstall: true },
Expand Down
4 changes: 4 additions & 0 deletions apps/kimi-code/test/tui/commands/reload.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ describe('reload slash commands', () => {
it('reloads tui.toml without touching Core session state', async () => {
await writeTuiConfig(`
theme = "light"
show_tips_banner = false

[editor]
command = "vim"
Expand All @@ -55,6 +56,7 @@ auto_install = false
expect(session.reloadSession).not.toHaveBeenCalled();
expect(host.state.appState).toMatchObject({
theme: 'light',
showTipsBanner: false,
editorCommand: 'vim',
notifications: { enabled: false, condition: 'always' },
upgrade: { autoInstall: false },
Expand Down Expand Up @@ -82,6 +84,7 @@ auto_install = false
expect(host.refreshSlashCommandAutocomplete).toHaveBeenCalledOnce();
expect(isExperimentalFlagEnabled('micro_compaction')).toBe(true);
expect(host.state.appState.theme).toBe('light');
expect(host.state.appState.showTipsBanner).toBe(true);
expect(host.state.appState.availableModels).toEqual({
fresh: { provider: 'test', model: 'fresh-model', maxContextSize: 1000 },
});
Expand Down Expand Up @@ -129,6 +132,7 @@ function makeHost({
const state = {
appState: {
theme: 'dark',
showTipsBanner: true,
editorCommand: null,
notifications: { enabled: true, condition: 'unfocused' },
upgrade: { autoInstall: true },
Expand Down
2 changes: 2 additions & 0 deletions apps/kimi-code/test/tui/commands/update-preferences.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ describe('update preference commands', () => {
state: {
appState: {
theme: 'auto' as const,
showTipsBanner: false,
editorCommand: null,
notifications: { enabled: true, condition: 'unfocused' as const },
upgrade: { autoInstall: true },
Expand All @@ -41,6 +42,7 @@ describe('update preference commands', () => {

expect(mocks.saveTuiConfig).toHaveBeenCalledWith({
theme: 'auto',
showTipsBanner: false,
editorCommand: null,
notifications: { enabled: true, condition: 'unfocused' },
upgrade: { autoInstall: false },
Expand Down
1 change: 1 addition & 0 deletions apps/kimi-code/test/tui/components/chrome/footer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const appState: AppState = {
planMode: false,
swarmMode: false,
theme: 'dark',
showTipsBanner: true,
editorCommand: null,
notifications: { enabled: true, condition: 'unfocused' },
upgrade: { autoInstall: true },
Expand Down
1 change: 1 addition & 0 deletions apps/kimi-code/test/tui/components/chrome/welcome.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const appState: AppState = {
planMode: false,
swarmMode: false,
theme: 'dark',
showTipsBanner: true,
editorCommand: null,
notifications: { enabled: true, condition: 'unfocused' },
upgrade: { autoInstall: true },
Expand Down
7 changes: 7 additions & 0 deletions apps/kimi-code/test/tui/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ describe('TUI config', () => {
const text = readFileSync(filePath, 'utf-8');
expect(text).toContain('Client preferences for kimi-code.');
expect(text).toContain('theme = "auto"');
expect(text).toContain('show_tips_banner = true');
expect(text).toContain('command = ""');
expect(text).toContain('[upgrade]');
expect(text).toContain('auto_install = true');
Expand All @@ -45,6 +46,7 @@ describe('TUI config', () => {
it('parses valid TOML', () => {
const config = parseTuiConfig(`
theme = "light"
show_tips_banner = false

[editor]
command = "code --wait"
Expand All @@ -59,6 +61,7 @@ auto_install = false

expect(config).toEqual({
theme: 'light',
showTipsBanner: false,
editorCommand: 'code --wait',
notifications: { enabled: false, condition: 'always' },
upgrade: { autoInstall: false },
Expand All @@ -73,6 +76,7 @@ command = " "

expect(config).toEqual({
theme: 'auto',
showTipsBanner: true,
editorCommand: null,
notifications: { enabled: true, condition: 'unfocused' },
upgrade: { autoInstall: true },
Expand Down Expand Up @@ -104,6 +108,7 @@ command = " "
await saveTuiConfig(
{
theme: 'light',
showTipsBanner: false,
editorCommand: 'vim',
notifications: { enabled: false, condition: 'always' },
upgrade: { autoInstall: false },
Expand All @@ -113,6 +118,7 @@ command = " "

expect(await loadTuiConfig(filePath)).toEqual({
theme: 'light',
showTipsBanner: false,
editorCommand: 'vim',
notifications: { enabled: false, condition: 'always' },
upgrade: { autoInstall: false },
Expand All @@ -124,6 +130,7 @@ command = " "
await saveTuiConfig(
{
theme,
showTipsBanner: true,
editorCommand: null,
notifications: DEFAULT_TUI_CONFIG.notifications,
upgrade: DEFAULT_TUI_CONFIG.upgrade,
Expand Down
Loading