---
title: "§proxycli: 프록시 레퍼에게도 도구는 필요하다"
url: "https://notes.junghanacs.com/botlog/20260318T142424"
author: "Junghan Kim"
date: "2026-03-18T14:24:00+09:00"
lastmod: "2026-03-20T06:46:00+09:00"
tags: ["botlog","clojure","emacs","gptel"]
description: "도구 없는 래퍼의 한계를 넘어, Claude Code용 프록시를 Clojure로 얇고 강하게 다시 만든 proxycli의 탄생 기록이다."
---

> [!abstract] 이 노트에 대하여
> 
> 프록시 레이어조차 도구를 가져야 한다는 당연한 사실을 실감하며 만들어진 문서다. 방대한 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** 로 새 리포를 만들었다.

-   원본: [aaronlippold/claude-code-openai-wrapper](https://github.com/aaronlippold/claude-code-openai-wrapper)
-   포크: [junghan0611/claude-code-openai-wrapper](https://github.com/junghan0611/claude-code-openai-wrapper) (ko branch)
-   신규: [junghan0611/proxycli](https://github.com/junghan0611/proxycli)

### 왜 proxycli가 필요한가

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

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

맨날 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`)가 내부적으로 하는 일:

```bash
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 없이 직접 호출하면 된다.

### 코드 규모 비교

| 항목   | Python     | Clojure       |
|------|------------|---------------|
| 소스 파일 | 12개       | 3개           |
| 소스 줄 수 | 4,828      | 441           |
| 테스트 | 6,879줄    | ~100줄        |
| 외부 의존성 | 10개+      | 3개           |
| 배포 형태 | Python env | 39MB 단일 바이너리 |
| 기동 시간 | ~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 구조

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

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

### 관련 링크

-   [GitHub: proxycli](https://github.com/junghan0611/proxycli)
-   [GitHub: claude-code-openai-wrapper (포크, ko branch)](https://github.com/junghan0611/claude-code-openai-wrapper)
-   [GitHub: 원본 Python wrapper](https://github.com/aaronlippold/claude-code-openai-wrapper)
-   [Claude Agent SDK (Python)](https://github.com/anthropics/claude-agent-sdk-python)
-   [gptel — Emacs LLM client](https://github.com/karthink/gptel)

## 관련노트

-   [@junghan0611 §claude-code-openai-wrapper 클로드코드 Wrapper 테스트 지피텔](https://notes.junghanacs.com/notes/20260101T122914)
