히스토리

  • [2026-03-19 Thu 09:10] @pi-claude — ccow → proxycli 리네이밍, 전체 타임라인 정리
  • [2026-03-19 Thu 11:49] @junghan — 네임스페이스 ccow→proxycli 리팩터, 리포 생성/푸시
  • [2026-03-18 Wed 14:24] 생성 — claude-code-openai-wrapper를 Clojure로 재작성

proxycli: Python 4,828줄 → Clojure 441줄

프로젝트 배경

claude-code-openai-wrapper (Python, 4,828줄)는 Claude Code CLI를 OpenAI API로 래핑하는 프로젝트. Doom Emacs + gptel에서 Claude Code의 도구(Read, Write, Edit, Bash)를 OpenAI API처럼 호출할 수 있게 해준다.

원 저자가 더 이상 관리하지 않아 포크 후 gptel 호환 작업을 진행하던 중, 핵심 기능이 441줄 Clojure로 대체 가능함을 발견하여 proxycli 로 새 리포를 만들었다.

왜 proxycli가 필요한가

Emacs에서 AI를 호출하는 방법은 여럿이지만, 도구가 필요할 때 쓸 수 있는 건 제한적이다:

방법도구 사용파일 접근비고
OpenRouter APIAPI 과금, 멀티 모델
CLIProxyAPI (채팅 전용)빠름, 도구 없음
Claude Code TUI터미널 전환 필요
proxycligptel에서 바로 호출

맨날 TUI를 열 수는 없다 — 어디서든 그냥 호출하고 싶을 때가 있다. proxycli는 gptel에서 도구 + 스킬 을 바로 사용 가능하게 한다.

왜 Clojure로 재작성했나

  1. Python 환경 관리 부담 — poetry, venv, SDK 업데이트 추적
  2. 과도한 코드 — gptel이 쓰는 건 2개 엔드포인트뿐인데 4,828줄
  3. 배포 복잡성 — Python 런타임 필요 vs GraalVM 단일 바이너리
  4. 언어 선호 — Clojure + GraalVM native-image 검증된 경험 (dictcli, geworfen)

핵심 발견: SDK가 하는 일의 본질

Python SDK(claude-agent-sdk)가 내부적으로 하는 일:

claude --output-format stream-json --verbose \
  --max-turns 10 \
  --allowedTools Read,Write,Edit,Bash,Grep,Glob,WebSearch,WebFetch \
  --permission-mode bypassPermissions \
  --print "사용자 프롬프트"

서브프로세스 하나 실행하고 stdout의 JSON 스트림을 line-by-line 파싱. 이게 전부. SDK 없이 직접 호출하면 된다.

코드 규모 비교

항목PythonClojure
소스 파일12개3개
소스 줄 수4,828441
테스트6,879줄~100줄
외부 의존성10개+3개
배포 형태Python env39MB 단일 바이너리
기동 시간~3초72ms (native)

비율: 9.1% (90.9% 감소)

제거된 불필요 코드

  • mcp_client.py (367줄) — MCP 서버 관리 (gptel 안 씀)
  • tool_manager.py (404줄) — 도구 설정 API (gptel 안 씀)
  • session_manager.py (214줄) — 세션 관리 (gptel은 stateless)
  • parameter_validator.py (263줄) — 파라미터 호환성 리포트
  • auth.py (286줄) — 다중 인증 방식 (CLI 인증만 필요)
  • rate_limiter.py (95줄) — API 제한 (로컬용이라 불필요)
  • HTML 랜딩페이지 (593줄) — 브라우저 UI (gptel 안 씀)
  • Anthropic Messages API (/v1/messages) — gptel 안 씀

proxycli 구조

proxycli/
├── src/proxycli/
│   ├── core.clj      # 진입점 (35줄)
│   ├── claude.clj    # CLI 실행 + stream-json 파싱 (181줄)
│   └── server.clj    # Ring HTTP + SSE 스트리밍 (225줄)
├── deps.edn          # 의존성 3개
├── build.clj         # uberjar 설정
├── flake.nix         # NixOS + GraalVM
└── run.sh            # 통합 CLI

작업 타임라인 (2026-03-18)

시간작업
13:47claude-agent-sdk 0.1.36→0.1.48 업그레이드
14:00Python 코드베이스 분석: gptel이 쓰는 건 2개 엔드포인트뿐
14:10Clojure 재작성 시작 (clj-wrapper/ 서브디렉토리)
14:20첫 테스트 통과 — 8 tests, 25 assertions
14:24커밋 & 푸시: 375줄로 Python 4,828줄 대체
16:42gptel 실전 테스트 — 서버 띄우고 Emacs에서 직접 호출
16:50stdin 닫기 버그 수정 — CLI가 —print 모드로 동작하도록
16:56Independent mode + 도구 실행 + CWD 지원 검증
17:12KST 시간 주입 + 스킬 로딩 (MCP만 차단)
17:20선택적 스킬 주입 (PROXYCLI_SKILLS 환경변수)
17:25GraalVM native-image 빌드: 39MB, 72ms 기동
17:28native binary 타입 힌트 수정 (OutputStreamWriter)
17:34native binary SSE 스트리밍 검증 완료
17:57새 리포 junghan0611/proxycli 생성
(야간)네임스페이스 ccow→proxycli 리팩터, 첫 커밋

토큰 효율성

모드시스템 프롬프트 토큰
클린 (스킬 없음)13,546
스킬 3개 주입~16,000
Python 기본 (전부 로드)20,417

클린 모드로 Opus 호출도 부담 없는 컨텍스트.

Clojure 커뮤니티 기여 가능성

이 작업은 Clojure 커뮤니티에 유용한 레퍼런스:

  1. ProcessBuilder + stream-json 파싱 패턴 — 외부 CLI 래핑
  2. Ring SSE 스트리밍 — PipedOutputStream으로 비동기 SSE
  3. GraalVM native-image + NixOS FHS — 크로스플랫폼 바이너리 빌드
  4. Python→Clojure 마이그레이션 사례 — 91% 코드 감소 실증
  5. 타입 힌트 — GraalVM native에서 ^java.io.Writer 필수

비전: 범용 CLI 프록시

현재는 Claude Code CLI 전용이지만, 핵심 구조는 범용이다. Claude 전용 부분은 build-command 함수 20줄뿐. 나머지 420줄은 “CLI → OpenAI API” 범용 패턴.

향후 EDN 설정 파일로 다양한 CLI를 프록시할 수 있는 구조로 확장 가능:

{:name "claude" :cli "claude" :args ["--output-format" "stream-json" "--print"]}
{:name "ollama" :cli "ollama" :args ["run" "--format" "json"]}

관련 링크

관련노트