Files
claude-code/src/commands/tui/index.ts
claude-code-best fdddb6dbe8 feat: 添加工具类命令(teleport、recap、break-cache、env、tui 等)
- /teleport: 从 claude.ai 恢复会话
- /recap: 生成会话摘要
- /break-cache: 提示缓存管理(once/always/off/status)
- /env: 环境信息展示(含密钥脱敏)
- /tui: 无闪烁 TUI 模式管理
- /onboarding: 引导流程
- /perf-issue: 性能问题诊断
- /debug-tool-call: 工具调用调试
- /usage: 用量统计(合并 /cost 和 /stats 别名)

Co-Authored-By: glm-5-turbo <zai-org@claude-code-best.win>
2026-05-09 23:04:31 +08:00

185 lines
6.3 KiB
TypeScript

import { existsSync, mkdirSync, unlinkSync, writeFileSync } from 'node:fs'
import { join } from 'node:path'
import { getIsNonInteractiveSession } from '../../bootstrap/state.js'
import { getClaudeConfigHomeDir } from '../../utils/envUtils.js'
import type { Command, LocalCommandResult } from '../../types/command.js'
/**
* Path to the TUI-mode marker file.
*
* When this file exists, the user has opted in to flicker-free TUI mode
* (alternate screen buffer via CLAUDE_CODE_NO_FLICKER=1). The marker is
* session-independent: it persists across restarts so the user only needs to
* run `/tui on` once.
*
* Shell-profile integration: add the following to ~/.bashrc / ~/.zshrc to
* auto-enable TUI mode when the marker is present:
*
* [ -f "$HOME/.claude/.tui-mode" ] && export CLAUDE_CODE_NO_FLICKER=1
*
* Note: setting CLAUDE_CODE_NO_FLICKER at runtime cannot retroactively enter
* the alternate screen buffer — the Ink render tree is already mounted. The
* change takes effect on the NEXT session start.
*/
export function getTuiMarkerPath(): string {
return join(getClaudeConfigHomeDir(), '.tui-mode')
}
/**
* Returns true when the TUI-mode marker file is present, meaning the user has
* opted in to flicker-free alternate-screen rendering.
*/
export function isTuiModeEnabled(): boolean {
return existsSync(getTuiMarkerPath())
}
const USAGE_TEXT = [
'Usage: /tui [subcommand]',
'',
' (no args) Toggle flicker-free TUI mode (alternate screen buffer)',
' on Enable TUI mode',
' off Disable TUI mode',
' status Show current TUI mode state',
'',
'TUI mode uses the ANSI alternate screen buffer (\\x1b[?1049h) so the',
'Claude Code UI occupies a clean full-screen area with no scroll-back',
'flicker. The setting is stored in ~/.claude/.tui-mode and takes effect',
'on the next session start.',
'',
'Shell-profile integration (auto-enable on every start):',
' [ -f "$HOME/.claude/.tui-mode" ] && export CLAUDE_CODE_NO_FLICKER=1',
'',
'Environment override:',
' CLAUDE_CODE_NO_FLICKER=1 force on (overrides marker)',
' CLAUDE_CODE_NO_FLICKER=0 force off (overrides marker)',
].join('\n')
function enableTui(): LocalCommandResult {
const markerPath = getTuiMarkerPath()
mkdirSync(getClaudeConfigHomeDir(), { recursive: true })
writeFileSync(markerPath, new Date().toISOString(), 'utf8')
return {
type: 'text',
value: [
'## TUI mode enabled',
'',
`Marker written: \`${markerPath}\``,
'',
'Flicker-free alternate-screen rendering will be active on the next',
'session start. Add this to your shell profile to make it permanent:',
'',
' [ -f "$HOME/.claude/.tui-mode" ] && export CLAUDE_CODE_NO_FLICKER=1',
'',
'To disable: `/tui off`',
].join('\n'),
}
}
function disableTui(): LocalCommandResult {
const markerPath = getTuiMarkerPath()
if (!existsSync(markerPath)) {
return {
type: 'text',
value: 'TUI mode was not active.',
}
}
unlinkSync(markerPath)
return {
type: 'text',
value: [
'## TUI mode disabled',
'',
`Marker removed: \`${markerPath}\``,
'',
'Standard (non-alternate-screen) rendering will be used on the next',
'session start.',
'',
'To re-enable: `/tui on`',
].join('\n'),
}
}
export async function callTui(args: string): Promise<LocalCommandResult> {
const sub = args.trim().toLowerCase()
// ── status ──────────────────────────────────────────────────────────
if (sub === 'status') {
const enabled = isTuiModeEnabled()
const markerPath = getTuiMarkerPath()
const envVal = process.env.CLAUDE_CODE_NO_FLICKER
let envLine: string
if (envVal === '1' || envVal === 'true') {
envLine = 'CLAUDE_CODE_NO_FLICKER=1 (forced on via env var)'
} else if (envVal === '0' || envVal === 'false') {
envLine = 'CLAUDE_CODE_NO_FLICKER=0 (forced off via env var)'
} else {
envLine = 'CLAUDE_CODE_NO_FLICKER not set'
}
return {
type: 'text',
value: [
'## TUI Mode Status',
'',
` Marker file: ${enabled ? 'present' : 'absent'} (\`${markerPath}\`)`,
` Mode: ${enabled ? 'enabled' : 'disabled'}`,
` Env var: ${envLine}`,
'',
'Note: changes take effect on the next session start.',
].join('\n'),
}
}
// ── on ───────────────────────────────────────────────────────────────
if (sub === 'on') {
return enableTui()
}
// ── off ──────────────────────────────────────────────────────────────
if (sub === 'off') {
return disableTui()
}
// ── toggle (legacy default) ──────────────────────────────────────────
if (sub === '' || sub === 'toggle') {
return isTuiModeEnabled() ? disableTui() : enableTui()
}
// ── unknown subcommand ───────────────────────────────────────────────
return {
type: 'text',
value: [`Unknown subcommand: "${sub}"`, '', USAGE_TEXT].join('\n'),
}
}
const tuiCommand: Command = {
type: 'local-jsx',
name: 'tui',
description:
'Manage flicker-free TUI mode. Open actions or run: status, on, off, toggle',
isHidden: false,
isEnabled: () => !getIsNonInteractiveSession(),
argumentHint: '[status|on|off|toggle]',
bridgeSafe: true,
getBridgeInvocationError: args =>
args.trim()
? undefined
: 'Use /tui status/on/off/toggle over Remote Control.',
load: () => import('./panel.js'),
}
export const tuiNonInteractive: Command = {
type: 'local',
name: 'tui',
description:
'Toggle flicker-free TUI mode (alternate screen buffer). Subcommands: on, off, status',
isHidden: false,
isEnabled: () => getIsNonInteractiveSession(),
supportsNonInteractive: true,
bridgeSafe: true,
load: async () => ({
call: callTui,
}),
}
export default tuiCommand