feat: Insights, session-budget pacing, Fleet (cross-machine), and Activity tabs#33
Open
pehqge wants to merge 18 commits into
Open
feat: Insights, session-budget pacing, Fleet (cross-machine), and Activity tabs#33pehqge wants to merge 18 commits into
pehqge wants to merge 18 commits into
Conversation
Extend the Forecast tab with a session-budget gauge that recommends how much of the current 5h window a Max user may burn so their weekly window lands at/under 100% at reset — paced against a *learned* active-session pattern rather than the raw 5h renewal cadence. All math is in percentage of the weekly window (QuotaWindow.used_percentage, the API-reported utilization), so it never guesses an unknown token cap and stays correct across plan tiers. Core: - new quota/pacing.rs: SessionBudget, PacingStatus, ActivityPattern, SessionTokens, session_budget(), learn_pattern(), collect_session_tokens(). learn_pattern uses a trimmed mean of active-session counts over active days only; session_budget floors sessions_left so it never divides by ~0 and emits an Insufficient state (no number) on thin history / non-API source. - QuotaSnapshot gains an additive Option<ActivityPattern> pacing_pattern field (skipped from the wire format when absent). - [pacing] config section: enabled (default false), active_session_min_tokens (50_000), history_days (14), fully wired into config_schema + describe/wizard. - lexicon: pacing_* strings in both personas. UI: - app.rs refresh learns the pattern (Claude Code + Api source + enabled) on the existing ProjectsWatcher cadence — no new timer/thread — and render_forecast appends render_session_budget() below the projected windows, reusing the forecast card's bar/badge primitives and theme tokens only. F3-independent: base SessionStat has no per-session token total, so F2 does its own minimal token-per-session JSONL pass instead of depending on the F3 branch. See BUILD_NOTES.md for the merge-sequencing note. Tests are written, not run. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…badges) Light up the previously-dead SessionStat with a new opt-in Insights tab that answers the "interesting" questions: which project burned the most tokens, which single session was the most expensive (and what mode it ran in), and how token spend splits across model tiers. Core (aura-core): - scan.rs: extend SessionStat (session_id, project_dir, total_tokens, dominant_model, is_ultracode) + ScanAccum.tokens_by_project, all accumulated in the existing single scan pass — no second scan, no extra file I/O. Ultracode is a cheap byte-substring check (ULTRACODE_MARKERS) over the same buffer used for JSON parsing. Subagent tokens fold into their parent project. - reader/insights.rs (new): pure aggregations — top_projects, top_sessions, mode_distribution, ModelTier, plus serializable ProjectStat / SessionInsight / ModeDistribution / InsightsSnapshot. Wired onto UsageSnapshot so the existing All/7d/30d period plumbing is reused. - config.rs: [insights] section (enabled=false, top_n=5); tab hidden unless on. - config_schema.rs / cli: descriptors, get/set, commented template, describe. - lexicon: tab_insights (polite "Insights", goblin "Receipts"). UI (aura): - app.rs: Insights AgentSection variant + conditional tab + render_insights (top projects bars, top sessions with tier/ultracode badges, mode distribution bar + counts, heuristic footnote). Theme tokens only, no hardcoded colors. - format.rs: compact_tokens (18.2M / 9.1K). Ultracode detection is a documented heuristic (rustdoc + UI footnote). AllTime insights cover the delta scan only (StatsCache has no per-project breakdown). Tests written for: top-projects ranking + token sums, top-sessions ordering, ultracode detection (Workflow tool_use / literal / plain), dominant_model, mode_distribution percentages (sum to 100), per-project + subagent folding, compact_tokens, and [insights] config round-trip. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the Fleet feature: machines on the same Claude account pair once with a one-time code, then auto-sync encrypted usage heartbeats over the free public ntfy.sh pub/sub broker. A new Fleet tab shows each machine's share of the 5h and weekly rate-limit windows. The ntfy broker is treated as untrusted. Defense in depth: - Topic = "aura-" + base32(HMAC-SHA256(secret, "ntfy-topic"))[:16] — a high-entropy capability, not a guessable name. - Every payload sealed with XChaCha20-Poly1305 (random 24-byte nonce per message) under an HKDF-SHA256(secret, "aura-fleet-v1") key; the broker sees only ciphertext. - Forged/tampered messages fail AEAD auth and are ignored; replays older than 2x the heartbeat are dropped. - The 256-bit pairing secret lives only in the OS keychain (dedicated service "aura-fleet-secret", never the Claude Code entry), with a 0600 file fallback on headless Linux. Never in config.toml or logs. Per-machine share is computed from token counts (the account window %s are global), then attributed against the account window %. No Claude tokens or message content ever leave the machine. New module tree crates/aura-core/src/net/ (pairing, crypto, fleet, transport, secret_store, plus FleetSync background loop). All pure logic unit-tested (written, not run); transport tested via an in-memory mock; one #[ignore] live ntfy round-trip. [fleet] config section (off by default — the sync task never spawns when disabled). Fleet tab + pairing UI in app.rs. docs/fleet.md and BUILD_NOTES.md added. Crates added to aura-core: chacha20poly1305 0.10 (new), hkdf/hmac/sha2/base64/ rand/zeroize/uuid (already in the lock tree), keyring sync-secret-service on Linux. Pure-Rust, cross-compilable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts: # BUILD_NOTES.md # FEATURE_SPEC.md # crates/aura-core/src/config.rs # crates/aura-core/src/config_schema.rs # crates/aura/src/cli/config.rs
# Conflicts: # crates/aura-core/src/config.rs # crates/aura-core/src/config_schema.rs # crates/aura/src/app.rs
The session-budget gauge was wrong in two ways and just echoed the
weekly-remaining %.
BUG 1 (algebraic cancellation): learn_pattern set
avg_weekly_pct_per_active_session = 100/(active_per_day*7); session_budget
then divided weekly_remaining/sessions_left by it, cancelling active_per_day
so recommended_pct collapsed to ~weekly_remaining. The geometric anchor was a
no-op — deleted.
BUG 2 (impossible unit): the pattern counted active JSONL session *files* per
day, reaching ~47/week, but the 5h rate-limit window only renews ~5x/day. The
unit is now the 5h WINDOW: history is bucketed onto the live reset grid
(window_index = floor((ts - session.resets_at)/5h)); multiple sessions in one
window collapse to one. A window is active when its summed tokens clear
active_session_min_tokens. active_windows_per_day is the trimmed mean over
active days (naturally <= ~5).
Token caps are now inverted from real window boundaries instead of guessed:
weekly_cap = tokens_in(weekly_window_start, now) / (weekly_used%/100), same
for the 5h window, via a new reader helper sum_tokens_in_range (message-level,
reuses RawEntry parsing, skips subagents + mtime-prunes). A MIN_PCT_FOR_CAP
(3%) / zero-token guard returns Insufficient ("warming up") rather than divide
by ~0. budget_tokens = weekly_remaining_tokens / windows_left; recommended_pct
= budget_tokens / session_cap * 100. Caps are computed at the app refresh site
where reader + quota are both in scope (new pacing::compute_caps + Caps) and
threaded to the pure, unit-tested session_budget.
Tests rewritten for active-window trimmed mean, cap inversion + guard,
windows-left floor, sum_tokens_in_range, and the screenshot scenario
(weekly 15% used, ~30 windows left) now yielding a modest recommendation
instead of ~85%.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e cards
Insights tab rework:
- Resolve project names from the real per-entry `cwd` Claude Code writes
(basename, e.g. `reconhecimento`) instead of the lossy on-disk slug;
fall back to the trimmed slug when no `cwd` was logged. `cwd` is captured
from the first entry seen per session/project during the existing scan pass.
- Top Sessions: drop the misleading wall-clock duration (included idle gaps);
rows now show tokens · project · badges, still sorted by tokens.
- Remove the Mode Distribution card and `ModeDistribution` aggregation
(favorite model already lives in the Summary tab).
- Add an Ultracode ROI card: ultracode vs normal session counts + avg tokens
and a heavier-by-N× multiplier (guarded when there are no normal sessions).
- Add a Cache Efficiency card: read/write cache totals + hit ratio
("N% of context served from cache"), guarded when there's no activity.
Tests: cwd→basename resolution incl. slug fallback, ultracode ROI averages +
zero-normal guard, cache hit-ratio + zero guard. Fixtures now write `cwd`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add an opt-in "Activity" tab — a live process monitor scoped to Claude Code. It enumerates running processes, classifies the `claude` CLI roots (name == `claude`, `.exe`-tolerant; excludes the Claude.app desktop app and aura itself), walks each root's full descendant subtree (MCP servers, bash/build commands, sub-agents), and sums CPU% + RSS per session. Each session is labelled by its project (basename of the root's cwd) and the active `*.jsonl` session id under `~/.claude/projects/<slug>/`, where the slug replaces every `/` and `.` with `-`. Sorted by total CPU% desc, with the heaviest 1-3 child processes surfaced as readable culprit rows. Pure logic lives in `aura-core::activity` and is unit-tested headless against a synthetic process list + an injected tempdir (16 tests). The OS-touching `ActivityMonitor` owns a long-lived `sysinfo::System` so CPU% deltas compute across ticks; it's gated behind the default-on `activity` Cargo feature. sysinfo is pinned to 0.31 (reusing the version already in Cargo.lock) with `default-features = false, features = ["system"]` to stay lean. The GUI ticks the monitor only while the modal is open and the tab is active, mirroring the spinner-tick pattern (cx.spawn + background timer, self-rescheduling, gated on a generation token + active tab) — zero background cost otherwise. First sample shows "measuring…" until a CPU baseline exists. Config: new `[activity]` section (`enabled` default false, `refresh_secs` default 3, clamped to >=1). Tab hidden unless enabled and the agent is Claude Code. The tab ignores the period selector (it's live). Docs in docs/activity.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…(was rendered commented → never saved)
…closed Fleet only published heartbeats the UI pushed via push_fleet_heartbeat, which fires on a modal-open / file-watch refresh. With the modal closed nothing published, so peers never saw each other unless someone sat with the modal open — unacceptable for a passive monitor on an always-alive tray app. The sync loop now owns a heartbeat_source closure and builds a fresh heartbeat every cadence by fetching the local Claude quota itself, with no dependency on the UI. A UI-pushed publish_local still takes priority for an instant update but is no longer required for publishing. The self-built heartbeat is mirrored into FleetState as the local "self" row so the user's own row renders with the modal closed. The first heartbeat still fires immediately on spawn for instant pairing feedback. The publish-decision is factored into a pure next_outbound helper so the priority/cadence logic is unit-testable without a thread or transport. Raise MIN_HEARTBEAT_SECS 10 -> 20: the loop makes two broker requests per cadence (publish + poll); the public ntfy.sh broker tolerates ~1 req/10s, so a 20s floor keeps even an aggressive config at ~2 req/20s = 1 req/10s and avoids HTTP 429. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…y caching or they never receive heartbeats
FleetSync was owned by AuraView, which only exists while the modal window is open — so Fleet stopped publishing/polling the moment the modal closed, defeating the point of a passive cross-machine monitor. Move Fleet ownership to a process-level FleetManager held in the always-running tray spawn loop in main.rs (alongside the keepalive window), so the single sleeping sync thread runs for the whole process lifetime and is never dropped between modal opens. - aura-core/net: add `build_heartbeat`, the single source of truth for turning a QuotaSnapshot into a Heartbeat (shared session/weekly needle logic), fixing the inconsistency where some heartbeats carried pct and others tokens. Unit-tested for API + local-fallback labels and the no-window default. - runtime.rs: add shared Fleet handles — `fleet_state()` / `set_fleet_state` / `clear_fleet_state` (the modal reads peers from this) and a dirty signal (`mark_fleet_dirty` / `take_fleet_dirty`) the modal's Pair/Leave actions raise so the manager reconciles without a tray Show. - main.rs: FleetManager::reconcile (re)starts/stops the sync to match config (enabled && paired); driven at startup, on each dirty tick, and on every tray Show after the config reload. - app.rs: AuraView no longer owns Fleet — drop fleet_sync, the spawn/push helpers, and window_metrics; render_fleet reads peers from runtime::fleet_state(); Pair/Leave mutate the secret then mark dirty. No platform-specific code added in the refactor. Silences the macOS/ Windows dead-code warning on secret_store::fallback_path (Linux/other only) so a -D warnings build is clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rfluid
requested changes
Jun 6, 2026
Rfluid
left a comment
Owner
There was a problem hiding this comment.
The CI checks are failing. You can run scripts/pre-pr.sh locally to validate the changes before pushing. One caveat is that platform validation is not included in this script and must be verified separately.
I also think this PR could be split into smaller, more atomic PRs to make the review process easier. If you'd like to speed up the review and merge process, I'd recommend doing that.
Rfluid
requested changes
Jun 6, 2026
Rfluid
left a comment
Owner
There was a problem hiding this comment.
Also, please rebase your branch onto main and resolve any merge conflicts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds four opt-in features to the modal — Insights, session-budget pacing, Fleet (cross-machine usage sync), and Activity (live process monitor) — plus a hover tooltip on the Tokens-per-day chart. Every feature is off by default and gated behind its own
[config]section, so existing users see no change until they opt in. All logic lives inaura-core(headless, unit-tested); only rendering lives inaura. macOS + Linux are first-class; nothing platform-specific was added beyond the existing keychain/quota seams.Note
Happy to split this into separate PRs (one per feature) if you'd prefer — they were developed independently and only share additive edits to
config.rs/app.rs. See Testing & caveats at the bottom.Insights tab
A new Insights tab (
[insights] enabled, period-aware: All / 7d / 30d) that answers the "interesting" questions raw token totals don't.What it shows
opus/sonnet/haiku) and anultracodechip when detected.ultracodesession vs normal, with aNx heaviermultiplier.read / (read+write)hit ratio ("X% of context served from cache").How it works
reader/scan.rs) — no second scan, no extra I/O. New pure functions inreader/insights.rs(top_projects,top_sessions,ultracode_roi,cache_efficiency) feed a serializableInsightsSnapshot.cwdfield Claude Code writes on every JSONL entry (basename(cwd)), not the lossy on-disk slug (-Users-pedro-Downloads-…), which is unrecoverable.ultracodeis a heuristic (the session JSONL contains aWorkflowtool_use or the literalultracode); model tier is exact. The tab footnotes this.Forecast — session-budget pacing
Extends the existing Forecast tab (
[pacing] enabled, Claude Code only) with a session-budget gauge: how much of the current 5h window you can spend without exhausting the weekly window before it resets.How it works
used_percentage, never a guessed cap:cap = tokens_in_window / (used_pct/100), computed for both the 5h and 7d windows from their real boundaries (resets_at − length_minutes).active_session_min_tokens).recommended_pct = (weekly_remaining_tokens / windows_left) / session_cap × 100. Guards:used_pct < 3%→ "warming up" instead of a fabricated number;sessions_leftfloored to avoid divide-by-~0.Fleet — cross-machine usage sync
A new Fleet tab (
[fleet] enabled, opt-in) that compares usage across every machine on the same Claude subscription — the 5h/weekly limits are account-wide and shared, so this shows each machine's share.How it works
broker_urlfor self-hosting). No account, no server to run.HKDF-SHA256(secret, "aura-fleet-v1"); the topic is"aura-" + base32(HMAC-SHA256(secret, "ntfy-topic"))[..16](an 80-bit capability). The broker only ever sees ciphertext + an opaque topic.aura-fleet-secretservice — never touchesClaude Code-credentials), never inconfig.toml.FleetSyncthread is owned by aFleetManagerin the always-running tray loop (main.rs), not by the modal view — so it publishes/receives 24/7 with the modal closed, then stops cleanly when disabled or unpaired (reconcile()on startup / pair-leave / config change). One sleepingstd::thread; wakes everyheartbeat_secs(default 45) to publish + poll. Per-machine share is computed from tokens (the account %s are identical on every machine).Heads-up:
MIN_HEARTBEAT_SECS = 20because the loop makes 2 requests/cadence (publish + poll) and the public ntfy broker tolerates ~1 req/10s; a heartbeat published withX-Cache: nois never cached and thus never reaches a one-shotpoll=1subscriber, so caching is left on.Activity — live Claude Code process monitor
A new Activity tab (
[activity] enabled, opt-in) — a live monitor scoped to Claude Code only: which CLI session is eating the machine's CPU/RAM right now (useful when parallel agents freeze the box).How it works
sysinfo(default-features = false,systemfeature only — lean) to enumerate processes, classify Claude Code roots (claudeCLI), and walk each root's subtree to capture what it spawned (MCP servers,cargo build, sub-agents — usually the real hogs).cwd→ basename) and active session (newest*.jsonlin~/.claude/projects/<slug>), with total CPU% + RAM and its heaviest children.refresh_secs(default 3) only while the tab is on screen (mirrors the existing spinner-tick timer); zero cost off-screen. One long-livedsysinfo::Systemso CPU deltas are valid; first tick shows "measuring…".aura-core/src/activity.rsand is unit-tested with synthetic process lists.Tokens-per-day hover tooltips
The Tokens per day bar chart (Models tab) is now interactive.
How it works
id+ a hover highlight (accent brightened ~28%) and a.tooltip()showing the date, total tokens, and a per-model breakdown (top 3, compact figures).with_animation). No new dependencies.Cross-platform
#[cfg]was added by these features beyond a dead-code guard. Secrets use macOS Keychain / Linux Secret Service (libsecret / GNOME Keyring / KWallet viakeyring, pure-Rustcrypto-rust) with a0600file fallback for headless boxes; Windows uses Credential Manager.security-frameworkis macOS-gated;keyringbackends are per-target inCargo.toml.ureq+ rustls; the Fleet thread is plainstd::thread; the process-level manager uses the samecx.spawn/background_executortray loop that already runs on Linux.sync-secret-servicekeyring backend links the system dbus C lib (libdbus-sys), so a Linux build needslibdbus-1-devpresent (same as this repo's existing ubuntu release CI).Testing & caveats
cargo test -p aura-core→ all green (232 tests incl. new pacing / insights / activity / fleet /build_heartbeatsuites).cargo build -p auraclean.pct(API quota source) vstokens(local-fallback source); harmless, can be unified by always computing local token sums.🤖 Generated with Claude Code