콘텐츠로 이동

테마 레퍼런스

이 문서는 현재 코딩 에이전트에서 테마가 작동하는 방식을 설명합니다: 스키마, 로딩, 런타임 동작, 실패 모드를 포함합니다.

테마 시스템이 구동하는 요소:

  • TUI 전반에 사용되는 전경/배경 색상 토큰
  • 마크다운 스타일링 어댑터 (getMarkdownTheme())
  • 선택기/편집기/설정 목록 어댑터 (getSelectListTheme(), getEditorTheme(), getSettingsListTheme())
  • 심볼 프리셋 + 심볼 오버라이드 (unicode, nerd, ascii)
  • 네이티브 하이라이터(@f5-sales-demo/pi-natives)가 사용하는 구문 강조 색상
  • 상태 라인 세그먼트 색상

주요 구현: src/modes/theme/theme.ts.

테마 파일은 theme.ts의 런타임 스키마(ThemeJsonSchema)에 대해 검증되고 src/modes/theme/theme-schema.json에 미러링되는 JSON 객체입니다.

최상위 필드:

  • name (필수)
  • colors (필수; 모든 색상 토큰 필수)
  • vars (선택; 재사용 가능한 색상 변수)
  • export (선택; HTML 내보내기 색상)
  • symbols (선택)
    • preset (선택: unicode | nerd | ascii)
    • overrides (선택: SymbolKey에 대한 키/값 오버라이드)

