최근 Claude Code를 터미널 에이전트로 자주 사용하며 한 가지 불편함을 느꼈다. 클로드는 AI 에이전트 중에서도 유독 사용량 제한이 빡빡하기로 유명하다. 그러나 사용량 한도를 확인하기 위한 별도의 알림이나 위젯이 제공되지 않았고, 매번 다 쓰고나서야 깨닫거나 웹사이트의 설정 페이지에서 사용량을 일일이 확인해야 했다.
취미로 하는 바이브 코딩도, 또는 녹을 받으며 하는 개발에서도 AI가 필수적인 시대에서, 적재적소에 사용해야 할 에이전트가 아예 불능이 되는 것은 꽤 스트레스 받는 일이다.
우아한테크코스 프리코스를 진행하며, 4주차 과제는 접해보지 않은 언어나 환경으로 개발에 참여하는 '낯선 도구 해커톤'을 시작하게 되었다. 이번 계기로 생에 처음 접해보는 macOS 유틸리티 개발과 그 기록을 이 글에 담아보았다.
프로젝트 구조와 MVC 패턴
이전 과제에서 학습한 MVC(Model-View-Controller) 패턴을 이번 macOS 앱 개발에 적용했다. SwiftUI는 MVVM 패턴과 자주 사용되지만, 데이터 흐름을 명확히 하고 역할을 분리하기 위해 MVC 패턴을 선택했다.
프로젝트의 디렉터리 구조는 다음과 같다.
ClaudeMeter
├── ClaudeMeterApp.swift // 앱 진입점
├── Model // 데이터 구조 정의
│ ├── UsageModel.swift
│ ├── UsageAPIResponse.swift
│ └── PreferenceModel.swift
├── View // UI 구성
│ ├── MenuBarView.swift
│ └── Settings
│ └── SettingsView.swift
├── Controller // 비즈니스 로직 조정
│ └── UsageController.swift
└── Service // 기능 구현
├── APIService.swift
├── UsageEstimator.swift
└── IconRenderer.swift각 계층의 역할은 다음과 같이 정의했다.
- Model:
UsageModel.swift등에서 데이터 형태를 정의한다. API 응답값과 앱 내부에서 사용할 데이터 구조를 분리하여 관리했다. - View:
MenuBarView.swift는 사용자 인터페이스를 담당한다. 로직을 포함하지 않고, 데이터 상태에 따라 화면을 렌더링 하는 역할만 수행한다. - Controller:
UsageController.swift는 앱의 중심을 잡는 제어 장치에 해당한다. Model과 View 사이에서 데이터를 전달하고 갱신 시점을 결정한다.
Service 계층의 분리
MVC 패턴 적용 시 Controller가 비대해지는 문제(Massive View Controller)를 방지하고자 구체적인 기능 구현부는 Service 디렉터리로 분리했다.
- APIService.swift: 네트워크 통신을 전담한다.
- IconRenderer.swift: 사용량에 따라 변하는 동적 메뉴 바 아이콘을 생성한다.
- UsageEstimator.swift: 사용량 데이터를 기반으로 연산 로직을 수행한다.
Controller는 Service 모듈을 소유하고 적절한 시점에 호출하는 관리자 역할에 집중하도록 설계했다.
핵심 로직: 동적 폴링(Dynamic Polling)
UsageController는 효율적인 자원 관리를 위해 동적 폴링 방식을 채택했다. 사용자가 활발히 활동할 때는 갱신 주기를 짧게 설정하고, 변화가 없을 때는 주기를 늘려 불필요한 네트워크 요청을 줄였다.
// 사용량이 변하면 → 1분마다 체크
// 사용량이 안 변하면 → 점진적으로 간격을 늘려 최대 10분마다 체크
if hasChanged {
currentInterval = minInterval // 1분
} else {
currentInterval = min(currentInterval * 1.5, maxInterval) // 최대 10분
}이러한 구조를 통해 UI 코드는 간결해졌고, 비즈니스 로직은 파일별로 분리되어 유지보수가 용이해졌다.
프로그램 UI/UX
내부 코드만큼이나 중요한 것은 사용자가 마주하는 인터페이스다. 특히 메뉴 바 앱은 작은 공간 내에서 정보를 효율적으로 전달해야 한다고 생각했다.
아이콘 디자인과 가시성
macOS 개발 시 Apple이 제공하는 SF Symbols를 활용하면 미리 정의된 아이콘을 이용할 수 있고, 개발 시간을 단축할 수 있다.
하지만 단순히 배터리 아이콘이나 숫자를 띄우는 것만으로는 부족하다고 판단했다. 사용자가 텍스트를 읽지 않아도 다른 메뉴바 아이콘들과 즉시 구분할 수 있어야 하며, 직관적으로 '클로드'임을 인지할 수 있어야 했기 때문이다.
초기에는 한자 '삼(三)'자나 안테나 모양, 원형 그래프 등 다양한 시안을 고려했으나, 최종적으로 Claude의 머리글자인 'C' 형태와 자동차 계기판(Tachometer)을 결합한 디자인을 채택했다. 이를 통해 사용자는 게이지가 차오르는 시각적 형태만으로 현재 상태를 파악할 수 있다.
사용량 경고와 시각적 피드백
사용자가 계획적으로 AI 에이전트를 활용할 수 있도록 경고 시스템을 구현했다. 수치 확인뿐만 아니라 사용자가 설정한 임계치에 도달했을 때 알림/뱃지 등으로 피드백을 제공하도록 했다.
- 경고(Warning): 사용자가 지정한 수치(예: 90%)에 도달하면 아이콘 우측 하단에 주의 배지가 표시되고 알림이 발송된다.
- 고갈(Depleted): 사용량이 100%에 도달하면 자물쇠 배지가 표시되어 현재 사용이 불가능함을 알린다.
실질적인 정보: 예상 고갈 시점
메뉴바에 표시되는 텍스트 정보는 '남은 시간', '사용량(%)', 혹은 '둘 다' 표시하도록 선택할 수 있다.
이 중 '남은 시간'은 특정 조건이 충족되면 "앞으로 얼마나 더 작업할 수 있는가"를 보여준다. 최근 사용량 데이터 포인트를 바탕으로 선형 회귀(Linear Regression) 알고리즘을 적용하여 세션 고갈 시점을 예측하도록 구현했다.
이 예측 알고리즘이 어떻게 작동하는지에 대한 구체적인 로직은 다음 포스팅에서 자세히 다루려고 한다.