cliproxyapi-statusline

Display cliproxyapi proxy multi-account quota usage in Claude Code statusline. cliproxyapi 프록시 서버의 다중 계정 쿼터 사용량을 Claude Code 상태라인에 표시하는 스킬.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "cliproxyapi-statusline" with this command: npx skills add tinycellcorp/cliproxyapi-statusline/tinycellcorp-cliproxyapi-statusline-cliproxyapi-statusline

cliproxy-statusline

cliproxyapi 프록시 서버의 다중 계정 쿼터 사용량을 Claude Code 상태라인에 표시하는 방법을 안내합니다.


0. 설정 (Setup)

0-1. 설정 파일

이 스킬은 ~/.cliproxy-statusline.json 파일에서 프록시 서버 정보를 읽습니다.

{
  "proxyUrl": "http://localhost:3000",
  "managementKey": "your-management-key"
}
  • proxyUrl: cliproxyapi 프록시 서버 URL
  • managementKey: 관리 API 인증 키

0-2. 최초 설정 흐름

설정 파일이 없으면 사용자에게 다음을 순서대로 질문합니다:

  1. "cliproxyapi 프록시 서버 URL을 입력해주세요 (기본값: http://localhost:3000):"
  2. "관리 API 키(management key)를 입력해주세요:"

입력받은 값으로 ~/.cliproxy-statusline.json을 생성합니다:

import { writeFileSync, chmodSync } from 'node:fs';
import { homedir } from 'node:os';
import { join } from 'node:path';

const CONFIG_PATH = join(homedir(), '.cliproxy-statusline.json');

writeFileSync(CONFIG_PATH, JSON.stringify({
  proxyUrl: userInputUrl || 'http://localhost:3000',
  managementKey: userInputKey
}, null, 2));

// Unix: 파일 권한 제한 (소유자만 읽기/쓰기)
try { chmodSync(CONFIG_PATH, 0o600); } catch {}

0-3. 설정 로드

import { readFileSync } from 'node:fs';
import { homedir } from 'node:os';
import { join } from 'node:path';

const CONFIG_PATH = join(homedir(), '.cliproxy-statusline.json');

function loadConfig() {
  try {
    const raw = readFileSync(CONFIG_PATH, 'utf8');
    const config = JSON.parse(raw);
    if (!config.proxyUrl || !config.managementKey) {
      throw new Error('Missing required fields: proxyUrl, managementKey');
    }
    return config;
  } catch {
    return null; // config not found or invalid
  }
}

설정이 없으면(loadConfig() returns null) 0-2의 설정 흐름을 실행합니다.


1. 개요

cliproxyapi는 여러 Anthropic 계정의 OAuth 토큰을 관리하는 프록시 서버입니다. 각 계정마다 5시간 및 7일 단위의 사용량 쿼터가 존재하며, fill-first 라우팅 방식으로 가장 많이 채워진 쿼터를 우선 소진합니다.

상태라인에 쿼터를 표시하면 현재 어느 계정이 활성 중인지, 각 계정의 남은 여유를 한눈에 파악할 수 있습니다.


2. API 엔드포인트

2-1. auth-files 목록 조회

GET {PROXY_URL}/v0/management/auth-files
Authorization: Bearer {MGMT_KEY}

응답: files 배열을 포함하는 객체. provider === "claude" 항목만 필터링합니다.

{
  "files": [
    { "name": "account1.json", "provider": "claude" },
    { "name": "account2.json", "provider": "claude" }
  ]
}

2-2. 계정별 토큰 다운로드

GET {PROXY_URL}/v0/management/auth-files/download?name={file.name}
Authorization: Bearer {MGMT_KEY}

응답에서 access_token 필드를 추출합니다.

2-3. Anthropic OAuth usage API

GET https://api.anthropic.com/api/oauth/usage
Authorization: Bearer {access_token}
anthropic-beta: oauth-2025-04-20

2-4. 응답 구조

{
  "five_hour": {
    "utilization": 0.52,
    "resets_at": "2026-02-19T15:30:00Z"
  },
  "seven_day": {
    "utilization": 0.07,
    "resets_at": "2026-02-25T12:00:00Z"
  }
}
  • utilization: 퍼센트 단위 (예: 37.0 = 37%). 코드에서 0.0~1.0 비율로 정규화 필요
  • resets_at: ISO 8601 리셋 시각