색상 값이 허용하는 형식:

  • 16진수 문자열 ("#RRGGBB")
  • 256색 인덱스 (0..255)
  • 변수 참조 문자열 (vars를 통해 해석)
  • 빈 문자열 ("") - 터미널 기본값을 의미 (\x1b[39m fg, \x1b[49m bg)

아래의 모든 토큰은 colors에 필수입니다.

accent, border, borderAccent, borderMuted, success, error, warning, muted, dim, text, thinkingText

selectedBg, userMessageBg, customMessageBg, toolPendingBg, toolSuccessBg, toolErrorBg, statusLineBg

userMessageText, customMessageText, customMessageLabel, toolTitle, toolOutput

mdHeading, mdLink, mdLinkUrl, mdCode, mdCodeBlock, mdCodeBlockBorder, mdQuote, mdQuoteBorder, mdHr, mdListBullet

toolDiffAdded, toolDiffRemoved, toolDiffContext, syntaxComment, syntaxKeyword, syntaxFunction, syntaxVariable, syntaxString, syntaxNumber, syntaxType, syntaxOperator, syntaxPunctuation

thinkingOff, thinkingMinimal, thinkingLow, thinkingMedium, thinkingHigh, thinkingXhigh, bashMode, pythonMode

statusLineSep, statusLineModel, statusLinePath, statusLineGitClean, statusLineGitDirty, statusLineContext, statusLineSpend, statusLineStaged, statusLineDirty, statusLineUntracked, statusLineOutput, statusLineCost, statusLineSubagents

HTML 내보내기 테마 헬퍼에 사용됩니다:

  • export.pageBg
  • export.cardBg
  • export.infoBg

생략된 경우, 내보내기 코드는 해석된 테마 색상에서 기본값을 도출합니다.

  • symbols.preset은 테마 레벨의 기본 심볼 세트를 설정합니다.
  • symbols.overrides는 개별 SymbolKey 값을 오버라이드할 수 있습니다.

런타임 우선순위:

  1. 설정의 symbolPreset 오버라이드 (설정된 경우)
  2. 테마 JSON의 symbols.preset
  3. 폴백 "unicode"

유효하지 않은 오버라이드 키는 무시되고 로깅됩니다 (logger.debug).

테마 조회 순서 (loadThemeJson):

  1. 내장 임베디드 테마 (defaults/xcsh-dark.jsondefaults/xcsh-light.jsondefaultThemes로 컴파일됨)
  2. 커스텀 테마 파일: <customThemesDir>/<name>.json

커스텀 테마 디렉토리는 getCustomThemesDir()에서 가져옵니다:

  • 기본값: ~/.xcsh/agent/themes
  • PI_CODING_AGENT_DIR로 오버라이드 가능 ($PI_CODING_AGENT_DIR/themes)

getAvailableThemes()는 내장 + 커스텀 이름을 병합하여 정렬된 결과를 반환하며, 이름 충돌 시 내장 테마가 우선합니다.

커스텀 테마 파일의 경우:

  1. JSON 읽기
  2. JSON 파싱
  3. ThemeJsonSchema에 대해 검증
  4. vars 참조를 재귀적으로 해석
  5. 해석된 값을 터미널 기능 모드에 따라 ANSI로 변환

검증 동작:

  • 필수 색상 토큰 누락: 명시적 그룹화된 오류 메시지
  • 잘못된 토큰 타입/값: JSON 경로가 포함된 검증 오류
  • 알 수 없는 테마 파일: Theme not found: <name>

변수 참조 동작:

  • 중첩 참조 지원
  • 누락된 변수 참조 시 예외 발생
  • 순환 참조 시 예외 발생

색상 모드 감지 (detectColorMode):

  • COLORTERM=truecolor|24bit => truecolor
  • WT_SESSION => truecolor
  • TERMdumb, linux, 또는 빈 값 => 256color
  • 그 외 => truecolor

변환 동작:

  • hex -> Bun.color(..., "ansi-16m" | "ansi-256")
  • numeric -> 38;5 / 48;5 ANSI
  • "" -> 기본 fg/bg 리셋

main.ts는 설정으로 테마를 초기화합니다:

  • symbolPreset
  • colorBlindMode
  • theme.dark
  • theme.light

자동 테마 슬롯 선택은 COLORFGBG 배경 감지를 사용합니다:

  • COLORFGBG에서 배경 인덱스 파싱
  • < 8 => 다크 슬롯 (theme.dark)
  • >= 8 => 라이트 슬롯 (theme.light)
  • 파싱 실패 => 다크 슬롯

설정 스키마의 현재 기본값:

  • theme.dark = "xcsh-dark"
  • theme.light = "xcsh-light"
  • symbolPreset = "unicode"
  • colorBlindMode = false
  • 선택한 테마를 로드
  • 전역 theme 싱글턴 업데이트
  • 선택적으로 워처 시작
  • onThemeChange 콜백 트리거

실패 시:

  • 내장 dark로 폴백
  • { success: false, error } 반환
  • 임시 미리보기 테마를 전역 theme에 적용
  • 자체적으로 영속화된 설정을 변경하지 않음
  • 폴백 대체 없이 성공/오류 반환

설정 UI는 이를 라이브 미리보기에 사용하고, 취소 시 이전 테마를 복원합니다.

워처가 활성화된 경우 (setTheme(..., true) / 대화형 초기화):

  • 커스텀 파일 경로 <customThemesDir>/<currentTheme>.json만 감시
  • 내장 테마는 사실상 감시되지 않음
  • 파일 change: 리로드 시도 (디바운스 적용)
  • 파일 rename/삭제: dark로 폴백, 워처 종료

자동 모드는 SIGWINCH 리스너도 설치하며, 터미널 상태가 변경될 때 다크/라이트 슬롯 매핑을 재평가할 수 있습니다.

colorBlindMode는 런타임에 하나의 토큰만 변경합니다:

  • toolDiffAdded가 HSV 조정됨 (녹색이 파란색 쪽으로 이동)
  • 조정은 해석된 값이 16진수 문자열인 경우에만 적용됨

다른 토큰은 변경되지 않습니다.

테마 관련 설정은 Settings에 의해 전역 설정 YAML에 영속화됩니다:

  • 경로: <agentDir>/config.yml
  • 기본 에이전트 디렉토리: ~/.xcsh/agent
  • 실질적 기본 파일: ~/.xcsh/agent/config.yml

영속화되는 키:

  • theme.dark
  • theme.light
  • symbolPreset
  • colorBlindMode

레거시 마이그레이션 존재: 이전 플랫 형식 theme: "name"은 휘도 감지를 기반으로 중첩된 theme.dark 또는 theme.light로 마이그레이션됩니다.

  1. 커스텀 테마 디렉토리에 파일을 생성합니다. 예: ~/.xcsh/agent/themes/my-theme.json.
  2. name, 선택적 vars, 그리고 모든 필수 colors 토큰을 포함합니다.
  3. 선택적으로 symbolsexport를 포함합니다.
  4. 설정에서 테마를 선택합니다 (Display -> Dark theme 또는 Display -> Light theme) - 원하는 자동 슬롯에 따라 선택합니다.

최소 뼈대. colors의 모든 키는 필수입니다 — 런타임 검증기(additionalProperties: false)는 누락된 키와 알 수 없는 키를 모두 거부합니다. 제공된 레퍼런스 구현은 packages/coding-agent/src/modes/theme/defaults/xcsh-dark.jsonxcsh-light.json을 참조하세요.

상태 라인에는 이슈 #242에 문서화된 두 가지 병렬 색상 시스템이 있습니다:

  • 16진수 텍스트 색상 (statusLinePath, statusLineGitClean, statusLineGitDirty, statusLineStaged, statusLineDirty, statusLineUntracked)은 비-파워라인 렌더링을 구동합니다.
  • 256색 팔레트 인덱스 (statusLine<Segment>Bg / statusLine<Segment>Fg)는 파워라인 세그먼트 채우기를 구동합니다. 이들은 위의 16진수 키와 독립적이므로 — 둘 다 설정해야 합니다.
{
"name": "my-theme",
"vars": {
"accent": "#7aa2f7",
"muted": 244
},
"colors": {
"accent": "accent",
"chromeAccent": "accent",
"spinnerAccent": "accent",
"contentAccent": "muted",
"border": "#4c566a",
"borderAccent": "accent",
"borderMuted": "muted",
"success": "#9ece6a",
"error": "#f7768e",
"warning": "#e0af68",
"muted": "muted",
"dim": 240,
"gutterSuccess": "#7dcfff",
"gutterWarning": "#e0af68",
"text": "",
"thinkingText": "muted",
"selectedBg": "#2a2f45",
"userMessageBg": "#1f2335",
"userMessageText": "",
"customMessageBg": "#24283b",
"customMessageText": "",
"customMessageLabel": "accent",
"toolPendingBg": "#1f2335",
"toolSuccessBg": "#1f2d2a",
"toolErrorBg": "#2d1f2a",
"toolTitle": "",
"toolOutput": "muted",
"mdHeading": "accent",
"mdLink": "accent",
"mdLinkUrl": "muted",
"mdCode": "#c0caf5",
"mdCodeBlock": "#c0caf5",
"mdCodeBlockBorder": "muted",
"mdQuote": "muted",
"mdQuoteBorder": "muted",
"mdHr": "muted",
"mdListBullet": "accent",
"toolDiffAdded": "#9ece6a",
"toolDiffRemoved": "#f7768e",
"toolDiffContext": "muted",
"syntaxComment": "#565f89",
"syntaxKeyword": "#bb9af7",
"syntaxFunction": "#7aa2f7",
"syntaxVariable": "#c0caf5",
"syntaxString": "#9ece6a",
"syntaxNumber": "#ff9e64",
"syntaxType": "#2ac3de",
"syntaxOperator": "#89ddff",
"syntaxPunctuation": "#9aa5ce",
"syntaxControl": "#bb9af7",
"thinkingOff": 240,
"thinkingMinimal": 244,
"thinkingLow": "#7aa2f7",
"thinkingMedium": "#2ac3de",
"thinkingHigh": "#bb9af7",
"thinkingXhigh": "#f7768e",
"bashMode": "#2ac3de",
"pythonMode": "#bb9af7",
"statusLineBg": "#16161e",
"statusLineSep": 240,
"statusLineModel": "#bb9af7",
"statusLinePath": "#7aa2f7",
"statusLineGitClean": "#9ece6a",
"statusLineGitDirty": "#e0af68",
"statusLineContext": "#2ac3de",
"statusLineSpend": "#7dcfff",
"statusLineStaged": "#9ece6a",
"statusLineDirty": "#e0af68",
"statusLineUntracked": "#f7768e",
"statusLineOutput": "#c0caf5",
"statusLineCost": "#ff9e64",
"statusLineSubagents": "#bb9af7",
"statusLineOsIconBg": 7,
"statusLineOsIconFg": 232,
"statusLinePathBg": 4,
"statusLinePathFg": 254,
"statusLineGitCleanBg": 2,
"statusLineGitCleanFg": 0,
"statusLineGitDirtyBg": 3,
"statusLineGitDirtyFg": 0,
"statusLineGitStagedBg": 64,
"statusLineGitStagedFg": 0,
"statusLineGitUntrackedBg": 39,
"statusLineGitUntrackedFg": 0,
"statusLineGitConflictBg": 1,
"statusLineGitConflictFg": 7,
"statusLinePlanModeBg": 236,
"statusLinePlanModeFg": 117,
"statusLineProfileXcshBg": "accent",
"statusLineProfileXcshFg": 231
}
}

다음 워크플로를 사용하세요:

  1. 대화형 모드를 시작합니다 (시작 시 워처 활성화).
  2. 설정을 열고 테마 값을 미리봅니다 (라이브 previewTheme).
  3. 커스텀 테마 파일의 경우, 실행 중에 JSON을 편집하고 저장 시 자동 리로드를 확인합니다.
  4. 주요 표면을 검증합니다:
    • 마크다운 렌더링
    • 도구 블록 (대기/성공/오류)
    • diff 렌더링 (추가/삭제/컨텍스트)
    • 상태 라인 가독성
    • 사고 레벨 테두리 변경
    • bash/python 모드 테두리 색상
  5. 테마가 글리프 너비/외관에 의존하는 경우 두 심볼 프리셋을 모두 검증합니다.
  • 커스텀 테마에는 모든 colors 토큰이 필수입니다.
  • exportsymbols는 선택입니다.
  • 테마 JSON의 $schema는 정보 제공용이며; 런타임 검증은 코드 내 컴파일된 TypeBox 스키마에 의해 강제됩니다.
  • setTheme 실패 시 dark로 폴백합니다; previewTheme 실패 시 현재 테마를 대체하지 않습니다.
  • 파일 워처 리로드 오류 시 성공적인 리로드 또는 폴백 경로가 트리거될 때까지 현재 로드된 테마를 유지합니다.