TrendShift 딥다이브 · 2026-07-07 · Daily #6

CodexBar 딥다이브
— "내 AI 코딩 토큰이 얼마나 남았나"를 메뉴바에: 로그인 없이 57개 프로바이더 사용량을 긁어오는 Swift 6 앱

CodexBar는 Codex·Claude Code를 비롯한 57개 AI 코딩 도구의 남은 사용량과 리셋 시각을 macOS 메뉴바에 항상 띄워주는 "로그인 없는" 메뉴바 앱이다. 비밀번호를 새로 받지 않고, 이미 내 컴퓨터에 있는 OAuth 토큰·브라우저 쿠키·로컬 로그 파일을 재사용해 사용량을 읽어온다. Swift 6.2 엄격 동시성(strict concurrency) 위에서 AppKit 메뉴바(NSStatusItem) + SwiftUI 설정 화면을 섞고, codex app-serverclaude CLI를 직접 구동하며, ccusage식 로컬 비용 스캐너까지 갖춘 784개 Swift 파일·약 18.7만 줄의 진짜 제품이다. (저장소: steipete/CodexBar · Swift 6.2 · MIT · v0.41.0 · macOS 14+ · ⭐ 약 15.7k · TrendShift 오늘 Daily 6위)
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어/시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

이 저장소가 대체 무엇인가.

CodexBar(제작자 Peter Steinberger, PSPDFKit 창업자)는 Codex·Claude Code·Cursor·Copilot 등 AI 코딩 구독 도구의 "남은 한도"를 메뉴바 아이콘 하나로 보여주는 macOS 앱이다. 핵심 원칙은 "로그인 없음(no login)" — 새 계정을 만들거나 비밀번호를 넣게 하지 않고, 이미 내 컴퓨터에 저장돼 있는 인증 정보(OAuth 토큰 파일·브라우저 쿠키·CLI 자격증명)를 그대로 재사용해 각 제공사의 사용량 API를 대신 호출한다.

한 줄 개념
메뉴바 사용량 계기판 (menu-bar usage gauge)
"세션 한도 / 주간 한도 / 월간 한도"가 각각 얼마나 남았고 언제 리셋되는지를, 앱을 열지 않아도 화면 맨 위 메뉴바에서 실시간으로 본다. 도구마다 흩어진 한도를 한자리에 모아 보여주는 것이 전부이자 핵심이다.
비유로 이해하기

"자동차 연료 게이지 — 단, 57개 연료탱크를 한 바늘로"

AI 코딩 도구를 쓰다 보면 한창 코딩 중에 갑자기 "이번 주 한도 초과"로 막힌다. 연료가 언제 바닥날지 모르고 달리는 차와 같다.

CodexBar는 그 계기판을 달아준다. 다만 탱크가 하나가 아니라 Codex·Claude·Cursor·Copilot… 수십 개다. 각 도구에 일일이 로그인해 대시보드를 열어보는 대신, 메뉴바 바늘 하나로 "얼마 남았고 몇 시간 뒤 다시 찬다"를 즉시 확인한다.

코드상 버전은 0.41.0(빌드 100)이며 라이선스는 관대한 MIT(저작권 2026 Peter Steinberger, 웹사이트 codexbar.app). 실행에는 macOS 14(Sonoma) 이상이 필요하고, Dock 아이콘 없이 메뉴바에만 사는 LSUIElement 방식이다. 별점은 약 15.7k로, 개발자용 소도구치고는 대단히 높다. CHANGELOG가 161KB에 달할 만큼 거의 매일 릴리스되는 초활발 프로젝트다.

먼저 짚고 갈 것 — 이 문서의 초점
"사용량 표시 앱"이 아니라 "modern macOS 앱의 교과서"

"한도 보여주기"는 겉모습이다. 이 레포의 진짜 가치는 NSStatusItem 메뉴바 설계 · Swift 6 엄격 동시성(actor·@MainActor) · 외부 CLI를 JSON-RPC/PTY로 구동 · 로컬 JSONL 로그 스트리밍 파싱 · 브라우저 쿠키 복호화 · Sparkle 자동업데이트 · 크로스플랫폼 CLI 배포한 코드베이스에서 통째로 볼 수 있다는 점이다. "네이티브 macOS 유틸리티를 제품 수준으로 만드는 법"의 살아있는 표본이다.