3. 상태라인 기본 원리

3-1. Claude Code settings.json 등록

~/.claude/settings.jsonstatusLine.command에 실행 파일 경로를 등록합니다.

{
  "statusLine": {
    "command": "node /path/to/proxy-status.mjs"
  }
}

3-2. One-shot 프로세스 모델

상태라인 프로세스는 매 렌더링마다 독립 실행됩니다.

  1. Claude Code가 프로세스를 시작합니다.
  2. stdin으로 JSON 컨텍스트를 전송합니다.
  3. 프로세스는 stdout에 결과를 출력하고 종료합니다.
// stdin 소비
const input = await new Promise(resolve => {
  let data = '';
  process.stdin.on('data', chunk => data += chunk);
  process.stdin.on('end', () => resolve(JSON.parse(data || '{}')));
});

// stdout 출력 후 종료
process.stdout.write(output + '\n');
process.exit(0);

3-3. stdin 주요 필드

필드설명
context_window현재 컨텍스트 사용량
model현재 모델 이름
cwd현재 작업 디렉토리
transcript_path대화 파일 경로

3-4. ANSI 색상 코드

코드용도조건
\x1b[32m초록 (정상)utilization < 0.70
\x1b[33m노랑 (주의)0.70 ≤ utilization < 0.90
\x1b[31m빨강 (위험)utilization ≥ 0.90
\x1b[2mdim (보조 텍스트)리셋 시간, 레이블 등
\x1b[0mreset색상 종료

3-5. 바 차트 형식

function makeBar(utilization, width = 8) {
  const filled = Math.round(utilization * width);
  return '█'.repeat(filled) + '░'.repeat(width - filled);
}

표시 예시:

Q1 5h:[████░░░░]52%(1h17m) wk:[█░░░░░░░]7%(6d20h)
Q2 5h:[██████░░]78%(0h43m) wk:[████░░░░]52%(3d12h)

리셋까지 남은 시간 포맷:

function formatRemaining(resetsAt) {
  const ms = new Date(resetsAt) - Date.now();
  if (ms <= 0) return '0m';
  const h = Math.floor(ms / 3600000);
  const m = Math.floor((ms % 3600000) / 60000);
  if (h >= 24) {
    const d = Math.floor(h / 24);
    return `${d}d${h % 24}h`;
  }
  return `${h}h${m}m`;
}

4. 구현 패턴

4-1. HTTP 조회 (node:http / node:https)

import { request } from 'node:https';

function httpGet(url, headers = {}) {
  return new Promise((resolve) => {
    const mod = url.startsWith('https') ? require('node:https') : require('node:http');
    const req = mod.get(url, { headers, timeout: 5000 }, res => {
      let body = '';
      res.on('data', chunk => body += chunk);
      res.on('end', () => {
        try { resolve(JSON.parse(body)); }
        catch { resolve(null); }
      });
    });
    req.on('error', () => resolve(null));
    req.on('timeout', () => { req.destroy(); resolve(null); });
  });
}

4-2. 파일 기반 캐싱 (30초 TTL)

이중 임계값 전략:

  • TTL (30s): 만료 시 백그라운드 갱신 트리거, 기존 캐시 반환
  • TTL*3 (90s): 초과 시 캐시 폐기, null 반환
import { readFileSync, writeFileSync } from 'node:fs';
import { homedir } from 'node:os';
import { join } from 'node:path';

const CACHE_PATH = join(homedir(), '.cliproxy-statusline-cache.json');
const TTL = 30_000;

function readCache() {
  try {
    const { ts, data } = JSON.parse(readFileSync(CACHE_PATH, 'utf8'));
    const age = Date.now() - ts;
    return { data, stale: age > TTL, dead: age > TTL * 3 };
  } catch {
    return { data: null, stale: true, dead: true };
  }
}

function writeCache(data) {
  writeFileSync(CACHE_PATH, JSON.stringify({ ts: Date.now(), data }));
}

4-3. Fill-first 정렬

weekly utilization 내림차순 정렬 → 가장 높은 계정이 현재 활성(fill-first 소진 중).

accounts.sort((a, b) => b.seven_day.utilization - a.seven_day.utilization);

4-4. 프록시 활성 판별

현재 세션이 프록시를 경유하는지 판별합니다. ANTHROPIC_BASE_URL 환경변수가 설정되어 있고, 그 값이 설정 파일의 proxyUrl과 동일한 호스트를 가리킬 때만 프록시 세션으로 간주합니다.

function isProxySession(config) {
  const baseUrl = process.env.ANTHROPIC_BASE_URL;
  if (!baseUrl) return false;
  try {
    const proxyHost = new URL(config.proxyUrl).host;
    const sessionHost = new URL(baseUrl).host;
    return proxyHost === sessionHost;
  } catch {
    return false;
  }
}

4-5. 전체 조회 함수

const config = loadConfig();
if (!config || !isProxySession(config)) {
  // 설정 없음 또는 프록시 비경유 세션 -- 쿼터 라인 생략
  process.stdout.write('\n');
  process.exit(0);
}

async function fetchProxyUsage(proxyUrl, mgmtKey) {
  const authHeader = { Authorization: `Bearer ${mgmtKey}` };

  // 1. auth-files 목록 (응답이 {files:[...]} 객체)
  const resp = await httpGet(`${proxyUrl}/v0/management/auth-files`, authHeader);
  const files = resp.files || [];
  const claudeFiles = files.filter(f => f.provider === 'claude');

  // 2. 각 파일에서 토큰 다운로드
  const tokens = await Promise.all(
    claudeFiles.map(f =>
      httpGet(`${proxyUrl}/v0/management/auth-files/download?name=${encodeURIComponent(f.name)}`, authHeader)
        .then(d => d.access_token)
    )
  );

  // 3. Anthropic usage API 조회
  const usages = await Promise.all(
    tokens.map(token =>
      httpGet('https://api.anthropic.com/api/oauth/usage', {
        Authorization: `Bearer ${token}`,
        'anthropic-beta': 'oauth-2025-04-20',
      })
    )
  );

  return usages;
}

const usages = await fetchProxyUsage(config.proxyUrl, config.managementKey);

4-6. utilization 정규화

Anthropic API는 utilization을 퍼센트 단위(예: 37.0 = 37%)로 반환합니다. 내부적으로 0.0~1.0 비율로 정규화합니다.

function normalizeUtil(val) {
  return val > 1 ? val / 100 : val;
}

4-7. 표시 렌더링

function colorize(utilization, text) {
  const color = utilization >= 0.9 ? '\x1b[31m'
              : utilization >= 0.7 ? '\x1b[33m'
              : '\x1b[32m';
  return `${color}${text}\x1b[0m`;
}

function renderQuota(usages) {
  return usages.map((u, i) => {
    const fh = { utilization: normalizeUtil(u.five_hour.utilization), resets_at: u.five_hour.resets_at };
    const wk = { utilization: normalizeUtil(u.seven_day.utilization), resets_at: u.seven_day.resets_at };
    const fhPct = Math.round(fh.utilization * 100);
    const wkPct = Math.round(wk.utilization * 100);
    return (
      `\x1b[2mQ${i + 1}\x1b[0m ` +
      `\x1b[2m5h:\x1b[0m[${colorize(fh.utilization, makeBar(fh.utilization))}]` +
      `${colorize(fh.utilization, `${fhPct}%`)}` +
      `\x1b[2m(${formatRemaining(fh.resets_at)})\x1b[0m ` +
      `\x1b[2mwk:\x1b[0m[${colorize(wk.utilization, makeBar(wk.utilization))}]` +
      `${colorize(wk.utilization, `${wkPct}%`)}` +
      `\x1b[2m(${formatRemaining(wk.resets_at)})\x1b[0m`
    );
  }).join('\n');
}

5. OMC HUD 연동 (선택 사항)

oh-my-claudecode(OMC) HUD를 사용하는 경우, 별도 statusLine 커맨드 없이 HUD 출력에 쿼터 라인을 통합할 수 있습니다. OMC 없이 standalone 모드를 사용하는 경우 이 섹션을 건너뛰세요.

상세 구현은 references/omc-hud.md를 참조하세요.


6. 트러블슈팅

프록시 서버 미응답

증상: 쿼터 라인이 표시되지 않거나 빈 상태.

동작:

  • TTL 이내 캐시가 있으면 캐시 데이터 표시 (stale 마킹)
  • TTL*3 초과 시 null 반환 → 쿼터 라인 생략

해결: 프록시 서버 실행 상태 확인. curl {PROXY_URL}/v0/management/auth-files -H "Authorization: Bearer {KEY}" 로 직접 테스트.

설정 파일을 찾을 수 없음

증상: 쿼터 라인이 표시되지 않음.

확인: ~/.cliproxy-statusline.json 파일이 존재하는지 확인합니다.

cat ~/.cliproxy-statusline.json

해결: 파일이 없으면 수동으로 생성합니다:

echo '{"proxyUrl":"http://localhost:3000","managementKey":"YOUR_KEY"}' > ~/.cliproxy-statusline.json

또는 스킬을 다시 호출하여 설정 흐름을 실행합니다.

settings.json의 env가 상태라인에 전달되지 않음

증상: settings.jsonenv 섹션에 설정한 환경변수가 statusLine 프로세스에서 읽히지 않음.

해결: ~/.cliproxy-statusline.json 설정 파일을 사용합니다 (Section 0 참조). 환경변수 대신, loadConfig()로 설정을 읽습니다.

const config = loadConfig(); // ~/.cliproxy-statusline.json
const { proxyUrl, managementKey } = config;

프록시 경유인데 쿼터가 표시되지 않음

증상: 프록시를 통해 접속했는데 쿼터 라인이 표시되지 않음.

원인: isProxySession()process.env.ANTHROPIC_BASE_URL을 확인합니다. 이 환경변수가 셸 레벨에서 설정되어야 statusLine 프로세스가 상속받을 수 있습니다. settings.jsonenv 섹션에만 설정된 경우 statusLine 프로세스에는 전달되지 않습니다.

해결: 프록시 실행 래퍼(예: ccs alias)에서 ANTHROPIC_BASE_URL을 셸 환경변수로 export합니다:

export ANTHROPIC_BASE_URL="http://localhost:3000"
claude  # 이 프로세스와 statusLine이 환경변수를 상속받음

OMC non-breaking space 변환으로 문자열 매칭 실패

증상: OMC HUD가 출력 문자열 내 일반 공백을 \u00A0(non-breaking space)으로 변환하여 정규식이나 문자열 비교가 실패함.

해결: 공백 비교 시 \u00A0도 함께 처리:

const normalized = str.replace(/\u00A0/g, ' ');

첫 실행 시 캐시 없음

증상: 최초 실행 시 쿼터 라인이 표시되지 않음.

동작 (정상): 캐시 파일이 없으면 fetch를 await하여 캐시를 생성하고, 결과를 표시합니다.

const config = loadConfig();
if (!config || !isProxySession(config)) {
  process.stdout.write('\n');
  process.exit(0);
}

const { data, stale, dead } = readCache();

if (!dead && data) {
  output = renderQuota(data);
  if (stale) {
    // stale: 기존 캐시 표시 후 백그라운드 갱신 (fire-and-forget OK)
    fetchProxyUsage(config.proxyUrl, config.managementKey)
      .then(writeCache)
      .catch(() => {});
  }
} else {
  // dead 또는 캐시 없음: await로 fetch 완료 대기
  try {
    const fresh = await fetchProxyUsage(config.proxyUrl, config.managementKey);
    writeCache(fresh);
    output = renderQuota(fresh);
  } catch {
    output = '';
  }
}

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

cocos-mcp-setup

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

openclaw-version-monitor

监控 OpenClaw GitHub 版本更新,获取最新版本发布说明,翻译成中文, 并推送到 Telegram 和 Feishu。用于:(1) 定时检查版本更新 (2) 推送版本更新通知 (3) 生成中文版发布说明

Archived SourceRecently Updated
Coding

ask-claude

Delegate a task to Claude Code CLI and immediately report the result back in chat. Supports persistent sessions with full context memory. Safe execution: no data exfiltration, no external calls, file operations confined to workspace. Use when the user asks to run Claude, delegate a coding task, continue a previous Claude session, or any task benefiting from Claude Code's tools (file editing, code analysis, bash, etc.).

Archived SourceRecently Updated