ccgui v0.5.9 · Tauri 데스크톱 · CodexMonitor에서 파생)package.json·Cargo.toml·Rust 엔진 소스까지 분석했다.이 레포가 무엇을 하는 물건인가.
Claude Code는 본래 터미널에서 claude를 실행해 텍스트로 대화하는 도구다. 강력하지만, 검은 화면에서 스크롤하며 "AI가 방금 어떤 파일을 고쳤지?", "이번 답에 토큰을 얼마나 썼지?"를 추적하기는 불편하다. ccgui는 이 CLI를 백그라운드에서 대신 실행하고, 그 출력 스트림을 파싱해 파일 편집·셸 명령·읽기 동작을 시각적 카드로 그려 준다.
핵심은 "또 하나의 챗봇"이 아니라는 점이다. ccgui는 여러 CLI 엔진을 추상화한 어댑터 계층(engine)을 두고, Claude Code·Codex·Gemini·OpenCode를 같은 UI로 다룬다. 즉 "AI 코딩 CLI들을 위한 통합 IDE 셸"이다.
ccgui는 데스크톱 AI 코딩 클라이언트다. 프로젝트 폴더를 열고 채팅으로 작업을 지시하면, 내부의 Rust 백엔드가 적절한 CLI(claude -p 등)를 자식 프로세스로 띄우고 stream-json 형식의 출력을 실시간 파싱해 화면에 흘려 보낸다. 파일 트리·내장 터미널·Git 패널·전역 검색은 물론, 프로젝트 맵(지식 그래프)·프로젝트 메모리·컨텍스트 원장(ledger)·사용량 통계·칸반·인텐트 캔버스까지 갖춘 풀 IDE급 데스크톱 앱이다. CodexMonitor라는 작은 프로젝트에서 출발해 멀티엔진 클라이언트로 성장했다.
트렌딩 이유 · Claude Code 단독·웹 챗봇 대비 장점.
AI 코딩 CLI(Claude Code·Codex 등)는 강력하지만 터미널 UX의 한계가 있다. 긴 출력은 스크롤로 사라지고, 어떤 파일이 바뀌었는지·비용이 얼마인지 추적이 어렵고, 여러 세션·여러 엔진을 오가기가 번거롭다. ccgui가 빠르게 스타를 모으는 이유는 이 "터미널에서 진짜 불편한 부분"을 데스크톱 앱으로 정면 돌파했기 때문이다. 첫째 하나의 앱에서 여러 엔진을 전환·혼합, 둘째 모든 동작이 시각적 카드로 투명하게, 셋째 세션·비용·컨텍스트의 영속화, 넷째 프로젝트 맵·메모리 같은 "프로젝트 지능"이다.
| 비교 항목 | Claude Code 단독 (터미널) | 웹 챗봇 (ChatGPT 등) | ccgui |
|---|---|---|---|
| 실행 위치 | 로컬 터미널 | 브라우저(원격) | 로컬 데스크톱 앱(데이터 로컬) |
| 엔진 | Claude 고정 | 해당 서비스 고정 | Claude·Codex·Gemini·OpenCode 전환·혼합 |
| 동작 가시성 | 텍스트 스크롤 | 코드 블록 복붙 | 파일편집·셸·읽기를 실시간 카드로 |
| 세션 | 종료 시 추적 어려움 | 웹 히스토리 | 재시작해도 복원·재개(로컬 SQLite) |
| 비용/토큰 | 직접 계산 | 불투명 | 토큰·비용·캐시율 리포트 + 월 예산 상한 |
| 주변 도구 | 외부 터미널·에디터 | 없음 | 파일트리·터미널·Git·검색·칸반 내장 |
터미널에서 AI 코딩 CLI를 돌리면, AI가 수십 개 파일을 고쳐도 결과가 텍스트 로그로 흘러가 무엇이 바뀌었는지 한눈에 안 보인다. 세션을 닫으면 맥락이 날아가기 쉽고, 비용은 직접 머릿속으로 더해야 한다. 게다가 엔진마다 명령어·옵션·출력 포맷이 달라 여러 도구를 오가려면 그때마다 새로 배워야 한다.
ccgui는 각 CLI를 engine 어댑터(claude.rs·codex_adapter.rs·gemini.rs·opencode.rs)로 감싸 하나의 통일된 인터페이스로 노출한다. CLI를 자식 프로세스로 띄우고 stream-json 출력을 파싱해 프론트엔드 카드 이벤트로 변환한다. 세션·사용량은 로컬 SQLite에 영속화하고, 프로젝트 맵·메모리로 코드베이스 이해까지 더한다. "여러 CLI를 위한 단일 IDE 셸"이 설계 철학이다.
백엔드(Rust/Tauri)·프론트엔드(React)·인프라 각각.
invoke()로 Rust 함수(command)를 호출하고, Rust는 emit()으로 이벤트를 프론트로 흘려 보낸다.src-tauri/)ccgui의 진짜 엔진은 Rust다. CLI 프로세스 관리, 파일·Git·터미널·SQLite 접근, 코드 분석 등 OS와 부딪치는 모든 일을 Rust가 맡고, 프론트엔드는 #[tauri::command]로 노출된 함수를 호출한다(소스 전반에 44개 파일에 걸쳐 명령이 흩어져 있다).
| 요소 | 역할 |
|---|---|
Tauri 2 (protocol-asset, macos-private-api) | 데스크톱 셸·IPC·창 관리·자동 업데이트. |
tokio | 비동기 런타임. 자식 프로세스(process)·파일·네트워크·타이머를 async로. |
portable-pty | 내장 터미널의 핵심. 진짜 의사 터미널(PTY)을 띄워 셸을 구동(terminal.rs). |
git2 (libgit2) | Git 패널 — 스테이징·커밋·브랜치·워크트리·diff·히스토리. |
rusqlite (bundled) | 로컬 SQLite — 세션·노트카드·메모리·사용량을 영속화. |
reqwest (rustls) | HTTP 클라이언트 — 프로바이더 호출·업데이트 체크·스트리밍. |
axum (ws) | 내부 웹 서비스·원격 백엔드 브리지(WebSocket 포함). |
ignore / regex | 코드 인텔리전스(code_intel.rs)·프로젝트 맵 — 파일 워킹·심볼 추출. |
whisper-rs + cpal (비-윈도우) | 음성 받아쓰기(dictation) — 로컬 음성→텍스트. |
lettre / imap / mailparse | 메일 기능(SMTP 송신·IMAP 수신·파싱). |
objc2-* (macOS) / windows-sys (Win) | OS별 네이티브 통합(투명창·메뉴·외형 등). |
src/)UI는 60개 이상의 기능(feature) 폴더로 모듈화돼 있다(git·terminal·project-map·kanban·context-ledger 등). 화면 자체는 웹 기술이지만, @tauri-apps/api의 invoke로 Rust 명령을 호출하고 이벤트를 구독한다.
| 요소 | 역할 |
|---|---|
| React 19 + TypeScript 5.8 | UI 컴포넌트·상태. 타입 정의가 방대(src/types.ts 약 55KB). |
| Tailwind CSS 4 + radix-ui + antd 6 | 스타일·디자인 시스템·컴포넌트(VS Code풍 15+ 테마). |
@xterm/xterm | 브라우저 안의 진짜 터미널 화면(백엔드 PTY와 연결). |
@uiw/react-codemirror + CodeMirror 6 언어팩 | 코드 편집/미리보기(JS·Python·Rust·HTML·YAML 등 다국어 하이라이트). |
@excalidraw/excalidraw | 인텐트 캔버스 — 코드 짜기 전 손그림 기획. |
mermaid · react-markdown + remark/rehype | AI 답변의 다이어그램·마크다운·수식(KaTeX) 렌더링. |
@tanstack/react-virtual | 긴 메시지/세션 목록 가상 스크롤(성능). |
framer-motion · @hello-pangea/dnd | 애니메이션·드래그앤드롭(칸반 카드 등). |
i18next + react-i18next | 영어/중국어 이중 언어 UI. |
dompurify · pdfjs-dist · mammoth · xlsx | HTML 정화·PDF/Word/Excel 첨부 미리보기. |
| 요소 | 역할 |
|---|---|
| Vite 7 | 프론트엔드 번들러/개발 서버. vite build 후 Tauri가 패키징. |
| npm 강제 | preinstall 스크립트로 pnpm/yarn 차단 — 모두 동일한 의존성 버전을 쓰게. |
| 크로스 플랫폼 빌드 | build-platform.mjs — mac(arm64/x64/universal)·win-x64·linux(x64/arm64) 산출. |
doctor / doctor:strict | 빌드 전 환경·런타임 계약·브랜딩 검증(tauri:build가 먼저 호출). |
check:* 게이트 다수 | "런타임 계약"·번들 청킹·라지파일·이벤트 스키마 등 수십 개 자동 검사 스크립트. |
| Vitest + Testing Library + jsdom | 프론트 테스트. Rust 쪽은 cargo test(계약 테스트 다수). |
| 자동 업데이트 | tauri-plugin-updater — 인앱 업데이트. |
Nix (flake.nix) | 재현 가능한 개발 환경(선택). |
check:app-shell:runtime-contract 등). 예: "이 이벤트는 반드시 이 스키마를 따라야 한다", "이 거대 파일은 분할돼야 한다". 사람의 리뷰만 믿지 않고 규약을 코드로 못 박는 대형 프로젝트의 품질 관리 기법이다.웹뷰(React) ↔ IPC ↔ Rust ↔ CLI 자식 프로세스, 그리고 멀티엔진 어댑터.
ccgui의 심장은 "프론트엔드(웹뷰)와 백엔드(Rust)가 IPC로 대화하고, 백엔드가 CLI를 자식 프로세스로 띄워 그 스트림을 다시 프론트로 중계"하는 흐름이다. 전체 구조를 그림으로 보면 다음과 같다.
가장 중요한 추상화는 src-tauri/src/engine/다. 서로 다른 CLI(Claude·Codex·Gemini·OpenCode)는 명령어 인자도, 출력 포맷도 다르다. ccgui는 이를 공통 EngineType 열거형 + 엔진별 어댑터 모듈로 통일한다. 프론트엔드는 "어떤 엔진인지"를 신경 쓰지 않고 같은 명령을 호출하면, EngineManager가 알맞은 어댑터로 라우팅한다.
// src-tauri/src/engine/mod.rs (발췌·요약)
pub enum EngineType {
Claude, // "Claude Code" — 기본값
Codex, // "Codex"
Gemini, // "Gemini"
OpenCode, // "OpenCode"
}
// 각 엔진은 별도 어댑터 파일로:
// claude.rs / codex_adapter.rs / gemini.rs / opencode.rs
// 기본 엔진은 Claude Code (impl Default for EngineType)
이 패턴 덕에 새 엔진 추가 = 새 어댑터 파일 작성이고, UI와 나머지 백엔드는 거의 손대지 않는다. capability_matrix.rs 같은 보조 모듈로 "이 엔진이 어떤 기능을 지원하는가"를 표로 관리해, UI가 엔진에 맞춰 버튼을 켜고 끈다.
Claude 어댑터(claude.rs, 약 87KB)는 CLI를 단순히 "텍스트로 실행"하지 않는다. 입력도 출력도 stream-json으로 묶어, 프롬프트 텍스트를 셸이 해석할 위험을 없애고 출력을 구조화된 이벤트로 받는다.
// src-tauri/src/engine/claude.rs (인자 구성 발췌)
cmd.arg("-p"); // print(비대화) 모드
cmd.arg("--input-format"); cmd.arg("stream-json"); // 입력도 JSON
cmd.arg("--output-format"); cmd.arg("stream-json"); // 출력도 JSON
cmd.arg("--verbose"); // 더 많은 이벤트
// 실제 프롬프트 본문은 인자가 아니라 stdin(stream-json)으로 주입
// → 셸 래퍼가 프롬프트를 절대 파싱하지 않는다(안전)
CLI를 인자로 실행하는 것은 식당에서 주문을 큰 소리로 외치는 것과 같다 — 옆 테이블(셸)이 잘못 알아들을 수 있다. ccgui는 대신 주문서를 손으로 적어 주방에 직접 건네고(stdin), 주방은 요리를 한 접시씩 컨베이어(stream-json)로 내보낸다. 손님은 접시가 나올 때마다 바로바로 받아 본다(실시간 카드).
백엔드는 이 NDJSON 줄들을 파싱해 도메인 이벤트(텍스트 델타·도구 호출·토큰 사용량 등)로 정규화한 뒤 emit으로 프론트에 보낸다. 그래서 화면에 "파일 편집 카드", "셸 명령 카드", "비용 표시"가 실시간으로 뜬다. 시작 시 일정 시간 내 유효한 스트림 이벤트가 안 오면 타임아웃 진단을 띄우는 등, 실패 경로까지 촘촘히 다뤄져 있다.
ccgui는 클라우드 없이 모든 상태를 로컬에 둔다. storage.rs·rusqlite로 세션·노트카드·메모리·사용량을 SQLite에 저장하고, session_management*.rs(여러 파일로 분할)가 세션 카탈로그·폴더·아카이브·되감기(rewind)/포크를 담당한다. 여기에 프로젝트 맵(project_map*.rs — 파일 관계·API 계약·모듈 의존성 그래프)과 프로젝트 메모리(project_memory/), 코드 인텔리전스(code_intel.rs — 심볼·정의 탐색)를 더해 "AI가 이 코드베이스를 안다"는 느낌을 만든다.
"프론트(src) ↔ 백엔드(src-tauri) ↔ 규칙/스펙 문서"의 3층.
주목할 점: 같은 관심사를 여러 파일로 잘게 쪼갠다. 예를 들어 세션 관리는 session_management.rs(108KB) 한 덩어리가 아니라 _types·_catalog_projection·_folder_counts·각종 _tests로 분리돼 있다. check:large-files 게이트가 거대 파일을 감시하기 때문이다. 또 .trellis/·openspec/·AGENTS.md처럼 "AI 에이전트가 이 레포에서 일할 때의 규칙"을 문서로 명시한 점이 특징이다(이 앱 자체가 AI 코딩 도구로 개발됨).
이 레포를 뜯어보며 배울 수 있는 것 + 실습 아이디어.
배울 것: invoke(프론트→Rust)와 emit(Rust→프론트)으로 양방향 통신하는 법, #[tauri::command]로 함수를 노출하는 법, capabilities/로 권한을 좁히는 법, 플랫폼별 설정(tauri.windows.conf.json)과 자동 업데이트.
emit으로 흘려 보내 화면에 실시간 표시해 보기.배울 것: tokio::process로 외부 프로그램을 띄우고, stdout을 줄 단위(NDJSON)로 읽어 구조화 이벤트로 바꾸는 패턴. 인자 대신 stdin으로 데이터를 넣어 셸 인젝션을 피하는 보안 감각. 시작 타임아웃·조기 종료 같은 실패 경로 처리.
claude -p --output-format stream-json --verbose "안녕"을 직접 터미널에서 실행해, 어떤 JSON 이벤트들이 줄줄이 나오는지 관찰하기(ccgui가 무엇을 파싱하는지 체감).배울 것: 공통 인터페이스(EngineType + 매니저)를 두고 도구별 차이를 어댑터로 격리하는 설계. "능력 행렬(capability matrix)"로 기능 차이를 데이터화해 UI를 조건부로 그리는 기법.
배울 것: 클라우드 없이 rusqlite로 세션·메모리·사용량을 저장하고, 앱 재시작 후에도 복원하는 설계. 되감기(rewind)·포크처럼 "대화의 시간여행"을 데이터 모델로 표현하는 법.
배울 것: 기능별 폴더(feature-based) 구조, 거대 파일 분할, 그리고 check:*처럼 규약을 빌드 시 강제하는 스크립트로 코드 품질을 자동 관리하는 문화. 가상 스크롤(react-virtual)로 긴 목록 성능 잡기.
그냥 쓰는 경우와, 직접 빌드하는 경우.
Releases 페이지에서 OS별 설치 파일을 받는다. 설치 후 설정에서 AI 엔진(예: Claude Code API 키나 로컬 CLI)을 구성하고 프로젝트 폴더를 추가하면 끝.
| 플랫폼 | 설치 파일 |
|---|---|
| macOS (Apple Silicon) | aarch64.dmg |
| macOS (Intel) | x64.dmg |
| Windows | .exe / .msi (WebView2 필요 — Win11 기본 탑재) |
| Linux | .AppImage (webkit2gtk 필요) |
| 도구 | 버전 | 용도 |
|---|---|---|
| Node.js | 20 이상 | 프론트엔드 구동(반드시 npm, pnpm/yarn 차단) |
| Rust | stable(rustup) | 백엔드 컴파일 |
| CMake | 최신 | 일부 의존성 빌드 |
# 빌드 3단계
git clone https://github.com/zhukunpenglinyutong/desktop-cc-gui.git
cd desktop-cc-gui
npm install # pnpm/yarn 쓰면 preinstall이 막음
npm run tauri:dev # doctor:strict 검증 후 개발 실행
npm run tauri:build # 배포용 빌드
ccgui는 "껍데기(클라이언트)"다. 실제 AI 코딩은 내부에서 호출하는 Claude Code·Codex 등 CLI/구독·API 키가 있어야 작동하며, 토큰 사용량만큼 과금된다. ccgui의 사용량/예산 기능은 바로 이 비용을 관리하기 위한 것이다. 또 macOS는 Xcode CLT, Windows는 C++ Build Tools 등 OS별 Tauri 사전 요구사항이 있다.
난이도별 — 손으로 만지며 구조를 체득한다.
Claude Code CLI가 설치돼 있다면 터미널에서 claude -p --output-format stream-json --verbose "hello"를 실행한다. 줄줄이 나오는 JSON 이벤트의 종류(텍스트 델타·도구 호출·usage 등)를 적어 보고, "ccgui가 이것을 어떤 카드로 바꿀까"를 추론해 본다.
src-tauri/src/engine/mod.rs에서 EngineType을 찾고, 각 엔진의 어댑터 파일 4개(claude.rs·codex_adapter.rs·gemini.rs·opencode.rs)의 첫머리 주석을 비교한다. "공통점 vs 엔진별 차이"를 한 표로 정리해 본다.
새 Tauri 앱을 만들어, 버튼을 누르면 Rust가 tokio::process로 간단한 명령(예: ping)을 띄우고 stdout을 줄 단위로 emit한다. 프론트는 이벤트를 받아 화면에 흘려 보낸다. ccgui의 핵심 흐름(spawn→stream→emit→render)을 축소판으로 재현하는 것.
가상의 다섯 번째 엔진(예: 로컬 LLM CLI)을 추가한다고 가정하고, 어떤 파일을 새로 만들고 어디(mod.rs·manager.rs·capability_matrix.rs·UI feature)를 고쳐야 하는지 변경 목록을 작성한다. 어댑터 패턴이 변경을 어떻게 국소화하는지 체감한다.
scripts/의 check:large-files나 런타임 계약 스크립트를 읽고, 본인 프로젝트에 "이 디렉토리의 파일은 N줄을 넘으면 안 됨"을 검사하는 Node 스크립트를 작성해 npm run check:*로 붙인다. "규약을 코드로 못 박기"를 실제로 해 본다.
"데스크톱 AI 코딩 클라이언트"를 이해/제작하기 위한 주차별 길.
| 주차 | 주제 | 학습 내용 |
|---|---|---|
| 1주차 | Tauri 기초 | Tauri 2 시작·창·invoke/emit IPC·권한(capability). Electron과의 차이. |
| 2주차 | Rust 비동기 + 프로세스 | tokio 런타임·process로 자식 프로세스 spawn·stdout 스트림 읽기·채널. |
| 3주차 | 스트림 프로토콜 | NDJSON/stream-json 파싱, 이벤트 정규화, 백프레셔·타임아웃·에러 경로. |
| 4주차 | 어댑터/플러그인 설계 | 이종 도구 통일(어댑터 패턴), capability matrix, 매니저로 라우팅. |
| 5주차 | 로컬 영속성 | SQLite(rusqlite) 스키마·마이그레이션·세션/메모리 모델·rewind/fork. |
| 6주차 | 대형 React 프론트 | feature 모듈화·가상 스크롤·CodeMirror/xterm 통합·i18n·테마. |
| 7주차 | 코드 인텔리전스 | 파일 워킹(ignore)·정규식 심볼 추출·의존성 그래프(프로젝트 맵). |
| 8주차 | 릴리스 엔지니어링 | 크로스 플랫폼 빌드·자동 업데이트·doctor/품질 게이트·CI. |
이 문서에서 반복된 용어를 한 번에.
| 키워드 | 뜻 |
|---|---|
| ccgui | 이 앱의 패키지명(desktop-cc-gui). 데스크톱 AI 코딩 클라이언트. |
| Tauri 2 | 웹뷰 UI + Rust 백엔드로 가벼운 데스크톱 앱을 만드는 프레임워크. |
| IPC | 프로세스 간 통신. 여기선 프론트(invoke)↔Rust(emit) 사이의 다리. |
#[tauri::command] | 프론트엔드가 호출할 수 있게 노출한 Rust 함수. |
| stream-json | CLI가 줄 단위 JSON 이벤트로 입출력하는 형식(NDJSON). GUI화의 통로. |
| EngineType | Claude·Codex·Gemini·OpenCode를 통일한 열거형(어댑터 패턴의 중심). |
| 어댑터 패턴 | 이종 도구의 차이를 어댑터로 격리하고 공통 인터페이스로 노출하는 설계. |
| PTY | 의사 터미널. portable-pty로 앱 안에 진짜 셸을 띄움. |
| local-first | 클라우드 없이 모든 데이터를 로컬(SQLite)에 두는 철학. |
| 프로젝트 맵 | AI가 코드베이스를 스캔해 만든 파일/API/모듈 관계의 지식 그래프. |
| 컨텍스트 원장 | 각 답변에 어떤 컨텍스트가 얼마나 들어갔는지 회계 장부처럼 기록. |
| 런타임 계약 | 코드 규약을 빌드 시 스크립트(check:*)로 강제 검증. |
| rewind / fork | 대화를 과거 시점으로 되감거나 그 지점에서 새 분기를 만드는 기능. |
원본과 더 깊이 파기 위한 자료.
src-tauri/src/engine/(어댑터)·terminal.rs(PTY)·storage.rs(SQLite)·project_map*.rs(지식그래프)