2왜 주목받는가

트렌딩 이유와, 흔한 "사용량 추적기"와의 결정적 차이.

① 로그인이 필요 없다 — 기존 세션을 재사용한다

보통의 사용량 대시보드는 "우리 서비스에 로그인하라"고 요구한다. CodexBar는 정반대다. 이미 내 기기에 있는 자격증명을 그대로 빌려 쓴다 — Codex는 ~/.codex/auth.json의 OAuth 토큰, Claude는 ~/.claude/.credentials.json이나 키체인 항목, 웹 대시보드는 Safari·Chrome·Firefox 쿠키를 읽는다. README는 "비밀번호를 저장하지 않는다"고 못박고, 파일시스템을 뒤지지 않고 알려진 소수의 경로만 해당 기능을 켰을 때에만 읽는다고 강조한다.

비교

일반 사용량 앱 = "우리 앱에 다시 로그인하세요"(계정·비밀번호를 또 맡긴다). CodexBar = "이미 로그인돼 있잖아요, 그 세션 잠깐 빌릴게요"(새 비밀번호 없이, 내 컴퓨터 안 자격증명만 재사용).

② 57개 프로바이더를 하나로 묶는다

Codex·OpenAI·Claude·Cursor·Gemini·Copilot·Grok·GroqCloud·z.ai·MiniMax·Kiro·Zed·Vertex AI·AWS Bedrock… README가 나열하는 제공사는 약 57~60개다. 이렇게 많은 AI 코딩 도구의 한도를 한 앱에서 보는 경쟁 제품은 사실상 없다. CHANGELOG는 Thanks @기여자 크레딧으로 가득해, 커뮤니티가 새 프로바이더를 계속 밀어넣는 구조임을 보여준다.

③ API를 안 치고도 비용을 계산한다 (ccusage 방식)

CodexBar는 라이브 한도(API 조회)와 별개로, 로컬 로그 파일만 읽어 "지금까지 토큰을 얼마나 썼고 달러로 얼마인지"를 계산하는 비용 스캐너를 내장한다. ~/.codex/sessions/**/*.jsonl~/.claude/projects/**/*.jsonl을 스트리밍으로 훑어 토큰 수를 세고, 내장 모델별 단가표로 곱한다. README는 이 아이디어가 유명한 ccusage(MIT)에서 왔다고 밝힌다. 네트워크 없이도 동작하는 셈이다.

④ 스크립트가 아니라 '진짜 제품'이다

메뉴바 앱이라고 얕볼 규모가 아니다. Swift 소스 784개 파일, 약 18.7만 줄이 6개의 SPM 타깃으로 나뉘고, Sparkle 자동업데이트(EdDSA 서명)·WidgetKit 위젯·21개 언어 현지화(RTL 포함)·프로바이더 장애 폴링·macOS Tahoe 메뉴바 아이콘 복구 경로까지 갖췄다. 심지어 Linux까지 빌드되는 크로스플랫폼 CLI(codexbar)와 Homebrew·AUR 패키지도 제공한다. "네이티브 유틸리티를 어디까지 다듬을 수 있나"의 상한선을 보여준다.

배움 관점 핵심
"이 레포에서 무엇을 얻는가"

사용량 표시라는 용도는 부차적이다. 진짜 가치는 NSStatusItem 메뉴바 · Swift 6 actor 격리 · 서브프로세스(JSON-RPC)·PTY 자동화 · JSONL 증분 파싱 · 쿠키 복호화 · Sparkle/appcast 배포 · SPM으로 .app 패키징실전 규모 코드로 통째로 관찰할 수 있다는 것.

3기술 스택 전체 지도

무엇으로 만들어졌나 — 계층별로. (Package.swift·Package.resolved에서 실제 확인)

언어 & 빌드 시스템

항목메모
언어Swift (tools 6.2)Swift 6 엄격 동시성 활성(모든 타깃에 StrictConcurrency upcoming feature)
최소 OSmacOS 14 (Sonoma)Info.plist LSMinimumSystemVersion 14.0
빌드Swift Package Manager메인 앱에 .xcodeproj 없음 — Package.swift가 진실원. 위젯만 별도 xcodeproj
패키징Scripts/package_app.sh손으로 쓴 Info.plist로 CodexBar.app을 즉석 서명(ad-hoc)해 생성
UIAppKit + SwiftUI 하이브리드메뉴바는 순수 AppKit(NSStatusItem), 설정 창은 SwiftUI Settings

