OpenAI/Gemini/Claude 호환 API로 바꾸는 Go 게이트웨이/v1/chat/completions(OpenAI)·/v1/messages(Claude)·/v1beta(Gemini)로 호출하게 해준다. "API 키 없이(no API keys)" 구독을 코드에서 쓴다는 게 이 프로젝트의 한 줄 요약.
이 문서는 "CLIProxyAPI를 설치하는 법"이 아니라, 멀티 프로바이더 API 게이트웨이를 어떻게 설계하는가를 공부하기 위한 자료다 — API 포맷 변환 계층·OAuth 인증·계정 풀 로드밸런싱·SSE 스트리밍의 기술 밀도가 매우 높은 교재. 동시에 이 도구가 딛고 선 이용약관(ToS) 회색지대도 솔직하게 짚는다.
(저장소: router-for-me/CLIProxyAPI · Go 1.26 · 766개 .go 파일 ~24.7만 줄 · v7.2.71 · MIT 라이선스 · 매우 활발 · TrendShift 오늘 Daily #3)
CLIProxyAPI는 "Claude Pro/Max·ChatGPT Plus·Gemini 같은 월정액 AI 구독 계정을, 표준 API처럼 프로그램에서 호출할 수 있게 중계해주는 프록시 서버"다. 이름을 풀면 CLI(각 벤더의 공식 명령줄 도구가 로그인한 세션) + Proxy(그 세션을 대신 붙잡고 요청을 중계) + API(OpenAI/Claude/Gemini 호환 HTTP 엔드포인트로 노출). 즉 "내가 이미 구독한 계정을, API 키를 따로 발급받지 않고도 API 클라이언트·SDK·에이전트에서 쓰게 해준다".
보통 AI API는 놀이기구를 탈 때마다 표를 사는 종량제다 — 토큰을 쓴 만큼 돈을 낸다(OpenAI/Anthropic API 키). 반면 Claude Code·Codex·Gemini CLI 구독은 월정액 자유이용권이다 — 정해진 한도 안에서 마음껏 탄다. 문제는 이 자유이용권이 "그 공식 CLI 앱 안에서만" 통한다는 것.
CLIProxyAPI는 그 자유이용권을 대신 들고 서서, 누가 표를 달라고 하면(=API 요청) 자기 이용권으로 기구를 태워주는 매표 대행소다. 내 코드는 평범한 OpenAI API를 부르는 것처럼 요청하고, 프록시가 뒤에서 구독 세션으로 실제 응답을 받아 형식을 맞춰 돌려준다. 여러 계정을 모아 돌려 쓰면(로드밸런싱) 한도도 늘어난다.
LiteLLM·one-api·OmniRoute와 같은 계열이다.2026년 개발자들에게 AI 도구 비용은 현실적 고민이다. 에이전트·자동화를 굴리면 종량제 API 토큰 요금이 순식간에 불어난다. 반면 Claude Max·ChatGPT Pro 같은 월정액 구독은 한도가 넉넉하지만 "공식 앱 안에서만" 쓸 수 있다. CLIProxyAPI는 이 간극을 정확히 노린다 — 이미 낸 구독료로 API를 쓰게 해주는 것. 여기에 여러 벤더·여러 계정을 한 엔드포인트로 묶는 실용성이 더해져 X·해커뉴스에서 화제가 되며 Daily 최상위로 올라왔다. (TrendShift의 굵은 숫자는 별 수가 아니라 사회적 언급 모멘텀이다.)
핵심 가치 제안은 명확하다: 종량제 API 대신, 이미 결제한 정액 구독을 API처럼 쓴다. Cursor·Windsurf가 토큰 단위로 과금하는 것과 달리, 구독 한도 안에서 자율 에이전트·배치 작업을 돌릴 수 있다. DesktopCommanderMCP 같은 도구가 내세운 "API 토큰이 아닌 구독으로 굴린다"와 같은 철학이되, CLIProxyAPI는 이를 표준 API 엔드포인트 형태로 일반화했다.
Codex(ChatGPT)·Claude Code·Gemini CLI·Google Antigravity·Moonshot Kimi·xAI Grok·Vertex AI·AI Studio까지 10종의 상류 실행기(executor)를 내장하고, 이를 전부 OpenAI/Claude/Gemini 호환 포맷으로 노출한다. 클라이언트는 어느 모델을 쓰든 익숙한 OpenAI API 한 벌로 말을 걸면 된다 — 백엔드가 무엇이든 프록시가 형식을 번역해준다.
여러 계정을 등록하면 round-robin(순번 분배)이나 fill-first(한 계정을 다 쓰고 다음으로) 전략으로 요청을 여러 구독에 분산한다. 한 계정이 한도 초과(429)되면 자동으로 다른 계정으로 넘기고(cooldown/failover), 세션 고정(session affinity)으로 대화 연속성도 유지한다. "여러 API 키를 묶어 한도를 늘리는" 게이트웨이의 전형적 기능을 구독 계정에 적용한 셈.
최신 커밋이 2026-07-13(PR #4190)일 만큼 개발이 빠르고, 릴리스가 v7.2.71까지 찍혔다. 게다가 sdk/ 아래에 Go 라이브러리로 임베드할 수 있는 미러 구조를 제공해, 다른 프로젝트가 CLIProxyAPI를 통째로 삼켜 쓰기도 한다(README의 "built on" 목록). 파일·Postgres·Git·S3 토큰 저장소, 클러스터(control-plane) 모드까지 갖춘, 완성도 높은 인프라 소프트웨어.
| 비교 축 | CLIProxyAPI | LiteLLM / one-api | 공식 벤더 API |
|---|---|---|---|
| 뒤에 붙는 대상 | 구독 계정(OAuth 세션) | API 키(종량제) | API 키(종량제) |
| 과금 모델 | 월정액 구독 재활용 | 토큰 종량제 그대로 | 토큰 종량제 |
| 노출 포맷 | OpenAI·Claude·Gemini·Codex 호환 | 주로 OpenAI 호환 | 각 벤더 고유 |
| 구현 언어 | Go | Python / Go | — |
| 약관 위치 | 회색지대(§6) | 합법(정식 키) | 합법(정식 키) |
기술적으로 CLIProxyAPI는 내 계정의 정당한 토큰을 쓴다. 하지만 각 벤더의 이용약관은 대개 "구독은 공식 클라이언트에서, 개인 용도로"를 전제하고, API 재판매·자동화 우회를 금지하기도 한다. 즉 코드 자체는 인프라 소프트웨어지만, 쓰는 방식에 따라 약관 위반 소지가 생긴다. 실제로 이 프로젝트는 공식 CLI인 척 트래픽을 위장(uTLS 지문 스푸핑)하는 코드까지 품고 있다(§6). 이 문서는 설계를 공부하려는 것이지, 약관 위반을 권하는 게 아니다 — 이 경계를 분명히 인지하고 읽어야 한다.
CLIProxyAPI는 단일 Go 모듈이다. 모듈 경로가 github.com/router-for-me/CLIProxyAPI/v7로, 메이저 버전 7이 경로에 박혀 있다(Go의 semantic import versioning). 언어는 Go 1.26.0(꽤 최신), 빌드에 CGO_ENABLED=1이 필요하다 — C-ABI 동적 라이브러리 플러그인을 지원하기 때문(§4-6). 규모는 937개 추적 파일 중 766개가 .go, 총 약 247,614줄로, 사실상 Go 100%다.
레포의 internal/registry/models/models.json(2,559줄)에는 claude-opus-4-8·claude-sonnet-5·gemini-3.5-flash·gpt-5.5 같은 근미래/추정 모델명이 실려 있고, 런타임에 원격 업데이터가 목록을 갱신한다(--local-model로 끄기 가능). 2026년이라는 저장소의 시간 설정과 일치하는 부분이니, 실제 사용 시 지금 내 계정이 접근 가능한 모델과 카탈로그가 다를 수 있음을 감안하라. 이 딥다이브의 수치·구조는 전부 클론한 소스 기준이다.
| 영역 | 패키지 (버전) | 역할 / 왜 골랐나 |
|---|---|---|
| HTTP 프레임워크 | gin-gonic/gin 1.10.1 | 모든 API 라우트·미들웨어의 뼈대 |
| JSON "수술" | tidwall/gjson 1.18 + sjson 1.2.5 | 구조체 대신 경로로 JSON 필드를 읽고 쓰는 방식 — 번역 계층 전체가 이걸로 굴러감 |
| TLS 지문 위장 | refraction-networking/utls 1.8.2 | Chrome ClientHello를 흉내 내 Cloudflare TLS 지문 검사 우회(Anthropic 도메인) |
| WebSocket | gorilla/websocket 1.5.3 | Codex/xAI ws 실행기 · /v1/responses ws · wsrelay |
| OAuth | golang.org/x/oauth2 0.30 | 각 벤더 OAuth2 로그인 흐름 |
| 토큰 카운팅 | tiktoken-go/tokenizer 0.7 | 요청 토큰 수 계산(과금·한도 추적) |
| 영역 | 패키지 (버전) | 역할 |
|---|---|---|
| 로깅 | sirupsen/logrus 1.9.3 + lumberjack.v2 2.2.1 | 구조화 로그 + 로그 회전 |
| 터미널 UI | charmbracelet/bubbletea 1.3.10 · bubbles · lipgloss | --tui 대화형 터미널 대시보드 |
| 토큰 저장소 | jackc/pgx v5 · minio-go v7 · go-git v6 | 인증 토큰을 Postgres · S3 · Git 저장소에 보관(파일이 기본) |
| 사용량 큐 | redis/go-redis v9.19 | 사용량 통계 보존 큐(선택) |
| 설정 핫리로드 | fsnotify/fsnotify 1.9 + joho/godotenv 1.5 | config.yaml 변경 자동 감지 · .env 자동 로드 |
| 압축 | andybalholm/brotli · klauspost/compress | 응답 브로틀리/gzip 처리 |
Dockerfile은 golang:1.26-bookworm(빌더) → debian:bookworm(런타임) 2단계로, CGO_ENABLED=1과 ldflags로 버전/커밋/빌드일자를 주입하고 EXPOSE 8317을 연다(공식 이미지 eceasy/cli-proxy-api). docker-compose.yml은 API 포트 8317과 함께 OAuth 콜백용 포트들(8085·1455·54545·51121·11451)을 매핑하고, config.yaml·auths/·logs/를 볼륨으로 뺀다. GitHub Actions의 release.yaml은 darwin·windows·linux(glibc/musl)·freebsd 멀티플랫폼 매트릭스 빌드를 돌린다(musl/freebsd는 플러그인 없는 no-plugin 변형).
gjson.Get(json, "messages.0.content")처럼 경로 문자열로 값만 콕 집어 읽고, sjson은 같은 방식으로 값을 써넣는다. CLIProxyAPI의 번역 계층은 상류마다 스키마가 제각각이라, 고정 구조체 대신 이 "경로 기반 수술"로 필드를 이리저리 옮긴다. 유연하지만 타입 안전성은 포기하는 트레이드오프.이 섹션의 첫 문장만 기억해도 절반은 이해한 것이다: CLIProxyAPI = "들어온 요청을 표준 포맷으로 정규화 → 알맞은 계정을 고르고 → 그 상류의 포맷으로 번역해 보내고 → 응답을 다시 클라이언트 포맷으로 되번역"하는 파이프라인이다. 이 파이프라인이 세 개의 큰 근육 위에서 돈다 — ①번역 계층(translator) · ②상류 실행기(executor) · ③라우팅 지휘자(conductor). 각각이 이 레포에서 가장 큰 패키지들이다.
internal/translator/는 이 레포에서 가장 큰 패키지다(약 45,554줄). 문제의 본질은 이렇다: OpenAI·Claude·Gemini·Codex는 요청/응답 JSON 스키마가 전부 다르다. 클라이언트가 OpenAI 포맷으로 보냈는데 뒤에 붙은 건 Claude라면, 누군가 형식을 바꿔줘야 한다. CLIProxyAPI는 이를 (출발 포맷, 도착 포맷) 쌍을 키로 하는 변환 레지스트리로 푼다.
// sdk/translator/registry.go — (from,to) 쌍으로 변환 함수를 등록
// 7개 표준 포맷: openai, openai-response, claude, gemini,
// codex, antigravity, interactions
translator.Register(
"claude", "openai", // Claude 요청 → OpenAI 스키마
ConvertClaudeRequestToOpenAI, // 요청 변환 함수
ConvertOpenAIResponseToClaude, // 응답 되번역 함수
)
// 이런 init.go가 26개 — 각자 한 방향(쌍)을 배선한다
변환 함수는 전부 순수 함수 func(model string, rawJSON []byte, stream bool) []byte 꼴이다 — 앞서 본 gjson/sjson으로 도착 스키마의 JSON 틀을 만들고 필드를 하나씩 옮겨 담는다. 스트리밍 변환은 청크 슬라이스([][]byte)를 돌려준다. 만약 (출발,도착)에 맞는 변환기가 없으면 model 필드만 정규화한 채 그대로 통과(passthrough)시킨다.
포맷이 7개일 때, 모든 쌍을 직접 변환하면 최악 7×6=42가지 조합이 필요하다. 대신 "가운데 정규형(canonical)"을 두고 각 포맷 ↔ 정규형만 만들면 변환 수가 확 준다. CLIProxyAPI의 thinking 모듈이 좋은 예다 — 각 벤더의 "추론(reasoning) 설정"을 일단 canonical ThinkingConfig로 모은 뒤 상류별로 다시 내보낸다. 컴파일러의 중간표현(IR), 환율의 기축통화와 같은 발상.
internal/runtime/executor/(약 39,900줄)에는 상류 제공자마다 하나씩 Executor가 있다. 전부 같은 계약(Identifier() + Execute/ExecuteStream)을 구현해, 지휘자가 어느 상류든 똑같이 다룰 수 있다.
// sdk/cliproxy/executor/types.go — 모든 실행기가 지키는 계약
type Request struct {
Model string
Payload []byte
Format sdktranslator.Format
Metadata map[string]any
}
type StreamResult struct {
Headers http.Header
Chunks <-chan StreamChunk // 스트리밍은 채널로 흘려보냄
}
type StatusError interface { // 401/402/429를 인증상태로 올림
error
StatusCode() int
}
| 실행기 | 상류(구독) | 비고 |
|---|---|---|
| CodexAutoExecutor | ChatGPT / Codex | HTTP + WebSocket 두 방식 |
| ClaudeExecutor | Claude Code | uTLS 지문 위장 · 서명(signing) 처리 |
| GeminiExecutor 외 3종 | Gemini CLI · Vertex AI · AI Studio | Interactions/Vertex/AIStudio 변형 |
| AntigravityExecutor | Google Antigravity | 서명 캐싱 · 크레딧 폴백 |
| KimiExecutor | Moonshot Kimi | — |
| XAIAutoExecutor | xAI Grok | HTTP + WebSocket |
| OpenAICompatExecutor | 범용 OpenAI 호환 | OpenRouter 등 일반 상류 |
구독 계정에 붙으려면 각 벤더의 OAuth2 + PKCE 로그인을 통과해야 한다. CLIProxyAPI는 공식 CLI 도구가 쓰는 것과 동일한 client ID를 그대로 사용한다 — 이게 "API 키 없이" 가능한 이유이자, 동시에 §6에서 짚을 회색지대의 뿌리다.
// internal/auth/claude/anthropic_auth.go (개념 발췌)
AuthURL = "https://claude.ai/oauth/authorize"
TokenURL = "https://api.anthropic.com/v1/oauth/token"
ClientID = "9d1c250a-...-5944d1962f5e" // 실제 Claude Code 클라이언트 ID
Redirect = "http://localhost:54545/callback" // 로컬 콜백 서버
Scopes = "user:profile user:inference user:sessions:claude_code ..."
-claude-login·-codex-login·-antigravity-login·-kimi-login·-xai-login 같은 명령을 실행하면, 프록시가 로컬 포트에 콜백 서버를 잠깐 띄우고 브라우저 OAuth를 유도해 토큰을 받는다(Codex는 device-code 흐름도 지원). 받은 토큰은 기본적으로 ~/.cli-proxy-api의 JSON 인증 레코드로 저장되며(FileTokenStore), 백그라운드 루프가 만료 전 자동으로 갱신한다(auth-auto-refresh-workers). 저장소는 Postgres·Git·S3로도 바꿀 수 있다.
sdk/cliproxy/auth/conductor.go는 6,157줄로 이 레포에서 가장 큰 단일 파일이다. 등록된 여러 계정 중 어느 것으로 이 요청을 보낼지를 결정하고, 실패하면 다른 계정으로 넘기며, 토큰 갱신·쿨다운을 총괄한다.
| 기능 | 동작 |
|---|---|
| 라우팅 전략 | round-robin(계정을 순번대로) 또는 fill-first(한 계정을 소진 후 다음 — 롤링 한도 분산용) |
| 세션 고정 | X-Session-ID·conversation_id 등으로 대화를 한 계정에 묶음(기본 TTL 1시간, 불가 시 자동 전환) |
| 쿨다운/한도 | 계정·모델별 쿨다운 상태 추적, 429에 reset_seconds 반환, quota-exceeded 시 프로젝트 전환·프리뷰 모델·Antigravity 크레딧으로 폴백 |
| 재시도 | request-retry(기본 3) · max-retry-credentials · max-retry-interval |
| 모델 풀·별칭 | 같은 별칭을 여러 상류 모델명에 반복하면 내부 라운드로빈 풀 생성(요청 중 장애조치), oauth-model-alias로 채널별 모델명 개명 |
internal/api/server.go(2,018줄, Gin)가 라우트 표다. OpenAI(/v1/chat/completions·/v1/completions·/v1/responses+ws·/v1/images/*·/v1/videos/*), Claude(/v1/messages·count_tokens), Gemini(/v1beta/models 등), 그리고 Codex 원문 통과(/backend-api/codex/responses)를 연다. 스트리밍은 SSE(text/event-stream)로, 변환기가 청크 슬라이스를 흘려보내고, streaming.keepalive-seconds가 빈 줄을 주기적으로 보내 유휴 타임아웃을 방지한다.
운영 측면에서 특이한 규약: 상류 연결이 열린 뒤에는 타임아웃을 걸지 않는다(긴 에이전트 스트림을 지원하려고). 타임아웃은 "계정 획득 단계"에서만 허용된다. log.Fatal은 금지, 토큰은 로그에 절대 남기지 않는다는 규칙도 AGENTS.md에 명문화돼 있다.
/v0/management/* 그룹은 설정·API 키·프로바이더별 키 CRUD·라우팅 전략·플러그인 설치·로그·사용량·쿼터 리셋을 웹으로 제어한다. remote-management.secret-key가 비어 있으면 이 그룹 전체가 404로 잠긴다(로컬에서도 키 필요). 번들 웹 관리 패널이 /management.html로 제공된다.
더 나아가 internal/pluginhost/(약 17,800줄)는 C-ABI 동적 라이브러리 플러그인 호스트다 — Go/C/Rust로 짠 플러그인이 실행기·번역기·인증 제공자·스케줄러 등 ~24종의 확장점에 꽂힌다. 다만 플러그인은 "프로세스 안에서 신뢰되는 코드"라 기본 비활성이고, 설치형 플러그인 스토어가 따로 있다(§6 주의).
구조를 읽는 요령: "internal은 진짜 구현, sdk는 남이 임포트하는 공개 얼굴"이다. 두 트리가 상당 부분 짝을 이룬다 — internal/은 이 서버 자체의 비공개 구현이고, sdk/는 다른 Go 프로젝트가 CLIProxyAPI를 라이브러리로 삼킬 때 쓰는 안정적 표면이다. 처음 읽는다면 별표(★) 여섯 곳부터 — cmd/server/main.go(부팅) → internal/api/server.go(라우트) → internal/translator/(번역) → internal/runtime/executor/(상류) → sdk/cliproxy/auth/conductor.go(지휘) → sdk/translator/registry.go(변환 등록) 순.
| 정독 우선순위 | 파일 / 폴더 | 배우는 것 |
|---|---|---|
| ★★★ | cmd/server/main.go (773) | 저장소 백엔드 선택 · 로그인/서버 분기 · 부팅 배선 |
| ★★★ | internal/api/server.go (2018) | Gin 라우트 표 · 미들웨어 · 관리 API 등록 |
| ★★★ | sdk/translator/registry.go (278) | (from,to) 변환 레지스트리 — 번역 계층의 뼈대 |
| ★★ | sdk/cliproxy/auth/conductor.go (6157) | 계정 선택·쿨다운·장애조치·토큰 갱신의 지휘부 |
| ★★ | internal/translator/openai/claude/*.go (505·793) | Claude↔OpenAI 구체 변환 — gjson/sjson 실전 |
| ★ | internal/auth/claude/anthropic_auth.go + utls_transport.go | OAuth2+PKCE 흐름 · uTLS 지문 위장 RoundTripper |
이 레포의 가장 값진 교재는 "서로 다른 API 스키마를 어떻게 하나로 잇는가"다. (from,to) 레지스트리 + 정규형(canonical) 패턴 + 순수 함수 변환기는 컴파일러의 IR, 데이터 ETL, 마이크로서비스 어댑터 어디서나 재사용되는 보편 설계다. gjson/sjson으로 "구조체 없이 JSON을 수술"하는 실전 코드도 여기서 통째로 읽을 수 있다.
"브라우저를 열어 로그인하고 → 로컬 콜백 서버가 코드를 받고 → 토큰으로 교환하고 → 백그라운드로 자동 갱신"하는 OAuth2+PKCE 전 과정이 벤더별로 구현돼 있다. OAuth를 글로만 배운 사람에게, 콜백 포트·PKCE 챌린지·refresh 루프가 실제 코드로 어떻게 생겼는지 보여주는 살아있는 예제.
conductor.go의 round-robin/fill-first·쿨다운·세션 고정·재시도는 "여러 백엔드를 묶어 한도를 늘리고 장애를 견디는" 분산 시스템의 축소판이다. 로드밸런서·API 게이트웨이·프록시를 설계할 때 필요한 개념(헬스체크·서킷브레이커 유사 쿨다운·스티키 세션)을 한 파일에서 배운다.
Gin 라우팅·SSE 스트리밍·typed 저장소 추상화(파일/Postgres/Git/S3를 같은 인터페이스로)·fsnotify 핫리로드·Bubbletea TUI·멀티플랫폼 릴리스 매트릭스까지 — 규모 있는 Go 인프라 서비스의 표준 관례가 한 레포에 모여 있다. "log.Fatal 금지, 토큰 로깅 금지, 연결 후 타임아웃 금지" 같은 규약을 AGENTS.md에 문서화한 것도 팀 엔지니어링 참고가 된다.
CLIProxyAPI는 공식 CLI의 client ID를 그대로 써서 구독 세션을 API로 바꾼다. 기술적으로는 내 계정의 정당한 토큰이지만, 대부분의 벤더 약관은 구독을 "공식 클라이언트에서 개인 용도로" 쓰도록 제한하고 API 재판매·자동화 우회를 금지한다. README 생태계(스폰서 다수)가 AI 구독 계정 재판매·중계 서비스로 채워져 있다는 점도, 이 도구가 상업적 우회에 쓰인다는 방증이다. 계정 정지·법적 리스크는 사용자 몫이며, 이 문서는 설계 학습용이지 약관 위반을 권하지 않는다.
internal/auth/claude/utls_transport.go는 refraction-networking/utls로 Chrome ClientHello를 흉내 내 Cloudflare의 TLS 지문 검사를 우회한다("공식 CLI 트래픽처럼 보이게"). 더 노골적으로, codex.identity-confuse 설정은 요청 추적 식별자(prompt_cache_key 등)를 계정마다 바꿔치기한다 — 주석은 이를 "일부 사용자가 추적 ID가 정지 근거가 될까 걱정해서 넣은 옵션"이라 설명한다. 즉 개발자 스스로 정지 회피가 사용 동기임을 인지하고 있다. 이런 위장 기법이 존재한다는 사실 자체가 §주의1의 회색지대를 뒷받침한다.
플러그인 호스트는 C/Rust/Go로 짠 .so/.dll을 프로세스 안에서 로드한다 — 샌드박스가 아니라 서버와 같은 권한으로 도는 신뢰 코드다. 그래서 기본 비활성이고, 설정 주석도 이 점을 경고한다. 플러그인 스토어에서 남의 플러그인을 설치할 때는 소스를 반드시 확인해야 한다. 관리 API도 로컬조차 secret-key를 요구하도록 설계된 이유가 이 위험성 때문이다.
| 항목 | 요구사항 |
|---|---|
| 빌드 도구 | Go 1.26 SDK · CGO_ENABLED=1(플러그인 지원 시). 미리 빌드된 릴리스 바이너리도 제공 |
| 실행 자원 | 프록시 자체는 매우 가볍다 — 실제 추론은 상류(구독 계정)가 수행. GPU·대용량 RAM 불필요 |
| 포트 | API 8317 + OAuth 콜백 포트(1455·54545·8085·51121·11451) |
| OS / 아키텍처 | darwin · windows · linux(glibc/musl) · freebsd, amd64/arm64 (musl·freebsd는 no-plugin 변형) |
| 전제 조건 | 유효한 구독 계정(Claude/ChatGPT/Gemini 등) — 이게 없으면 프록시는 빈 껍데기 |
| 설정 | config.yaml(예시 config.example.yaml 26KB) + auths/ 토큰 디렉토리. Docker·Compose 제공 |
| 선택 인프라 | 토큰 저장소로 Postgres/S3/Git, 사용량 큐로 Redis, 클러스터 control-plane(mTLS+JWT) |
현실 점검: CLIProxyAPI를 돌리는 데 필요한 건 성능 좋은 서버가 아니라 유효한 구독 계정과, 그 구독을 이렇게 쓰는 것이 약관상 괜찮은지에 대한 스스로의 판단이다. 기술적 진입장벽은 낮지만(가벼운 Go 바이너리), 실질적 진입장벽은 §6의 회색지대에 있다. 학습 목적이라면 구독 없이도 소스만으로 충분히 공부할 수 있다.
레포를 git clone --depth 1로 받아, cmd/server/main.go → internal/api/server.go → sdk/translator/registry.go → sdk/cliproxy/auth/conductor.go 순으로 읽으며, §4-0의 ASCII 흐름이 어느 코드에 대응하는지 표시해보라. "부팅 → 라우트 → 번역 → 지휘"의 큰 그림을 몸에 새기는 연습.
파이썬이나 Go로, OpenAI chat/completions 요청 JSON을 Claude messages 스키마로 바꾸는 40줄짜리 변환 함수를 짜보라(messages 역할 매핑·system 분리·max_tokens 대응). 그런 다음 (from,to)를 키로 하는 작은 레지스트리에 등록해보면, §4-1의 정규형 패턴이 왜 유용한지 손에 잡힌다.
계정 3개를 흉내 낸 리스트에, 각자 "5분당 10회 한도"를 두고 요청을 분배하는 미니 스케줄러를 round-robin과 fill-first 두 방식으로 구현해보라. 한 계정이 429가 되면 쿨다운 타이머를 걸고 건너뛰는 로직까지 넣으면, conductor.go가 왜 그렇게 커졌는지 체감한다.
Gin(또는 아무 HTTP 서버)으로, 상류의 text/event-stream 응답을 받아 청크 단위로 클라이언트에 흘려보내는 프록시를 짜보라. 유휴 타임아웃 방지용 keepalive 빈 줄, 연결이 끊겼을 때의 정리(context 취소)까지 다루면 §4-5의 스트림 포워더를 축소 재현한 것이다.
아무 합법적인 OAuth 제공자(GitHub·Google 등, 본인이 발급한 정식 client ID)로, "브라우저 열기 → 로컬 포트 콜백 수신 → 코드를 토큰으로 교환 → 만료 시 refresh"를 구현해보라. CLIProxyAPI의 internal/auth/*가 벤더마다 반복하는 바로 그 패턴을, 약관 문제 없는 대상으로 안전하게 익히는 과제.
| 주차 | 주제 | 학습 내용 |
|---|---|---|
| 1주차 | Go 웹 서버 & Gin | Gin 라우팅·미들웨어, 컨텍스트, 그레이스풀 종료, 설정 로딩(fsnotify 핫리로드). cmd/server/main.go·internal/api/server.go 읽기 + 과제 1. |
| 2주차 | API 포맷 & 번역 계층 | OpenAI/Claude/Gemini 스키마 비교, gjson/sjson JSON 조작, (from,to) 레지스트리·정규형 패턴. sdk/translator/registry.go·internal/translator/ + 과제 2. |
| 3주차 | OAuth2 & 토큰 수명주기 | OAuth2 인가 코드 흐름·PKCE, 로컬 콜백 서버, refresh 루프, 토큰 저장소 추상화(파일/DB/S3). internal/auth/*·sdk/auth/ + 과제 5. |
| 4주차 | 로드밸런싱 & 스트리밍 | round-robin/fill-first, 쿨다운·세션 고정·장애조치, SSE 스트리밍·keepalive, 상류 실행기 인터페이스. conductor.go·runtime/executor/ + 과제 3·4. |
먼저 요청이 어디로 들어와 어떻게 갈라지는지(1주차)를 잡아라. 그다음 "서로 다른 API를 어떻게 통역하나"(2주차)가 이 레포의 핵심 지력이고, "구독에 어떻게 붙나"(3주차·OAuth)와 "여러 계정을 어떻게 지휘하나"(4주차)가 실전 운영 지혜다. 플러그인 호스트(§4-6)는 가장 무겁고 특수하니 나중에 곁가지로 봐도 된다. AI 인프라·게이트웨이 쪽 관심자라면 2·4주차만으로도 "멀티 프로바이더 게이트웨이의 뼈대"를 완주할 수 있다.
같은 "여러 LLM을 한 API로 묶는" 문제를 다르게 푼 도구들과 나란히 놓으면 시야가 넓어진다. LiteLLM(Python)·one-api(Go)는 정식 API 키를 묶는 게이트웨이라 CLIProxyAPI와 구조는 닮았지만 약관 위치가 다르다(합법). OmniRoute는 같은 트렌딩 목록의 유사 프로젝트다. 뒤집어, DesktopCommanderMCP는 "구독으로 굴린다"는 철학을 MCP 도구로 구현한 사례다. 이들을 비교하면 "구독을 API로"라는 CLIProxyAPI의 선택이 얼마나 실용적이면서도 약관상 미묘한지가 또렷해진다.
"messages.0.content" 같은 경로 문자열로 JSON 값을 읽고(gjson) 쓰는(sjson) 라이브러리. 스키마가 제각각인 번역 계층을 유연하게 만들어준다(타입 안전성은 포기).text/event-stream으로 서버가 클라이언트에 토큰을 조각조각 밀어 보내는 단방향 스트리밍. LLM의 "타이핑되듯 나오는" 응답이 이것. keepalive 빈 줄로 유휴 타임아웃을 막는다.| 구분 | 링크 / 위치 |
|---|---|
| GitHub 저장소 | github.com/router-for-me/CLIProxyAPI |
| TrendShift | trendshift.io/repositories/19331 |
| 핵심 파일 | cmd/server/main.go (부팅 · 저장소 선택 · 로그인 분기) |
| 핵심 파일 | internal/api/server.go (Gin 라우트 표 · 관리 API) |
| 핵심 파일 | sdk/translator/registry.go · internal/translator/ (포맷 번역) |
| 핵심 파일 | sdk/cliproxy/auth/conductor.go (계정 라우팅 지휘자) |
| 핵심 파일 | internal/auth/*/ · internal/auth/claude/utls_transport.go (OAuth · 지문 위장) |
| 이웃 프로젝트 | LiteLLM · one-api(API 키 게이트웨이) · OmniRoute · DesktopCommanderMCP |
| 연관 개념 | API 게이트웨이 · 포맷 번역/정규형 · OAuth2+PKCE · 로드밸런싱/failover · SSE 스트리밍 |