이 노트에 대하여

프록시 레이어조차 도구를 가져야 한다는 당연한 사실을 실감하며 만들어진 문서다. 방대한 Python 래퍼를 훨씬 작은 Clojure 코드로 재구성하며, Emacs와 gptel에서 도구 사용 가능한 경로를 연다. 얇지만 결정적인 기반층을 손에 넣는 과정이다.

히스토리

  • [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"]}

관련 링크

관련노트