SPM 의존성 (선언값 → 잠금값)

패키지버전역할
Sparkle2.9.3자동업데이트(SUFeedURL + EdDSA 공개키). macOS 앱 배포 표준
Commander0.2.2CLI 인자 파싱 (제작자 본인 라이브러리)
swift-crypto3.15.1쿠키 복호화·JWT 등 암호화
swift-log1.13.2구조적 로깅
KeyboardShortcuts2.4.0전역 단축키 (sindresorhus)
SweetCookieKit0.4.1브라우저 쿠키 추출 (제작자 본인 라이브러리)
Vortex(pin된 리비전)파티클 효과 — 주간 리셋 축하 컨페티
swift-asn11.7.1swift-crypto 경유 전이 의존성
CSQLite3시스템 라이브러리브라우저·Windsurf·OpenCode의 SQLite 캐시 읽기(주로 Linux 링크용)

주목할 점: 의존성 목록이 짧고 대부분 애플 공식(swift-crypto·swift-log·swift-asn1)이거나 제작자 본인 라이브러리(Commander·SweetCookieKit)다. 무거운 서드파티 프레임워크에 기대지 않고 플랫폼 API를 직접 쓰는 취향이 드러난다.

SPM 타깃 6개 (한 저장소, 여러 산출물)

타깃종류역할
CodexBarCore라이브러리fetch+parse 엔진 — Codex RPC, PTY 러너, Claude 프로브, 웹 스크래핑, 상태 폴링. Linux에서도 컴파일
CodexBarmacOS 앱상태+UI — UsageStore, SettingsStore, StatusItemController, 아이콘 렌더링
CodexBarCLI실행파일크로스플랫폼 codexbar CLI
CodexBarWidget위젯WidgetKit 확장
CodexBarClaudeWatchdog헬퍼안정적인 Claude CLI PTY 세션용 별도 프로세스
CodexBarClaudeWebProbe헬퍼 CLIClaude 웹 페치 진단용 도구

도구 & CI

SwiftFormat + SwiftLint로 스타일을 강제하고(make check), CI(.github/workflows/ci.yml)는 lint를 ubuntu-24.04에서, macOS 테스트를 macos-15-intel 러너에서 Xcode 26.3/26.2로 돌린다. 별도 워크플로 release-cli.yml(19KB)은 macOS·Linux × glibc·musl × arm64·x86_64 조합으로 CLI 타르볼을 굽는다. 이 정도 릴리스 매트릭스는 메뉴바 앱에서 보기 드물다.

4아키텍처 심화 분석

"메뉴바 숫자"가 어디서 어떻게 오는가.

전체 그림 — fetch → store → UI

공식 docs/architecture.md가 그리는 데이터 흐름은 단순명료하다: 백그라운드 새로고침 → 프로바이더 프로브가 사용량을 가져옴 → 중앙 저장소(UsageStore)에 반영 → 메뉴/아이콘/위젯이 그림. 복잡함은 전부 "프로바이더별로 어디서 어떻게 긁어오나"에 숨어 있다.

