Phased checklist. Work top-to-bottom within each phase; check items off as they land. Stack reference:
.agent/context/stack.md· Architecture:docs/architecture.md
- Create
Cargo.tomlworkspace at repo root with members:crates/aura,crates/aura-core,plugins/rtk-gains -
crates/aura-core— library crate (data layer, plugin runner, config, state); no UI deps -
crates/aura— binary crate (GPUI app, tray icon, modal); depends onaura-core -
plugins/rtk-gains— standalone binary crate (the RTK Gains plugin) - Add
.rustfmt.toml(defaults) andclippy.toml - Add
.github/workflows/ci.yml:cargo fmt --check,cargo clippy -- -D warnings,cargo test --workspace - Add
.gitignore(standard Rust +target/) - Smoke-test:
cargo build --workspacepasses
Crate: aura-core
- Define
AgentConfigandAgentKind(enum:ClaudeCode,Codex) inconfig.rs - Define
PluginConfig(name + command path) inconfig.rs - Define
AppConfig(agents list, plugins list, display defaults) — deserialize from TOML viaserde+toml - Implement
AppConfig::load(path)— reads~/.config/aura/config.toml; creates default file on first run - Write default config template (two Claude Code profiles + RTK plugin entry)
- Define
AppState(active profile name) instate.rs - Implement
AppState::load()/AppState::save()— reads/writes~/.local/share/aura/state.jsonviaserde_json - Unit tests: round-trip serialize/deserialize for both config and state
- Smoke-test: binary loads config and prints active profile
Crate: aura-core — mirrors the ML8 / AT5 / Wp6 logic from claude /usage
- Define
UsageSnapshotstruct:input_tokens,output_tokens,cache_read_tokens,cache_write_tokens,sessions,active_days,longest_session_secs,current_streak,longest_streak,peak_hour,favorite_model,per_model: Vec<ModelUsage>,daily_tokens: Vec<DailyModelTokens>— all fields needed to render Overview + Models panels - Define
Periodenum:AllTime,Last7Days,Last30Days - Implement
list_session_files(config_path)—readdironprojects/subdirectories, collect all*.jsonlpaths includingsubagents/subdirs - Implement
scan_jsonl_files(files, from_date, to_date)→ raw aggregated data:- Skip file by
mtime < from_date(cheap OS check before opening) - Parse each
assistantentry:timestamp,message.model,message.usage.* - Skip synthetic models (model name
<synthetic>) - Accumulate per-model:
inputTokens,outputTokens,cacheReadTokens,cacheWriteTokens dailyModelTokens[date][model]+=input + outputonly (matches/usage)- Track session stats: start/end timestamps per file → duration, message count
- Track
hourCountsfrom session start hours
- Skip file by
- Implement streak computation from daily activity dates (matches
WM4logic) - Implement
ClaudeCodeReader::snapshot(config, period):Last7Days/Last30Days: callscan_jsonl_fileswith date range; returnUsageSnapshotfrom resultsAllTime: loadstats-cache.jsonas baseline, find JSONL files newer thanlastComputedDate, merge delta, append today's JSONL on top
- Define
AgentReadertrait withfn snapshot(&self, period: Period) -> Result<UsageSnapshot> - Implement
AgentReaderforClaudeCodeReader - Unit tests:
- Fixture JSONL files covering multi-model, multi-day, cache tokens, synthetic model filtering
- Assert
snapshot(Last7Days)totals match hand-computed values - Assert
AllTimemerges cache + delta correctly - Assert streak computation for gaps, current streak reaching today
- Integration smoke-test: run against real
~/.claude/projects/— outputs 119,845 tokens / 5 sessions / 2 active days / favorite model claude-opus-4-7
Crate: aura-core
- Add
notify-debouncer-mini = "0.7"dependency (pairs with notify 8.2; stable; built-in debouncing) - Implement
ProjectsWatcherwrappingnotify::RecommendedWatcheron{config_path}/projects/ - Expose channel via
try_recv()/recv_timeout()returningVec<PathBuf>of changed*.jsonlfiles (non-JSONL events filtered out) - Implement
read_jsonl_since(path, offset): returns parsed new entries + new byte offset; respects partial trailing lines mid-write - Deferred to Phase 6 —
UsageSnapshotmerge logic lives with the UI consumer; for now the UI callssnapshot()after watcher events (mtime fast-skip already keeps this cheap) - Debounce: 500ms quiet-window coalescing handled by
notify-debouncer-mini - Unit tests: file create fires event, non-JSONL ignored, rapid writes coalesced,
read_jsonl_sincebyte-offset correctness incl. partial trailing line
Crate: aura-core
- Define
PluginPanelstruct:title: String,lines: Vec<PluginLine>,error: Option<String> - Define
PluginLine:label: String,value: String,highlight: bool - Implement
PluginRunner::run(config: &PluginConfig) -> PluginPanel:- Spawn subprocess via
std::process::Command - 500ms timeout via
std::thread+mpsc::recv_timeout - Read stdout, parse JSON into
PluginPanel - On non-zero exit, timeout, missing binary, or bad JSON: return
PluginPanelwitherrorset (never panics, never returns Err)
- Spawn subprocess via
- Unit tests: valid JSON, missing binary, timeout, bad JSON, non-zero exit (5 cases)
Plugin: plugins/rtk-gains
- Uses
rtk gain -a --format json(covers summary + daily + monthly in one call) - Outputs
PluginPanelJSON with: tokens saved today / this month / all-time, savings rate, commands intercepted - Graceful fallback if
rtkis missing or fails (emits error panel) - Unit tests:
format_thousandsformatting, realrtk gainJSON parsing
Crate: aura
- Add
gpui = "0.2"(crates.io release); builds on Linux X11/Wayland -
AuraViewholding:config,state,active_profile,active_period,active_tab,snapshot,plugin_panels,error - Borderless 520×640 window, dark background (Zed-ish palette), monospace font
- Profile picker — pills in the header row; click switches profile, persists to state, refreshes snapshot
- Period selector — three pills (All time / Last 7 days / Last 30 days); active pill in accent color
- Overview panel — 2-col grid of stat cards: Favorite model, Total tokens, Sessions, Longest session, Active days, Peak hour, Current/Longest streak
- Models panel — daily token bar chart + per-model rows with horizontal % bar
- Loading / error states — "Loading…" placeholder; red error message replaces body when snapshot fails
- Plugin panels — RTK gains rendered as title + key/value lines with highlight + error variant
- Tab switching (Overview ↔ Models) via accent-underlined header
- Click
Aura ⟳title to manually refresh - Active profile saved to state on selection change
- Deferred — wire
ProjectsWatcherto GPUI background executor for auto-refresh. Manual refresh via title click works today. - Manual test required — UI rendering can't be verified from headless CLI. Launch with
cargo run -p aurato confirm layout
Crate: aura
- Add
tray-icon = "0.24"withdefault-features = false, features = ["gtk"](dropslibxdosystem dep) - Programmatic 32×32 RGBA placeholder icon (purple/violet square); real SVG asset is a follow-up
- Tray installed at startup with tooltip "Aura — Agent Usage Reporter"; handle held by main fn so it persists for app lifetime
- Best-effort install: warns and continues if no tray host is available
- Deferred — dynamic tooltip ("Aura · profile · tokens today") and click-to-toggle-window. Requires bridging tray-icon's event channel into the GPUI main loop; left as a future iteration since main flow is "window opens on launch"
Crate: aura-core
- Stub
CodexReaderreturningUsageSnapshot::default() -
reader::make_reader(agent)factory dispatches onAgentKind(ClaudeCode → real, Codex → real) -
codex_scanmodule — walks{config_path}/sessions/{YYYY}/{MM}/{DD}/rollout-*.jsonl- Parses
session_meta(session id + start ts),turn_context(active model), andevent_msg/token_count(per-turnlast_token_usage) - Attributes each
last_token_usagedelta to the most-recentturn_context.model - Splits Codex's
input_tokens(which includes cached) intoinput_tokens = input − cachedandcache_read_tokens = cached;cache_write_tokens = 0(Codex has no separate creation bucket) - mtime-based fast skip for files outside the requested date window
- Parses
-
CodexReader::snapshotreusesclaude_code::build_snapshot(with no cache baseline) forLast7Days/Last30Days/AllTime - Default
AppConfigships aCodexprofile so it works out of the box - Unit tests: file discovery, single-session token attribution, mid-session model switch, pre-context token_count skipped, date-range filter, multi-session aggregation, empty dir
- Future — incremental scanning + watcher integration for live updates (today the UI scans on demand)
-
justfilewith targets:build,run,test,lint,install,install-plugin-rtk,uninstall,start/stop/status/logs - Systemd user unit
packaging/aura.service(graphical-session.target, restart-on-failure, inherits DISPLAY/WAYLAND_DISPLAY) -
install.sh— checks prereqs, builds release, installs binaries to~/.local/bin/, drops the systemd unit - Updated
README.mdInstallation section with system deps, one-shot install, manual install, and common commands - Manual test required — verify on a clean session:
./install.sh,systemctl --user enable --now aura, tray icon appears, app launches
- Cargo gating:
tray-iconfalls through to its Win32 backend on Windows (nogtkfeature needed); the existing "not Linux/BSD" target table covers this - Windows Credential Manager read/write for Claude Code OAuth (
Claude Code-credentials, user =%USERNAME%) viakeyringcrate (windows-nativebackend); falls back to.credentials.jsonfor legacy installs -
scripts/install.ps1— copies binaries to%LOCALAPPDATA%\Programs\Auraand drops a Startup-folderAura.lnkfor autostart -
install.shdetects MSYS/MinGW/Cygwin and points users atinstall.ps1 -
justfileaddsinstall-windows,uninstall-windows,start-windows,stop-windows,status-windows - Release CI matrix builds
x86_64-pc-windows-msvc(stable) andaarch64-pc-windows-msvc(experimental); Windows artifacts are zipped (not tarballed) - README documents Windows config path (
%APPDATA%\aura\config.toml), install flow, and Credential Manager caveat - Manual test required — first-launch verification on x86_64 + aarch64 Windows hosts; code-signing setup once a Windows Authenticode cert is available
- Cross-platform Cargo gating for
tray-icon(gtkfeature only on Linux/BSD) -
objc2-app-kitoverride ofNSApplicationActivationPolicy→Accessory(menu-bar app, no Dock icon) since GPUI hard-codesRegular - Keychain read/write for Claude Code OAuth credentials (
Claude Code-credentials, account =$USER) viasecurity-framework; falls back to.credentials.jsonfor legacy installs -
packaging/com.aura.agent-usage.plistLaunchAgent (RunAtLoad, KeepAlive on crash) -
packaging/macos/Info.plistwithLSUIElement=true -
scripts/build-macos-app.shassemblesAura.app(optional codesign viaCODESIGN_IDENTITY) -
install.shandjustfiledispatch onuname -s(Linux: systemd, macOS: launchd + .app) - Release CI builds and packages
Aura.app+ plist for both darwin targets;experimental: truedropped - README documents macOS install, config path, Gatekeeper quarantine, Keychain caveat
- Manual test required — first-launch verification on Intel + Apple Silicon, codesign / notarization setup once an Apple Developer ID is available
- Light theme
- Historical usage charts in the modal (beyond ASCII)
- Plugin authoring guide + example repo
- Plugin registry (
aura plugin install <name>) - Custom command agents (BYOA)
- macOS: Developer-ID codesign + notarization (needs Apple account)