Docs / Architecture
Architecture
A native macOS menu bar app (Swift 6, SwiftUI) that reads what Claude Code already reports about itself, on your Mac. It works fully without an account; signing in is optional and uploads daily counts only (see Privacy).
On this page
A reading's path
Six layers, bottom to top. Point at a step to light its layer.
- 01Claude Code · Status lineRuns your status line command on every refresh and hands it a JSON description of the session.
- 02Helper · statusline.sh · clepso-statuslinePasses the input to your own status line unchanged, keeps only allowlisted fields, sends them on.
- 03Ingest · Socket · IngestorLocal socket, peer checks and a rate limit; each frame becomes readings with source and freshness.
- 04Store · SQLite · UsageEngineOne transaction per frame: insert the reading, work out each limit's state, keep the history.
- 05Show · Menu bar · DashboardRenders snapshots it is handed and never computes a state itself.
- 06Sync · Optional, if you sign inUploads daily counts once an hour; the only module allowed to use the network.
Processes
| Process | Role |
|---|---|
Clepso.app | The menu bar app. Listens on a local socket, keeps readings in a local database, works out each limit's state and shows it. If you sign in, it uploads daily counts once an hour. |
clepso-statusline | A small signed helper inside the app that Claude Code runs on every status refresh. It passes the input to your own status line unchanged, keeps only allowlisted fields and sends them to the app. It links no networking at all. |
hook.sh (optional) | Installed only when you turn on agents per task. Claude Code runs it in the background on four events; it runs the helper in hook mode, which sends counts only and always exits silently, so it can never block or change what Claude Code does. |
statusline.sh | The installed command. Runs the helper if the app is there, otherwise your original status line, so removing the app never breaks Claude Code. |
Data flow on the Mac
Claude Code ──stdin JSON──► statusline.sh ──exec──► clepso-statusline
│ allowlist (ShimProtocol) │ raw bytes
├──► frame ──► run/agent.sock ▼
│ /bin/sh -c <original command>
└──◄ stdout + exit status ◄─────────┘
│
▼ (verbatim to Claude Code)
Clepso.app
IngestSocket (actor) ──AsyncStream<ShimFrame>──► Ingestor (actor)
│ accept, peer checks, rate limit, decode │ parse, HMAC session id, Observation
▼ ▼
connector_health Store (actor, one transaction per frame)
insert observation
reduce (UsageEngine) ──► pool_snapshots, events
Scheduler.candidates(events) ──► notification_receipts
│ │
ValueObservation ▼ ▼
OverviewSnapshot ──► @MainActor view models NotificationDispatcher ──► UNUserNotificationCenter
MenuBarExtra popover · provider detail · provenance inspector · settings
Modules
| Module | Responsibility |
|---|---|
Domain | Values that carry where they came from and how fresh they are, so a missing number can never show as 0%. |
ShimProtocol · StatusLineShim | The frame format and the allowlist; the helper's logic. |
ClaudeCodeAdapter | Socket ingest, mapping frames to readings, the settings and hooks installers, the statistics-file reader. |
UsageEngine | Pure rules: limit states, windows, freshness, the pace estimate. |
Storage | SQLite (GRDB): one writer, a ledger of readings, migrations with a backup first, retention. |
Sync | Optional sign-in by email link and the hourly upload of daily counts. The only module that may use the network, and only the app imports it. |
DesignSystem | Tokens, themes (Phosphor dark and light), pixel glyphs and components, accessibility formatting. |
ClepsoApp | The app: menu bar panel, Dashboard (Home, Limits, Session, Stats, Goals, Account, Settings), onboarding, diagnostics. |
SubscriptionCatalog | Plan names, never used to infer usage. |
Principles
Local first
Everything works on your Mac without an account. Network code lives only in Sync, used only after you sign in.
Allowlist at the source
The helper drops everything not on the allowlist before sending, so the app cannot receive what it must not keep.
Absent is not zero
A limit the source did not report is shown in words with an empty outlined bar, never as 0%.
Reversible
Installing into Claude Code backs up your settings; uninstalling and delete-all restore them byte for byte.
Quality gates
One command, make ci, runs every gate and must pass before each increment is committed. The list below is read from scripts/ci.sh.
| Gate | What it checks |
|---|---|
| Toolchain pinned | The expected Swift version. |
| Strict build (warnings=errors) | Warnings are errors; Swift 6 strict concurrency. |
| Tests | The full suite (Swift Testing). |
| Helper links no networking | The helper binary links no networking frameworks. |
| Release build has no debug code | |
| Networking only in Sync | Networking APIs appear only in the Sync module, and only the app imports it. |
| Clock injected | Logic reads time through an injected clock, so tests control it. |
| Design tokens only | Views use theme tokens and pixel glyphs, never literal colours, sizes or system symbols. |
| Motion through tokens | |
| Keyboard reachable | |
| Redaction audit | Logs and exports contain no fields outside the allowlist. |
| Secret scan | No credentials in the repository. |
| No committed accounts project | |
| Email templates | |
| App bundle | Signature, embedded helper, entitlements, URL scheme. |
Relevant source files
scripts/ci.shdocs/ARCHITECTURE.md