┌──────────────────────────────────────────────────────────────────────┐ │ CodexBar.app (LSUIElement, Dock 없음) │ │ │ │ 메뉴바 (AppKit) 설정 창 (SwiftUI Settings) │ │ ┌────────────────────────┐ ┌───────────────────────┐ │ │ │ NSStatusItem × N개 │ │ 프로바이더 토글·소스 │ │ │ │ (프로바이더당 1개 또는 │ │ 선택·단축키·테마 │ │ │ │ Merge 모드 1개) │ └───────────┬───────────┘ │ │ └───────────┬────────────┘ │ │ │ │ 그린다 │ 설정 저장 │ │ ▼ ▼ │ │ ┌──────────────────────────┐ 참조 ┌────────────────────────────┐ │ │ │ StatusItemController │◀───────▶│ SettingsStore │ │ │ │ (@MainActor, ~120 확장) │ └────────────────────────────┘ │ │ └───────────┬──────────────┘ │ │ │ 관찰(@Observable) │ │ ▼ │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ UsageStore (@MainActor @Observable, 중앙 상태) │ │ │ │ · isRefreshing 단일화 가드(한 번에 한 배치만) │ │ │ │ · AdaptiveRefreshPolicy(순수함수, 2~30분 간격) │ │ │ └───────────┬───────────────────────────────────────────────────┘ │ │ │ refresh() async │ │ ▼ │ │ ┌───────────────────────────────────────────────────────────────┐ │ │ │ CodexBarCore — UsageFetcher / 프로바이더 프로브 │ │ │ └───────────┬───────────────────────────────────────────────────┘ │ └───────────────┼─────────────────────────────────────────────────────────┘ │ ┌──────────┼───────────┬─────────────────┬──────────────────────┐ ▼ ▼ ▼ ▼ ▼ [OAuth API] [CLI RPC/PTY] [웹 쿠키] [Admin API] [로컬 비용 스캔] auth.json codex/claude Safari/Chrome sk-ant-admin… *.jsonl 로그 → HTTP GET 서브프로세스 /Firefox 쿠키 조직 리포트 토큰×단가표
핵심 개념 — 데이터 소스 랭킹
프로바이더마다 여러 소스를 우선순위로 시도 (fallback chain)
한 숫자를 얻는 길이 하나가 아니다. Codex는 .auto → .oauth → .cli, Claude는 .auto → .api → .oauth → .web → .cli 순으로 가능한 소스를 차례로 시도한다. OAuth 토큰이 없으면 CLI로, CLI가 막히면 웹 쿠키로 — "어떻게든 숫자를 가져온다"는 견고함이 이 앱의 설계 철학이다.

메뉴바 만들기 — NSStatusItem

메뉴바 아이콘은 StatusItemController(@MainActor final class … : NSObject, NSMenuDelegate)가 관리한다. 프로바이더가 여러 개면 statusItems: [UsageProvider: NSStatusItem]처럼 아이콘도 여러 개 만든다. 실제 생성 코드는 이렇게 생겼다:

private static func makeStatusItem(
    statusBar: NSStatusBar,
    identity: StatusItemIdentity,
    defaults: UserDefaults,
    legacyDefaultItemIndex: Int?)
    -> NSStatusItem
{
    let item = statusBar.statusItem(withLength: NSStatusItem.variableLength)
    item.autosaveName = identity.autosaveName          // 위치 기억
    if let button = item.button {
        button.imageScaling = .scaleNone               // 1:1 렌더(선명한 템플릿 아이콘)
        button.setAccessibilityIdentifier(identity.accessibilityIdentifier)
        button.toolTip = title
    }
    return item
}

autosaveName으로 사용자가 옮긴 아이콘 위치를 기억하고, imageScaling = .scaleNone으로 리샘플링 없이 선명하게 그린다. StatusItemController는 메뉴·애니메이션·스위처 등으로 나뉘어 약 120개의 StatusItemController+*.swift 확장 파일로 쪼개져 있다(한 파일이 74KB에 이르기도 한다). macOS Tahoe가 NSStatusItem.isVisible 동작을 바꿔도 살아남도록 방어 코드까지 들어 있다.

Codex 사용량 가져오기 — OAuth HTTP

가장 기본 경로는 ~/.codex/auth.json의 OAuth 토큰을 읽어, ChatGPT 백엔드의 사용량 엔드포인트를 직접 부르는 것이다:

public static func fetchUsage(accessToken: String, accountId: String?, …)
    async throws -> CodexUsageResponse
{
    var request = URLRequest(url: Self.resolveUsageURL(env: env))
    request.httpMethod = "GET"
    request.timeoutInterval = 30
    request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
    request.setValue("application/json", forHTTPHeaderField: "Accept")
    if let accountId, !accountId.isEmpty {
        request.setValue(accountId, forHTTPHeaderField: "ChatGPT-Account-Id")
    }
    let response = try await ProviderHTTPClient.shared.response(for: request)
    switch response.statusCode {
    case 200...299: return try JSONDecoder().decode(CodexUsageResponse.self, …)
    case 401, 403: throw CodexOAuthFetchError.unauthorized
    …
    }
}

