Repository navigation
feat(cli): CH 03 Utilities channel + status-line append fix - #102
Conversation
statusline was clobbering the user's existing custom status line — Claude's statusLine is a single command slot and we overwrote it. now setStatusLine stashes any pre-existing command, `distro statusline` runs it (same stdin) and appends our line below, uninstall restores it. same stdin parse feeds CH 03: a locally-built instrument panel rotating alongside news/markets showing claude usage (limits/reset, ctx, cost, cache, burn, time-to-limit), git state, machine vitals, service health. local-only, every-3rd-slot injection, full vs complement layout, gated by utilitiesEnabled pref (distro preferences → utilities panel).
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
flat dot-separated text → reference-style gauges. context/gauge/machine rows share one 4-column grid (teal·amber·violet·periwinkle) with fill+track bars that line up across rows and │ separators that run straight down. - bars always shown (no more text-only complement); branch keeps name + ↑ahead ↓behind vs remote; api line hidden unless degraded/down - mem bar reddens ≥90%; 5h/7d/ctx redden + ⚠ at warn threshold - bar width scales with tty width; narrow terminals degrade gracefully (full → drop machine → slim context → gauges-only) so core gauges fit
machine stats were wrong on macOS — os.freemem reported ~97% used (it's meaningless when RAM is used for cache) and load-avg overstated cpu. now: - cpu: real 250ms sample of cpu times (instantaneous % busy) - mem: real pressure via memory_pressure (macOS) / MemAvailable (linux); omitted, never faked, when unavailable - disk: % used (100 - free) so a fuller disk reads as a fuller bar - battery reddens only when LOW (≤20%), not at full charge panel polish: drop the "distro tv · utils" footer + "· live" suffix, relabel the weekly gauge "7d" → "week" (it's the limit users hit first), and add a blank line between the gauge and machine rows for readability. rotation verified working (news → markets → utils, 1-in-3); the freeze users saw is the intended idle-pin when no prompt fires for ACTIVE_WINDOW_MS.
… rule divider - git: show just branch + ↑ahead ↓behind vs remote (git-CLI style); drop the uncommitted-files count - machine: remove battery (not useful in a terminal) → cpu / mem / disk - gauge order ctx · cache · week · 5h so the weekly + 5-hour limits sit together - replace the gauge/machine separator with a dim horizontal rule (a blank line was being collapsed by Claude's statusLine)
swap the dim horizontal rule between the gauge and machine rows for a blank gap. a truly-empty line gets collapsed by Claude's statusLine, so the row is non-breaking spaces — it renders blank but carries width, so it survives.
…e line a non-breaking-space "blank" row made Claude's statusLine truncate every row after it (the machine row vanished). a plain empty line just collapses to no gap. so revert to a thin divider, dimmed (60,64,74) to read as breathing room rather than a hard line — the lightest separator that renders the machine row.
|
hey @claude |
|
Claude finished @distroinfinity's task in 4m 1s —— View job PR Review: CH 03 Utilities channel + status-line append fixTasks
Feature SummaryThe PR ships two things: a status-line wrap fix (stash + chain the user's existing command rather than clobbering it) and CH 03 Utilities — a locally-built instrument panel injected every 3rd slot. The panel pulls from three sources: Claude's own stdin telemetry, local probes (git, CPU/mem/disk, Anthropic status page), and a rolling history file for burn-rate derivation. Everything stays on-device; no API calls from the utility path. Architecture is clean overall — the approach is sound. Issues below are real but scoped. Bugs1. Double (triple) snapshot reads on every // utility-slot.ts:248-260
build() {
const t = now()
const usage = readUsageSnapshot(t, UTILITY_SNAPSHOT_STALE_MS) // read #1
...
const ai = aiFrom(usage) // → deriveUsage() → readSnapshot() // read #2Fix: pass the snapshot into 2. const cacheRead = ...cache_read_input_tokens
const cacheCreate = ...cache_creation_input_tokens
const inTok = ...input_tokens
const cacheDenom = cacheRead + cacheCreate + inTok
const cachePct = cacheDenom > 0 ? Math.round((cacheRead * 100) / cacheDenom) : undefined
3. Degradation fallback loop — let chosen = build(attempts[attempts.length - 1] as string[][]) // pre-seed with narrowest
for (const rows of attempts) {
const r = build(rows)
if (r.max <= W) {
chosen = r
break
}
chosen = r // ← overwrites the pre-seeded fallback every iteration
}This currently works because let chosen = build(attempts[attempts.length - 1] as string[][])
for (const rows of attempts) {
const r = build(rows)
if (r.max <= W) { chosen = r; break }
}Silent Failures4. Background probe errors are entirely swallowed run()
.then((v) => { cell.value = v; cell.at = now() })
.catch(() => {}) // ← no log, no metric
.finally(() => { cell.inFlight = false })When a probe (git, machine, health) fails, we get no signal. In a long-running daemon this makes diagnosing "why is the utility panel empty" impossible. The daemon already has a 5. const res = spawnSync("sh", ["-c", wrapped.command], {
input, encoding: "utf8", timeout: 800, maxBuffer: 1024 * 1024,
})
const out = (res.stdout ?? "").replace(/\n+$/, "")
if (out.length > 0) parts.push(out)
Dead / Unused Code6. burnTokPerMin?: number // defined but never computed in deriveUsage()
compactAtPct?: number // defined but never parsed from stdin or set anywhereNeither field is set anywhere — Code Smells / Anti-patterns7. Reference equality check to inject a visual separator is fragile if (r === mac && max > 0) {
lines.push(`${LEFT_PAD}${rgb("\u2500".repeat(ruleW), ...)}`)
}This uses object identity ( 8. export function deriveUsage(now: number, staleMs: number): UsageDerived {
...
const hist = snap.history.filter((s) => now - s.ts <= Math.max(staleMs, 10 * 60_000))The 9. Architecture Notes (no action needed, FYI)
Summary
The status-line wrap logic, the probe/cache architecture, and the progressive degradation rendering are all solid. The |
… smells
- claude-usage: single snapshot read per rotation via loadUsage() (was reading
the same file twice — readUsageSnapshot + deriveUsage); cache-hit-rate now
excludes cache_creation tokens (writes, not reads) from the denominator;
burn-rate window is an explicit BURN_RATE_WINDOW_MS const (the old staleMs
arg was silently clamped and had no effect)
- render-line: replace fragile `r === mac` identity check with a GridRow
{ cells, sepBefore } shape; drop the misleading `chosen = r` fallback in the
non-break path (pre-seed handles it)
- utility-slot: background probe failures now reported via an optional debug
logger (daemon threads log.debug in) instead of a silent .catch(() => {})
- statusline: skip wrapped-command output when spawnSync sets res.error
(timeout / spawn failure) so we never emit a partial line
- shared: drop never-populated UtilityAi fields (burnTokPerMin, compactAtPct)
|
Thanks for the thorough review — all findings addressed in 7d00c87. Per item: 1. 🔴 Double snapshot read per 2. 🔴 3. 🟡 Fallback loop 4. 🟡 Probe errors swallowed — Fixed. 5. 🟡 6. 🟡 Dead fields 7. 🟠 8. 🟠 9. ℹ️ Verification: 151 unit tests + monorepo typecheck + lint all green; panel re-rendered (gauges + divider + machine row) and the new cache-rate test added then removed (per our no-lingering-throwaway-tests convention). |
What
Adds CH 03 — Utilities, a third rotating slot alongside NEWS and MARKETS that surfaces local, real-time dev telemetry, and fixes a status-line regression found along the way.
1. Status-line append fix (bug)
Claude Code's
statusLineis a single command slot. We were overwriting it wholesale and ignoring stdin, silently clobbering any custom status line a user already had. Now we stash the existing command (~/.distro/wrapped-statusline.json), run it (same stdin, 800ms timeout), and append our line below it; uninstall restores the original.2. CH 03 — Utilities channel
The same stdin Claude pipes to the status line is parsed into a local snapshot that feeds a locally-built instrument panel, injected every 3rd slot (news → markets → utils). Local-only — never hits the API, usage/cost stays on device.
Panel (final design):
memory_pressure, disk-used); not load-avg/free-mem junkdistro preferences→ utilities panel (CLI-local pref)Accuracy
Every stat fact-checked: ctx/limits/cost/cache/model from Claude's own data, git from real
git, api from status.anthropic.com. CPU/mem were fixed (macOSos.freememis meaningless; load-avg overstates). Anything unreadable is omitted, never faked.Testing
install.sh(versioned layout + realbetter-sqlite3prebuild + shims), ran the real daemon driven by real hook events — confirmed status-line append, rotation, accurate live stats, no crashes.Notes
CHANNEL_KEYS/news-selection flow.Docs:
gitbook-docs/cli/utilities-channel.md.