claude-dashboard v1.10~v1.13: 테마, 성능 최적화, 그리고 셸 통합까지
claude-dashboard v1.10부터 v1.13까지 6번의 릴리스에 담긴 변경사항을 정리합니다. 테마 시스템, Effort Level 표시, 성능 최적화, 그리고 터미널에서 바로 쓰는 check-ai 커맨드까지.
1. 지난 이야기
이전 글에서 Claude, Codex, Gemini, z.ai 네 가지 AI CLI의 사용량을 한 번에 조회하는 대시보드를 만들었습니다. v1.9 기준으로 핵심 기능은 완성된 상태였습니다.
🤖 Opus │ ██████░░ 80% │ $1.25 │ 5h: 42% │ 7d: 69%
하지만 실제로 매일 사용하다 보니 여러 가지 아쉬운 점이 보이기 시작했습니다:
- 터미널 테마가 Catppuccin인데 상태줄만 파스텔톤이라 어색함
- Opus가 High/Medium/Low effort를 지원하는데, 어떤 모드인지 안 보임
- 긴 세션에서 상태줄 렌더링이 점점 느려짐
check-usage명령어를 쓰려면 Claude Code를 실행해야 함
v1.10부터 v1.13까지, 약 2주간 6번의 릴리스를 거치며 이 문제들을 하나씩 해결했습니다.
2. 테마 시스템 (v1.12.0)
2.1 문제: 하드코딩된 색상
기존에는 ANSI 256 컬러가 코드 곳곳에 하드코딩되어 있었습니다:
// Before: 색상이 여기저기 흩어져 있음
const model = `\x1b[38;5;117m${name}\x1b[0m`; // pastelCyan
const cost = `\x1b[38;5;222m$${amount}\x1b[0m`; // pastelYellow
const danger = `\x1b[38;5;210m${text}\x1b[0m`; // pastelRed
문제점은 명확했습니다:
- 색상을 바꾸려면 모든 위젯 파일을 수정해야 함
- "모델 이름 색상"같은 의미가 코드에서 보이지 않음
- 사용자가 선호하는 터미널 테마와 조화를 이룰 수 없음
2.2 해결: 시맨틱 컬러 역할
색상에 **의미(역할)**를 부여하는 인터페이스를 설계했습니다:
export interface ThemeColors {
// 시맨틱 역할
model: string; // 모델 이름
folder: string; // 디렉토리, 비용
branch: string; // Git 브랜치
safe: string; // 낮은 사용량
warning: string; // 중간 사용량
danger: string; // 높은 사용량
secondary: string; // 보조 정보
accent: string; // 강조 (라벨)
info: string; // 정보 (파란/시안)
// 프로그래스 바
barFilled: string;
barEmpty: string;
}
이제 위젯에서는 getTheme().safe처럼 역할로 색상을 참조합니다:
// After: 의미 기반 색상 참조
render(data: ModelData): string {
return `${getTheme().model}🤖 ${data.name}${RESET}`;
// ↑ "모델 이름 색상"이라는 의미가 명확
}
2.3 5가지 테마
각 테마는 동일한 ThemeColors 인터페이스를 구현합니다:
// Default: 파스텔 톤 (ANSI 256)
default: {
model: '\x1b[38;5;117m', // pastelCyan
safe: '\x1b[38;5;151m', // pastelGreen
warning: '\x1b[38;5;222m', // pastelYellow
danger: '\x1b[38;5;210m', // pastelRed
}
// Catppuccin Mocha: RGB 트루컬러
catppuccin: {
model: '\x1b[38;2;137;180;250m', // #89b4fa blue
safe: '\x1b[38;2;166;227;161m', // #a6e3a1 green
warning: '\x1b[38;2;249;226;175m', // #f9e2af yellow
danger: '\x1b[38;2;243;139;168m', // #f38ba8 red
}
// Dracula: 보라톤 중심
dracula: {
model: '\x1b[38;2;189;147;249m', // #bd93f9 purple
safe: '\x1b[38;2;80;250;123m', // #50fa7b green
warning: '\x1b[38;2;241;250;140m', // #f1fa8c yellow
danger: '\x1b[38;2;255;85;85m', // #ff5555 red
}
// Gruvbox: 따뜻한 어스톤
gruvbox: {
model: '\x1b[38;2;215;153;33m', // #d79921 yellow
safe: '\x1b[38;2;152;151;26m', // #98971a green
warning: '\x1b[38;2;250;189;47m', // #fabd2f bright yellow
danger: '\x1b[38;2;251;73;52m', // #fb4934 red
}
// Minimal: 흑백 모노크롬
minimal: {
model: '\x1b[97m', // bright white
safe: '\x1b[37m', // white
warning: '\x1b[37m', // white
danger: '\x1b[97m', // bright white
}
설정은 claude-dashboard.local.json에서 한 줄이면 됩니다:
{
"theme": "catppuccin"
}
2.4 ANSI 256 vs RGB 트루컬러
테마마다 ANSI 이스케이프 코드 방식이 다릅니다:
| 방식 | 문법 | 색상 수 | 사용 테마 |
|---|---|---|---|
| ANSI 256 | \x1b[38;5;{n}m |
256색 | default, minimal |
| RGB 트루컬러 | \x1b[38;2;{r};{g};{b}m |
1600만색 | catppuccin, dracula, gruvbox |
// ANSI 256: 팔레트 번호로 참조
'\x1b[38;5;117m' // 팔레트 #117 = 파스텔 시안
// RGB 트루컬러: 정확한 색상 지정
'\x1b[38;2;137;180;250m' // R=137 G=180 B=250 = Catppuccin Blue
RGB 트루컬러는 공식 색상 팔레트의 정확한 HEX 값을 그대로 사용할 수 있어서, Catppuccin이나 Dracula 같은 테마의 원본 색상을 왜곡 없이 재현할 수 있습니다.
3. Effort Level과 Fast Mode (v1.11.0)
3.1 모델 정보가 부족했던 기존 위젯
기존 모델 위젯은 이름만 보여줬습니다:
🤖 Opus │ ██████░░ 80% │ $1.25
하지만 Claude Code에서는 /config 명령으로 effort level(high/medium/low)을 설정할 수 있고, Opus는 /fast 모드도 지원합니다. 같은 "Opus"라도 동작이 완전히 다른데, 상태줄에서 이 정보를 알 수 없었습니다.
3.2 settings.json 읽기 + 파일 캐싱
Claude Code는 설정을 ~/.claude/settings.json에 저장합니다. 이 파일을 읽되, 상태줄은 초 단위로 갱신되므로 매번 파일을 읽으면 안 됩니다:
let settingsCache: {
mtime: number;
effortLevel: EffortLevel;
fastMode: boolean;
} | null = null;
async function getModelSettings(): Promise<ModelSettings> {
const settingsPath = join(homedir(), '.claude', 'settings.json');
try {
const fileStat = await stat(settingsPath);
// ← 핵심: mtime이 같으면 파일을 다시 읽지 않음
if (settingsCache && settingsCache.mtime === fileStat.mtimeMs) {
return { effortLevel: settingsCache.effortLevel, fastMode: settingsCache.fastMode };
}
const content = await readFile(settingsPath, 'utf-8');
const settings = JSON.parse(content);
const effortLevel: EffortLevel = isEffortLevel(settings.effortLevel)
? settings.effortLevel
: 'high';
const fastMode = settings.fastMode === true;
settingsCache = { mtime: fileStat.mtimeMs, effortLevel, fastMode };
return { effortLevel, fastMode };
} catch {
settingsCache = null;
}
// 환경변수 fallback
const envEffort = process.env.CLAUDE_CODE_EFFORT_LEVEL;
if (isEffortLevel(envEffort)) {
return { effortLevel: envEffort, fastMode: false };
}
return DEFAULT_SETTINGS; // { effortLevel: 'high', fastMode: false }
}
stat() 호출은 readFile()보다 훨씬 가볍습니다. mtime(수정 시간)만 비교해서 파일이 변경되지 않았으면 캐시를 재사용합니다.
3.3 렌더링 규칙
모든 모델에 effort를 표시하면 의미 없는 정보가 늘어납니다. 규칙을 정했습니다:
render(data: ModelData): string {
const shortName = shortenModelName(data.displayName);
// Opus, Sonnet만 effort 표시 (Haiku는 제외)
const supportsEffort = shortName === 'Opus' || shortName === 'Sonnet';
const effortSuffix = supportsEffort
? `(${data.effortLevel[0].toUpperCase()})` // 'high' → '(H)'
: '';
// Fast mode는 Opus 전용 (↯ = 번개 기호)
const fastIndicator = shortName === 'Opus' && data.fastMode ? ' ↯' : '';
return `${getTheme().model}🤖 ${shortName}${effortSuffix}${fastIndicator}${RESET}`;
}
결과:
🤖 Opus(H) # High effort
🤖 Opus(M) ↯ # Medium effort + Fast mode
🤖 Sonnet(L) # Low effort
🤖 Haiku # Effort 표시 없음 (의미 없으므로)
4. 성능 최적화 (v1.12.0)
4.1 Git 호출 병렬화
projectInfo 위젯은 디렉토리명, Git 브랜치, dirty 상태, ahead/behind 정보를 보여줍니다. 문제는 이 정보를 얻기 위해 Git 명령을 3번 실행해야 한다는 것이었습니다:
// Before: 순차 실행 (~2초)
const branch = await getGitBranch(currentDir); // ~500ms
const dirty = await isGitDirty(currentDir); // ~500ms
const ab = await getAheadBehind(currentDir); // ~500ms
각 호출이 독립적이므로 병렬화할 수 있습니다:
// After: 병렬 실행 (~1초)
const [branch, dirty, ab] = await Promise.all([
getGitBranch(currentDir),
isGitDirty(currentDir),
getAheadBehind(currentDir),
]);
worst-case 기준 ~2초 → ~1초로 절반 단축되었습니다.
4.2 Ahead/Behind 구현
Git의 rev-list --left-right --count를 사용하면 한 번의 호출로 ahead와 behind를 모두 알 수 있습니다:
async function getAheadBehind(
cwd: string
): Promise<{ ahead: number; behind: number } | null> {
try {
// ← @{u} = upstream (추적 중인 원격 브랜치)
const result = await execGit(
['rev-list', '--left-right', '--count', '@{u}...HEAD'], cwd, 500
);
const parts = result.trim().split(/\s+/);
if (parts.length === 2) {
return {
behind: parseInt(parts[0], 10) || 0, // upstream이 앞선 커밋 수
ahead: parseInt(parts[1], 10) || 0, // 로컬이 앞선 커밋 수
};
}
return null;
} catch {
return null; // upstream이 없는 브랜치는 null
}
}
렌더링은 간결하게:
const aheadStr = (data.ahead ?? 0) > 0 ? `↑${data.ahead}` : '';
const behindStr = (data.behind ?? 0) > 0 ? `↓${data.behind}` : '';
결과:
📁 my-project (main ↑3) # 3개 커밋 푸시 안 됨
📁 my-project (main ↑2↓1) # 2개 로컬, 1개 리모트
📁 my-project (main) # 동기화됨
4.3 트랜스크립트 증분 파싱
Claude Code는 대화 내용을 JSONL 파일로 기록합니다. toolActivity, agentStatus, todoProgress 세 위젯이 이 파일을 파싱하는데, 긴 세션에서는 파일이 수 MB까지 커집니다.
기존에는 매번 전체 파일을 파싱했습니다. 바이트 오프셋 기반 증분 파싱으로 바꿨습니다:
let cachedTranscript: {
path: string;
size: number; // ← 마지막 파싱 시점의 파일 크기
data: ParsedTranscript;
} | null = null;
export async function parseTranscript(
transcriptPath: string
): Promise<ParsedTranscript | null> {
try {
const fileStat = await stat(transcriptPath);
const fileSize = fileStat.size;
if (cachedTranscript?.path === transcriptPath
&& cachedTranscript.size <= fileSize) {
// 파일 크기 동일 → 변경 없음, 캐시 반환
if (cachedTranscript.size === fileSize) {
return cachedTranscript.data;
}
// ← 핵심: 새로 추가된 부분만 읽어서 기존 데이터에 병합
const newContent = await readFromOffset(
transcriptPath, cachedTranscript.size, fileSize
);
processEntries(parseJsonlContent(newContent), cachedTranscript.data);
cachedTranscript.size = fileSize;
return cachedTranscript.data;
}
// 첫 파싱 또는 파일이 잘려진 경우 → 전체 파싱
const content = await readFromOffset(transcriptPath, 0, fileSize);
const data: ParsedTranscript = {
entries: [],
toolUses: new Map(),
toolResults: new Set(),
};
processEntries(parseJsonlContent(content), data);
cachedTranscript = { path: transcriptPath, size: fileSize, data };
return data;
} catch {
return null;
}
}
바이트 오프셋으로 읽는 헬퍼:
async function readFromOffset(
filePath: string,
offset: number,
fileSize: number
): Promise<string> {
const bytesToRead = fileSize - offset;
if (bytesToRead <= 0) return '';
const fd = await open(filePath, 'r');
try {
const buffer = Buffer.alloc(bytesToRead);
await fd.read(buffer, 0, bytesToRead, offset);
return buffer.toString('utf-8');
} finally {
await fd.close();
}
}
효과: 1시간 세션(~5MB JSONL)에서 매 갱신 시 전체 파일 대신 마지막 몇 KB만 읽습니다. 체감 차이가 큽니다.
5. Widget Toggle (v1.12.0)
5.1 필요없는 위젯 숨기기
모든 사용자가 모든 위젯을 원하지는 않습니다. Codex를 안 쓰는 사람, Session ID가 필요 없는 사람 등 다양합니다. disabledWidgets 설정으로 특정 위젯을 숨길 수 있게 했습니다:
{
"displayMode": "detailed",
"disabledWidgets": ["codexUsage", "geminiUsage", "sessionId"]
}
구현은 Set 기반 필터링입니다:
export function getLines(config: Config): WidgetId[][] {
const lines = config.displayMode === 'custom' && config.lines
? config.lines
: DISPLAY_PRESETS[config.displayMode as keyof typeof DISPLAY_PRESETS]
|| DISPLAY_PRESETS.compact;
const disabled = config.disabledWidgets;
if (!disabled || disabled.length === 0) {
return lines;
}
const disabledSet = new Set(disabled);
return lines
.map((line) => line.filter((id) => !disabledSet.has(id)))
.filter((line) => line.length > 0); // ← 빈 줄 자동 제거
}
핵심은 마지막 .filter((line) => line.length > 0) 입니다. 예를 들어 detailed 모드의 4번째 줄이 ['codexUsage', 'geminiUsage']인데, 둘 다 비활성화하면 빈 줄이 됩니다. 이 빈 줄을 자동으로 제거해서 레이아웃이 깨지지 않습니다.
6. Session ID 위젯 (v1.10.0)
Claude Code의 각 세션에는 UUID가 부여됩니다. 디버깅이나 로그 추적 시 세션을 식별하는 데 유용합니다:
export const sessionIdWidget: Widget<SessionIdData> = {
id: 'sessionId',
name: 'Session ID (Short)',
getData: async (ctx) => {
const sessionId = ctx.stdin.session_id;
if (!sessionId) return null;
return { sessionId, shortId: sessionId.slice(0, 8) };
},
render(data: SessionIdData): string {
return colorize(`🔑 ${data.shortId}`, getTheme().secondary);
},
};
// Full UUID 버전도 제공
export const sessionIdFullWidget: Widget<SessionIdData> = {
id: 'sessionIdFull',
name: 'Session ID (Full)',
getData: getSessionIdData,
render(data: SessionIdData): string {
return colorize(`🔑 ${data.sessionId}`, getTheme().secondary);
},
};
결과:
🔑 a1b2c3d4 # sessionId (기본: 8자)
🔑 a1b2c3d4-e5f6-... # sessionIdFull (전체 UUID)
기본 프리셋에서는 sessionId(8자)를 사용하고, 전체 UUID가 필요한 경우 sessionIdFull로 교체할 수 있습니다.
7. Setup UX 개선 (v1.12.1~v1.12.3)
7.1 인터랙티브 설정의 진화
초기 /claude-dashboard:setup은 display mode 하나만 물어봤습니다. 테마 시스템과 위젯 토글이 추가되면서 설정 항목이 늘어났고, UX를 3번에 걸쳐 개선했습니다:
v1.12.1: 테마, disabledWidgets 질문 추가
Turn 1: Display mode?
Turn 2: Theme?
Turn 3: Hide widgets?
v1.12.2: 독립 질문 배치 처리
Turn 1: Display mode + Theme + Hide widgets (한 번에)
Turn 2: Custom 모드일 때만 위젯 구성 질문
v1.12.3: 마크다운 프리뷰 추가
Turn 1: Display mode (각 옵션에 프리뷰 포함) + Language + Plan + Theme
Turn 2: Hide widgets?
Turn 3: Custom 모드 위젯 구성 (해당 시)
최종 형태에서는 display mode 선택 시 실제 출력 모습을 미리 볼 수 있습니다:
compact:
🤖 Opus(H) │ ██░░ 80% │ $1.25 │ 5h: 42% │ 7d: 69%
normal:
🤖 Opus(H) │ ██░░ 80% │ $1.25 │ 5h: 42% │ 7d: 69%
📁 project (main ↑3) │ 🔑 abc123 │ ⏱ 45m │ 🔥 5K/m │ ✓ 3/5
detailed:
🤖 Opus(H) │ ██░░ 80% │ $1.25 │ 5h: 42% │ 7d: 69%
📁 project (main ↑3) │ 🔑 abc123 │ ⏱ 45m │ 🔥 5K/m │ ⏳ 2h │ ✓ 3/5
CLAUDE.md: 2 │ ⚙️ 12 done │ 🤖 Agent: 1 │ 📦 85%
🔷 codex │ 5h: 15% │ 💎 gemini │ 0%
7.2 교훈: 질문은 적을수록 좋다
처음에는 새 설정 항목이 추가될 때마다 질문을 늘렸습니다. v1.12.2에서 "독립적인 질문은 한 턴에 배치"하는 원칙을 세웠습니다. Claude Code의 AskUserQuestion 도구는 한 번에 4개까지 질문할 수 있으므로, 서로 의존성이 없는 질문은 묶어서 보냅니다.
# Bad: 4턴
Turn 1: Display mode?
Turn 2: Language?
Turn 3: Plan?
Turn 4: Theme?
# Good: 1턴 (4개 질문을 한 번에)
Turn 1: Display mode? + Language? + Plan? + Theme?
8. 셸 통합: check-ai 커맨드 (v1.13.0)
8.1 문제: Claude Code 바깥에서 사용량 확인
/claude-dashboard:check-usage는 강력하지만, Claude Code 안에서만 쓸 수 있습니다. 터미널에서 빠르게 사용량을 확인하고 싶을 때는 불편합니다.
8.2 setup-alias 커맨드
/claude-dashboard:setup-alias를 실행하면 셸 설정 파일에 check-ai 함수를 추가합니다:
# ~/.zshrc 또는 ~/.bashrc에 추가되는 함수
check-ai() {
node "$(ls -d ~/.claude/plugins/cache/claude-dashboard/claude-dashboard/*/dist/check-usage.js 2>/dev/null | sort -V | tail -1)" "$@"
}
핵심 포인트는 동적 경로 해석입니다. 플러그인이 업데이트되면 버전 디렉토리가 바뀌는데(1.12.3/ → 1.13.0/), 와일드카드 + sort -V로 항상 최신 버전을 찾습니다.
Windows PowerShell도 지원합니다:
function check-ai {
$script = (Get-ChildItem "$env:USERPROFILE\.claude\plugins\cache\claude-dashboard\claude-dashboard\*\dist\check-usage.js" |
Sort-Object { [version]$_.Directory.Parent.Name } |
Select-Object -Last 1).FullName
node $script $args
}
8.3 사용 예시
설정 후 어떤 터미널에서든:
# 컬러 출력
$ check-ai
════════════════════════════════════════
CLI Usage Dashboard
════════════════════════════════════════
[Claude]
5h: 18% (3h49m) | 7d: 60% (1d17h)
[Codex]
5h: 0% (4h59m) | 7d: 3% (4d13h) | Plan: plus
[Gemini]
gemini-2.5-pro 0% (23h59m)
gemini-2.5-flash 0% (23h59m)
...
════════════════════════════════════════
Recommendation: codex (Lowest usage (0% used))
════════════════════════════════════════
# JSON으로 스크립팅
$ check-ai --json | jq '.recommendation'
"codex"
9. 전체 아키텍처 변화
9.1 위젯 렌더링 파이프라인
v1.12.0 이후의 렌더링 파이프라인을 정리하면:
설정 로드 → 테마 초기화 → 라인/위젯 결정 → 비활성 위젯 필터링
↓
출력 ← 줄 결합 ← 위젯 병렬 렌더링 ← 빈 줄 제거
코드로 보면:
// 1. 라인별 위젯을 병렬로 렌더링
async function renderLine(
widgetIds: WidgetId[],
ctx: WidgetContext
): Promise<string> {
const results = await Promise.all(
widgetIds.map((id) => renderWidget(id, ctx))
);
const outputs = results
.filter((r): r is WidgetRenderResult => r !== null && r.output.length > 0)
.map((r) => r.output);
return outputs.join(getSeparator()); // ' │ '
}
// 2. 전체 출력 조합
export async function formatOutput(ctx: WidgetContext): Promise<string> {
const lines = await renderAllLines(ctx);
return lines.join('\n');
}
9.2 Display Presets
각 모드가 어떤 위젯을 포함하는지 한눈에 볼 수 있는 프리셋 정의:
export const DISPLAY_PRESETS: Record<
Exclude<DisplayMode, 'custom'>, WidgetId[][]
> = {
compact: [
['model', 'context', 'cost', 'rateLimit5h', 'rateLimit7d',
'rateLimit7dSonnet', 'zaiUsage'],
],
normal: [
['model', 'context', 'cost', 'rateLimit5h', 'rateLimit7d',
'rateLimit7dSonnet', 'zaiUsage'],
['projectInfo', 'sessionId', 'sessionDuration', 'burnRate',
'todoProgress'],
],
detailed: [
['model', 'context', 'cost', 'rateLimit5h', 'rateLimit7d',
'rateLimit7dSonnet', 'zaiUsage'],
['projectInfo', 'sessionId', 'sessionDuration', 'burnRate',
'depletionTime', 'todoProgress'],
['configCounts', 'toolActivity', 'agentStatus', 'cacheHit'],
['codexUsage', 'geminiUsage'],
],
};
additive 원칙: 각 모드는 이전 모드의 줄을 포함하고 새 줄을 추가합니다. compact(1줄) → normal(2줄) → detailed(4줄). 위젯은 항상 같은 위치에 있어서, 모드를 바꿔도 시선이 혼란스럽지 않습니다.
10. 핵심 개념 정리
| 개념 | 적용 | 효과 |
|---|---|---|
| 시맨틱 컬러 역할 | ThemeColors 인터페이스 | 색상 변경이 테마 파일 하나로 완결 |
| 파일 stat 캐싱 | settings.json mtime 비교 | 불필요한 파일 I/O 제거 |
| Promise.all 병렬화 | Git 호출, 위젯 렌더링 | 레이턴시 50% 감소 |
| 바이트 오프셋 증분 파싱 | JSONL 트랜스크립트 | 긴 세션에서도 일정한 파싱 속도 |
| Set 기반 필터링 | disabledWidgets | O(1) 조회로 위젯 토글 |
| Additive 프리셋 | compact → normal → detailed | 모드 전환 시 일관된 레이아웃 |
| 동적 경로 해석 | check-ai alias의 와일드카드 | 플러그인 업데이트에 자동 대응 |
11. 버전별 변경 요약
| 버전 | 주요 변경 | 카테고리 |
|---|---|---|
| v1.10.0 | Session ID 위젯 | 기능 |
| v1.10.1 | 문서/README 정리 | 문서 |
| v1.11.0 | Effort Level (H/M/L) + Fast Mode (↯) | 기능 |
| v1.12.0 | 테마 시스템 (5종), Widget Toggle, Git ↑↓, 증분 파싱, 성능 2x | 기능/성능 |
| v1.12.1~3 | Setup 인터랙티브 UX 개선 (배치 질문, 프리뷰) | UX |
| v1.13.0 | setup-alias (check-ai 셸 커맨드) |
기능 |
12. FAQ
Q: 테마를 커스텀으로 만들 수 있나요?
A: 현재는 5가지 내장 테마만 지원합니다. ThemeColors 인터페이스를 구현하면 새 테마를 추가할 수 있지만, 사용자 정의 테마 설정 파일은 아직 지원하지 않습니다. 향후 추가를 검토하고 있습니다.
Q: Fast Mode (↯)는 무엇인가요?
A: Claude Code에서 /fast를 활성화하면 같은 Opus 모델에서 더 빠른 출력을 제공합니다. 상태줄에서 ↯ 기호로 이 모드가 활성화되어 있는지 한눈에 확인할 수 있습니다.
Q: check-ai 별칭이 플러그인 업데이트 후에도 작동하나요?
A: 네. check-ai 함수는 와일드카드와 sort -V를 사용해 항상 최신 플러그인 버전의 스크립트를 찾습니다. 업데이트 후 별도 설정이 필요 없습니다.
Q: 증분 파싱은 어떤 경우에 전체 파싱으로 돌아가나요?
A: 두 가지 경우입니다. (1) 처음 파싱할 때, (2) 파일이 잘려서 기존 캐시 크기보다 작아졌을 때. 일반적인 사용에서 (2)는 거의 발생하지 않습니다.
Q: 성능 개선 효과를 체감할 수 있나요?
A: Git 병렬화는 모든 사용자에게 체감됩니다 (~2초 → ~1초). 증분 파싱은 30분 이상 긴 세션에서 효과가 두드러집니다. 짧은 세션에서는 차이를 느끼기 어렵습니다.
13. 참고 자료
- ANSI Escape Codes - Wikipedia
- Catppuccin Color Palette
- Dracula Theme
- Gruvbox Color Scheme
- esbuild Documentation
14. 다음 단계
6번의 릴리스를 통해 보이는 것(테마, Effort Level), 보이지 않는 것(성능, 증분 파싱), 밖에서 쓰는 것(check-ai)을 모두 개선했습니다. 다음은 세션 목록 조회 기능을 작업할 예정입니다.
시리즈 목차:
- Claude Dashboard 플러그인 개발기
- Claude Code, Codex, Gemini 통합 사용량 대시보드
- v1.10~v1.13: 테마, 성능 최적화, 셸 통합 ← 현재 글
- 세션 목록과 히스토리 관리 (예정)