엔드포인트는 GET https://chatgpt.com/backend-api/wham/usage, 헤더는 Authorization: Bearer <token>. 응답의 primary_window는 세션 레인, secondary_window는 주간 레인으로 매핑된다. OAuth가 막히면 codex -s read-only -a untrusted app-server를 띄워 JSON-RPC(stdin/stdout)account/rateLimits/read를 부르는 CLI 경로로 폴백한다.

Claude 사용량 가져오기 — 로컬 JSONL 파싱

라이브 한도와 별개로, 비용 스캐너는 ~/.claude/projects/**/*.jsonl 로그를 직접 읽어 토큰을 센다. 핵심 파싱은 이렇게 단순하다:

guard let message = obj["message"] as? [String: Any] else { return }
guard let model   = message["model"] as? String else { return }
guard let usage   = message["usage"] as? [String: Any] else { return }

let input       = max(0, toInt(usage["input_tokens"]))
let cacheCreate = max(0, toInt(usage["cache_creation_input_tokens"]))
let cacheRead   = max(0, toInt(usage["cache_read_input_tokens"]))
let output      = max(0, toInt(usage["output_tokens"]))

let cost = CostUsagePricing.claudeCostUSD(          // 토큰 × 모델별 단가
    model: model, inputTokens: input,
    cacheReadInputTokens: cacheRead,
    cacheCreationInputTokens: cacheCreate,
    outputTokens: output, pricingDate: timestamp, …)

영리한 부분은 파일을 통째로 메모리에 올리지 않는다는 것. CostUsageJsonl이 로그를 256KB 청크로 스트리밍하며 개행(0x0A)으로 잘라 한 줄씩 넘기고, 바이트 오프셋을 기억(resume)해 다음번엔 새로 추가된 바이트만 읽는다. 스트리밍 중복 청크는 message.id + requestIddedup한다. 거대한 히스토리도 값싸게 훑는 비결이다.

동시성 — Swift 6 엄격 모드를 실전 규모로

중앙 상태는 @MainActor @Observable final class UsageStore 하나다. UI가 건드리는 상태는 전부 메인 액터에 묶여 데이터 레이스가 원천 차단된다. 반대로 무거운 외부 I/O는 전용 actor로 분리한다 — actor CodexCLISession, actor ClaudeCLISession, actor ClaudeWebBrowserFetchGate 등. 소스 전체에 async1,103회, @MainActor700회 등장한다.

@MainActor
@Observable
final class UsageStore {
    var isRefreshing = false        // 단일화(single-flight) 가드
    …
    func refresh(forceTokenUsage: Bool = false) async { … }
}
설계 포인트
"순수 함수로 뽑아낸 새로고침 정책"

AdaptiveRefreshPolicy는 시계나 ProcessInfo스스로 읽지 않는 순수 함수다. "메뉴를 최근에 열었나 + 전원/발열 상태"를 인자로 받아 2~30분 사이의 다음 새로고침 간격을 반환만 한다. 부수효과(시계 읽기)를 밖으로 밀어내면 테스트가 쉬워진다 — 클록을 목킹할 필요 없이 입력만 바꿔 검증한다.

5디렉토리 구조 해부

784개 파일이 어디에 어떻게 놓였나.

CodexBar/ ├── Package.swift, Package.resolved # SPM 매니페스트 + 잠금 ├── version.env → MARKETING_VERSION=0.41.0 / BUILD_NUMBER=100 ├── README.md, CHANGELOG.md (161KB), LICENSE, VISION.md, AGENTS.md ├── Makefile, appcast.xml (Sparkle), Icon.icns ├── .swiftformat, .swiftlint.yml ├── .github/workflows/ → ci.yml, release-cli.yml, upstream-monitor.yml ├── .agents/skills/ → codexbar, release-codexbar, qa-test (에이전트 스킬) ├── Scripts/ → package_app.sh, compile_and_run.sh, sign-and-notarize.sh … ├── docs/ → architecture.md, codex.md, claude.md, refresh-loop.md + 프로바이더별 ~50개 ├── WidgetExtension/ → CodexBarWidgetExtension.xcodeproj ├── Tests/CodexBarTests/ + TestsLinux/ └── Sources/ ├── CodexBar/ (195 파일) # UI + UsageStore/SettingsStore/StatusItemController ├── CodexBarCore/ (~350 파일) # fetch+parse 엔진 │ ├── Providers/<약 57개 프로바이더>/ # Codex, Claude, Cursor, Copilot, Bedrock … │ ├── Vendored/CostUsage/ # ccusage식 로컬 비용 스캐너 │ ├── OpenAIWeb/, WebKit/, Config/, Logging/, Host/ ├── CodexBarCLI/ (24 파일) ├── CodexBarWidget/ (6 파일) ├── CodexBarClaudeWatchdog/ └── CodexBarClaudeWebProbe/

