Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Pre-built binaries for all platforms are available on the [GitHub Releases](http
```bash
abtop # Launch TUI
abtop --once # Print snapshot and exit
abtop --json # Print one JSON snapshot and exit (for scripts/tools)
abtop --setup # Install rate limit collection hook
abtop --theme dracula # Launch with a specific theme
```
Expand Down Expand Up @@ -152,9 +153,42 @@ When `language` is unset, abtop auto-detects from `LANG` — any value starting
| `q` | Quit |
| `r` | Force refresh |

## Library / JSON snapshot

abtop is also a library crate, so local tools can reuse its data-collection
layer in-process — no re-scanning, no subprocesses — and serialize the same
state the TUI renders.

```bash
abtop --json # one-shot JSON snapshot for scripts
```

For long-running consumers, build an `App`, refresh it with
`App::tick_no_summaries()` (which never spawns `claude --print`, so it doesn't
touch your Claude quota), and call `App::to_snapshot(interval_ms)` to get a
JSON-serializable [`Snapshot`]:

```rust,no_run
use abtop::app::App;
use abtop::{config, theme::Theme};

let cfg = config::load_config();
let mut app = App::new_with_config_and_claude_dirs(
Theme::default(), &cfg.hidden_agents, cfg.panels, &cfg.claude_config_dirs,
);
app.tick_no_summaries();
let json = serde_json::to_string(&app.to_snapshot(2_000)).unwrap();
```

`App` is not `Send` (it owns the collectors), so keep it on one thread and pass
the serialized JSON elsewhere. [abtop-web-ui](https://github.com/XKHoshizora/abtop-web-ui)
is a reference consumer: a local-first web dashboard built on exactly this API.

## Privacy

abtop reads local files and local process/open-file metadata only. No API keys, no auth. Tool names and file paths are shown in the UI, but file contents and prompt text are never displayed. Session summaries are generated via `claude --print`, which makes its own API call — this is the only indirect network usage.
abtop reads local files and local process/open-file metadata only. No API keys, no auth. In the TUI and `--once` output, tool names and file paths are shown, but file contents and prompt text are never displayed. Session summaries are generated via `claude --print`, which makes its own API call — this is the only indirect network usage.

The JSON snapshot includes richer local dashboard data, including `summary`, `chat_messages`, working directories, config roots, tool-call previews, child process commands, token counts, and port metadata. Chat text is bounded and redacted by the collectors, but it is still derived from local transcripts and may contain sensitive project context. Treat JSON snapshots as local/private data and avoid writing them to shared logs or exposing them on a network without your own access controls.

## Acknowledgements

Expand Down
16 changes: 14 additions & 2 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,21 @@ impl App {
self.status_msg = Some((msg, Instant::now()));
}

/// Full refresh used by the TUI: collect monitored data, then generate and
/// retry session summaries. Equivalent to [`App::tick_no_summaries`] followed
/// by [`App::drain_and_retry_summaries`].
pub fn tick(&mut self) {
self.tick_no_summaries();
self.drain_and_retry_summaries();
}

/// Refresh all monitored data WITHOUT spawning background summary jobs.
///
/// `tick` additionally calls [`App::drain_and_retry_summaries`], which
/// shells out to `claude --print` to generate session titles. Headless
/// consumers (e.g. the web snapshot API) call this variant so they never
/// spawn subprocesses or consume the user's Claude quota.
pub fn tick_no_summaries(&mut self) {
self.collector.set_mcp_suppress(self.mcp_suppress_sessions);
self.sessions = self.collector.collect();
self.orphan_ports = self.collector.orphan_ports.clone();
Expand Down Expand Up @@ -532,8 +546,6 @@ impl App {
}

promote_waiting_to_rate_limited(&mut self.sessions, &self.rate_limits);

self.drain_and_retry_summaries();
}

/// Drain completed summary results and spawn retries. Does NOT recollect
Expand Down
6 changes: 6 additions & 0 deletions src/collector/claude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,12 @@ impl ClaudeCollector {
}
}

impl Default for ClaudeCollector {
fn default() -> Self {
Self::with_configured_dirs(Vec::new())
}
}

impl super::AgentCollector for ClaudeCollector {
fn collect(&mut self, shared: &super::SharedProcessData) -> Vec<AgentSession> {
self.collect_sessions(shared)
Expand Down
6 changes: 6 additions & 0 deletions src/collector/codex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,12 @@ impl CodexCollector {
}
}

impl Default for CodexCollector {
fn default() -> Self {
Self::new()
}
}

impl super::AgentCollector for CodexCollector {
fn collect(&mut self, shared: &super::SharedProcessData) -> Vec<AgentSession> {
self.collect_sessions(shared)
Expand Down
6 changes: 6 additions & 0 deletions src/collector/opencode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,12 @@ LIMIT {};"#,
}
}

impl Default for OpenCodeCollector {
fn default() -> Self {
Self::new()
}
}

impl super::AgentCollector for OpenCodeCollector {
fn collect(&mut self, shared: &super::SharedProcessData) -> Vec<AgentSession> {
self.collect_sessions(shared)
Expand Down
6 changes: 4 additions & 2 deletions src/host_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
//! callers should treat absence as "metrics unavailable" and render a graceful
//! fallback.

#[derive(Debug, Clone, Copy)]
use serde::Serialize;

#[derive(Debug, Clone, Copy, Serialize)]
pub struct HostMetrics {
/// Aggregate CPU usage in percent (0.0 - 100.0). Computed across all cores.
pub cpu_pct: f64,
Expand Down Expand Up @@ -141,7 +143,7 @@ fn sample_load() -> Option<f64> {
}

/// Aggregate per-session metrics into a single agent-wide summary.
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Clone, Copy, Default, Serialize)]
pub struct AgentAggregate {
pub mem_mb: u64,
/// Average context window fill across active sessions (0.0 - 100.0).
Expand Down
Loading
Loading