꼭 알아둘 핵심 파일

파일크기역할
CodexbarApp.swift23KB@main SwiftUI 앱 + AppDelegate 배선(상태 컨트롤러·Sparkle·알림)
StatusItemController.swift41KB메뉴바 UI 본체(+ 확장 ~120개). 프로바이더별 아이템·메뉴·애니메이션
UsageStore.swift80KB@MainActor @Observable 중앙 상태 + 새로고침 오케스트레이션(+ 확장 ~35개)
IconRenderer.swift48KB메뉴바의 동적 사용량 게이지 아이콘을 직접 드로잉
Vendored/CostUsage/CostUsageScanner.swift115KBJSONL 로그 스캐너 코어(비용 계산의 심장)
CostUsagePricing.swift29KB모델별 토큰 단가표(Codex + Claude), 티어·캐시 단가 분리
Providers/Claude/ClaudeUsageFetcher.swift67KBClaude 사용량을 모든 소스에서 가져오는 총괄
Providers/Claude/ClaudeStatusProbe.swift45KBclaude CLI의 /usage·/status 출력을 ANSI 제거 후 파싱

디렉토리만 봐도 설계 의도가 보인다. 한 프로바이더 = 한 폴더(Providers/Codex/, Providers/Claude/…)로 격리돼 있어, 새 도구를 추가할 때 다른 코드를 건드리지 않는다. 문서(docs/)도 프로바이더별로 약 50개가 따로 있다.

6학습 포인트 (기술별)

이 코드베이스에서 무엇을 훔쳐 배울 수 있나.

① 네이티브 macOS 메뉴바 앱 설계

NSStatusItem(variableLength·autosaveName·템플릿 아이콘 1:1 렌더), Dock 없는 에이전트 앱(LSUIElement), 그리고 AppKit 메뉴바 + SwiftUI 설정 화면을 한 앱에 섞는 하이브리드 패턴. SwiftUI Settings 씬을 살리려고 숨겨진 1×1 WindowGroup으로 생명주기를 유지하는 트릭까지 배운다.

② Swift 6 엄격 동시성 — 대규모 적용 사례

교과서 예제가 아니라 18만 줄 규모에서 어떻게 액터를 나누는가를 본다. UI 상태는 @MainActor로 모으고, 외부 프로세스/네트워크 I/O는 전용 actor로 격리하며, 새로고침은 단일화 가드로 한 번에 하나만 돈다. "부수효과를 순수 함수 밖으로 밀어내는" 테스트 친화 설계도 실물로 확인한다.

③ 로컬 텔레메트리 파싱 — 값싸게, 증분으로

대용량 *.jsonl을 청크 스트리밍으로 읽고, 바이트 오프셋 resume으로 새 데이터만 처리하며, message.id 기반으로 중복을 제거하고, 모델별 단가표로 토큰을 달러로 환산한다. ccusage의 비용 모델을 Swift로 재구현한 깔끔한 표본이다.

④ 외부 CLI를 프로그램으로 몰기

두 가지 상반된 방식을 한 레포에서 본다 — (a) codex app-serverJSON-RPC over stdin/stdout으로 구동(구조적 프로토콜), (b) 대화형 TUI인 claudePTY(가상 터미널)로 자동화하고 /usage를 쳐서 나온 화면을 ANSI 코드 제거 후 파싱(비구조적 출력 긁기). 타임아웃·자식 프로세스 강제 종료 같은 안전장치도 포함이다.

⑤ 비밀번호 없이 인증하기

OAuth 토큰을 디스크에서 재사용하고, 브라우저 쿠키를 SweetCookieKit + swift-crypto + 키체인으로 복호화하며(Chromium "Safe Storage"), Copilot은 디바이스 플로우를 쓴다. 소스가 막히면 다음 소스로 우아하게 폴백하는 체인 설계가 핵심이다.

⑥ 제품 수준 배포 파이프라인

SPM을 배포 가능한 .app의 주 빌드 시스템으로 쓰고, Sparkle의 EdDSA 서명 + appcast로 자동업데이트하며, WidgetKit 위젯은 앱 그룹 JSON 스냅샷으로 데이터를 넘긴다. macOS·Linux를 아우르는 CLI 릴리스 매트릭스, SwiftFormat/SwiftLint, 21개 언어 현지화까지 — "1인 개발자가 어디까지 자동화하나"를 본다.

7하드웨어/시스템 요구사항

돌리려면 / 빌드하려면 무엇이 필요한가.

구분요구사항메모
실행 OSmacOS 14 (Sonoma) 이상Info.plist LSMinimumSystemVersion 14.0, LSUIElement true
빌드 도구Swift 6.2+ / Xcode 26.xCI는 macos-15-intel + Xcode 26.3/26.2
메모리·CPU사실상 무관(가벼움)메뉴바 앱 — 무거운 모델을 로컬에서 돌리지 않는다. 주기적 HTTP·파일 읽기가 전부
디스크 권한전체 디스크 접근(선택)Safari 쿠키(Cookies.binarycookies)를 읽을 때만 필요. 없어도 대부분 동작
키체인접근 프롬프트 발생Chromium 쿠키 복호화·Claude OAuth 부트스트랩 시. "키체인 접근 끄기" 탈출구 제공
설치(사용자)Homebrew / Releasesbrew install --cask codexbar, Linux CLI는 AUR codexbar-cli
주의
이 앱은 "내 자격증명"을 읽는다 — 그래서 권한을 묻는다

사용량을 로그인 없이 가져오는 대가로, CodexBar는 OAuth 토큰 파일·브라우저 쿠키·키체인에 접근한다. README는 파일시스템 전체를 뒤지지 않고 "알려진 소수 경로만", 그것도 해당 기능을 켰을 때만 읽는다고 명시한다. 그래도 민감한 접근인 만큼, 권한 프롬프트가 뜨는 이유를 이해하고 허용 여부를 스스로 판단하는 게 좋다. Screen Recording·손쉬운 사용(Accessibility) 권한은 쓰지 않는다.

8직접 해볼 수 있는 실습 과제

읽기만 하면 안 남는다 — 난이도별로 손을 움직여 보자.

과제 ① 난이도: 입문

소스에서 빌드해 실행하기

레포를 클론하고 ./Scripts/package_app.sh를 돌려 CodexBar.app을 즉석 서명으로 만든 뒤 open CodexBar.app으로 띄운다. 개발 루프는 ./Scripts/compile_and_run.sh, 스타일 검사는 make check. 목표: "SPM만으로 배포용 .app이 나온다"는 감각 잡기.

과제 ② 난이도: 초급

내 Codex/Claude 로그로 토큰 직접 세기

파이썬 20줄로 ~/.codex/sessions/**/*.jsonl(또는 ~/.claude/projects/**/*.jsonl)을 한 줄씩 읽어 usage.input_tokens·output_tokens를 합산해 본다. 목표: CodexBar 비용 스캐너가 "무슨 데이터를 어디서 읽는지"를 몸으로 이해. 스트리밍으로 읽어 큰 파일도 메모리 폭발 없이 처리해 보기.

과제 ③ 난이도: 중급

최소 메뉴바 앱 만들기 (Swift)

빈 SwiftUI 앱에 LSUIElement를 켜고, AppDelegate에서 NSStatusBar.system.statusItem(withLength:)로 아이콘 하나를 띄운 뒤 클릭하면 NSMenu가 열리게 한다. 목표: CodexBar가 하는 일의 뼈대(NSStatusItem + 메뉴)를 60줄로 재현.

과제 ④ 난이도: 고급

바이트 오프셋 resume 스캐너 구현

과제 ②를 발전시켜, 마지막으로 읽은 바이트 위치를 저장해 두고 다음 실행 때 그 지점부터만 읽는 증분 스캐너를 만든다. 로그가 append-only라는 성질을 이용한다. 목표: CostUsageJsonl의 resume 아이디어 재현 — "다시 스캔이 아니라 이어 스캔".

과제 ⑤ 난이도: 심화

새 프로바이더 추가하기

Sources/CodexBarCore/Providers/의 기존 폴더(예: 단순한 API 키 방식 프로바이더) 하나를 골라 구조를 익힌 뒤, 가상의 새 도구용 프로바이더를 스캐폴딩한다. 데이터 소스 열거형·페처·매핑을 채워 본다. 목표: "한 프로바이더 = 한 폴더" 격리 설계가 왜 확장에 강한지 체득.

9관련 기술 심화 학습 로드맵

이 레포를 온전히 읽으려면 — 5주 코스.

주차주제무엇을 / 왜
1주차Swift + SwiftUI 기본옵셔널·프로토콜·Codable·@main 앱 구조. CodexBar의 UI/설정을 읽는 토대
2주차AppKit 메뉴바NSStatusItem·NSMenu·템플릿 이미지·LSUIElement. 이 앱의 표면을 이루는 기술
3주차Swift 동시성async/await·Task·actor·@MainActor·데이터 레이스. 이 코드의 뼈대
4주차프로세스·파싱Process/PTY로 CLI 구동, JSON-RPC, JSONL 스트리밍, ANSI 제거. 데이터 수집의 핵심
5주차배포·서명SPM로 .app 패키징, 코드사이닝/노터라이즈, Sparkle appcast(EdDSA). "제품화" 마지막 마일
학습 순서 팁

거꾸로 읽지 말자. 2주차(메뉴바)로 "화면에 뭔가 띄우는" 성취감을 먼저 얻고, 3주차(동시성)에서 "왜 @MainActor가 사방에 붙나"를 이해한 다음, 4주차에서 진짜 어려운 "외부 CLI 긁기"로 들어가는 게 좌절이 적다.

10핵심 키워드 사전

문서에 나온 용어를 한 번에.

용어
NSStatusItem / 메뉴바 앱
macOS 화면 우상단 메뉴바에 아이콘을 올리는 AppKit 객체. CodexBar의 모든 표시가 여기서 그려진다.
용어
LSUIElement
Info.plist 플래그. 켜면 Dock 아이콘·앱 전환기에서 사라지고 메뉴바에만 존재하는 "액세서리 앱"이 된다.
용어
actor / @MainActor
Swift 동시성의 격리 단위. actor는 내부 상태를 한 번에 하나의 태스크만 만지게 강제하고, @MainActor는 "메인 스레드에서만"을 뜻한다. 데이터 레이스를 컴파일 단계에서 막는다.
용어
@Observable
SwiftUI가 상태 변화를 자동 추적하게 하는 매크로. UsageStore가 바뀌면 UI가 알아서 다시 그려진다.
용어
SPM (Swift Package Manager)
애플 공식 빌드·의존성 도구. CodexBar는 .xcodeproj 없이 Package.swift만으로 앱·CLI·위젯을 모두 빌드한다.
용어
Sparkle / appcast / EdDSA
macOS 앱 자동업데이트의 사실상 표준. appcast.xml이 새 버전 목록을, EdDSA 서명이 위변조 방지를 담당한다.
용어
JSONL
"한 줄에 JSON 하나"인 로그 형식. append-only라 뒤에 계속 붙는다 → 바이트 오프셋으로 증분 읽기가 쉽다.
용어
PTY (가상 터미널)
진짜 터미널인 척 만드는 가짜 터미널. claude 같은 대화형 TUI를 프로그램이 "사람처럼" 조종하려면 필요하다.
용어
JSON-RPC
JSON으로 함수 호출을 주고받는 규약. CodexBar는 codex app-server와 stdin/stdout으로 이걸 주고받아 한도를 읽는다.
용어
ccusage
Claude Code 사용량·비용을 로컬 로그로 계산하는 인기 도구(MIT). CodexBar의 비용 스캐너가 여기서 영감을 받았다.
용어
single-flight(단일화)
"이미 도는 작업이 있으면 새로 시작하지 않고 합류"하는 패턴. isRefreshing 가드로 새로고침이 겹쳐 도는 낭비를 막는다.

11참고 링크

더 깊이 파고들 곳.