Skip to content
Open
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/target
__pycache__/
.DS_Store
.agent/
.claude/worktrees/
demo.tape
.idea/
.idea/
57 changes: 37 additions & 20 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

AI agent monitor for your terminal. Like btop++, but for AI coding agents.

Supports Claude Code, Codex CLI, and OpenCode sessions.
Supports Claude Code, Codex CLI, OpenCode, and Kimi Code sessions.

## Language Policy

Expand All @@ -20,7 +20,8 @@ English is mandatory for all project-facing work and communication.
src/
├── main.rs # Entry, terminal setup, event loop, --setup flag
├── app.rs # App state, tick logic, key handling, summary generation
├── setup.rs # StatusLine hook installation (abtop --setup)
├── setup.rs # Claude StatusLine + optional Kimi quota companion
├── kimi_usages.py # Explicitly installed Kimi quota helper
├── ui/
│ └── mod.rs # All panels in single file: header, context, quota,
│ # tokens, projects, ports, sessions, footer
Expand All @@ -29,8 +30,9 @@ src/
│ ├── claude.rs # Claude Code: session discovery, transcript parsing
│ ├── codex.rs # Codex CLI: session discovery via ps+lsof, JSONL parsing
│ ├── opencode.rs # OpenCode: session discovery via ps + SQLite DB parsing
│ ├── kimi.rs # Kimi Code: ps+cwd → sessions/wire.jsonl
│ ├── process.rs # Child process tree (ps) + open ports (lsof) + git stats
│ └── rate_limit.rs # Rate limit file reading (~/.claude/abtop-rate-limits.json)
│ └── rate_limit.rs # Local Claude/Kimi quota file reads + Codex cache
└── model/
├── mod.rs # Re-exports
└── session.rs # AgentSession, SessionStatus, RateLimitInfo,
Expand Down Expand Up @@ -76,15 +78,15 @@ Panel rendering priority (top to bottom):

Panel descriptions:
- **¹context**: Left = token rate braille sparkline (200-point history). Right = per-session context % bars with yellow/red warning.
- **²quota**: Claude + Codex rate limit gauges side-by-side (5h and 7d windows with reset countdown). Quota is intentionally limited to Claude and Codex; do not add an OpenCode row unless OpenCode exposes a reliable account-level provider rate-limit source.
- **²quota**: Claude + Codex + Kimi rate limit gauges side-by-side. Kimi's long window is the account plan period rather than a fixed seven-day window. Do not add an OpenCode row unless OpenCode exposes a reliable account-level provider rate-limit source.
- **³tokens**: Total token breakdown (in/out/cache) + per-turn sparkline for selected session.
- **projects** (always visible): Per-project git branch + added/modified file counts.
- **⁴ports**: Agent-spawned open ports + orphan ports (from dead sessions). Conflict detection.
- **⁵sessions**: Full-width panel below mid row. Session list table (top) + selected session detail (bottom), separated by divider.

## Data Sources

All read-only from local filesystem + `ps` + `lsof`. No API calls, no auth.
Session monitoring is read-only from the local filesystem plus `ps` and `lsof`. The explicitly installed Kimi quota companion is the only provider-API/auth exception.

### 1. Claude Code session discovery: process + config-root mapping

Expand Down Expand Up @@ -171,34 +173,46 @@ Rate limits extracted from `token_count` events:
- Discover running `opencode` processes via shared `ps` data.
- Read recent sessions from OpenCode's SQLite DB through `sqlite3 -readonly -json`.
- Match live PIDs to DB sessions by process cwd. OpenCode does not expose a PID/session mapping, so when multiple DB rows share one cwd, only live PIDs should be assigned and older rows should not be shown as live duplicates.
- OpenCode contributes session/token/project/port data, but not quota data. Quota remains Claude + Codex only.
- OpenCode contributes session/token/project/port data, but not quota data.

### 5. Subagents: `~/.claude/projects/{path}/{sessionId}/subagents/`
### 5. Kimi Code sessions: `~/.kimi-code/sessions/`
- Discover running `kimi` processes via shared `ps` data.
- Resolve PID → cwd locally (`/proc` on Linux, `sysinfo` on Windows, cached `lsof` on macOS).
- Map cwd to Kimi workspaces and select the newest matching live session directories.
- Parse `agents/main/wire.jsonl` incrementally for tokens, model, context, and current tools.
- `KIMI_CODE_HOME` overrides the default `~/.kimi-code` data root.
- Kimi contributes session/token/project/port data. Account quota comes only from the optional companion described below.

### 6. Subagents: `~/.claude/projects/{path}/{sessionId}/subagents/`
- `agent-{hash}.jsonl` — same JSONL format as main transcript
- `agent-{hash}.meta.json` — `{ "agentType": "general-purpose", "description": "..." }`

### 6. Process tree: `ps` + `lsof`
### 7. Process tree: `ps` + `lsof`
```bash
ps -eo pid,ppid,rss,%cpu,command # All processes
lsof -i -P -n -sTCP:LISTEN # Open ports
```
- Build parent→children map from ppid
- Map listening PID → parent agent PID → session

### 7. Git status per project
### 8. Git status per project
```bash
git -C {cwd} status --porcelain # added/modified file counts
```

### 8. Memory status
### 9. Memory status
- Path: `~/.claude/projects/{encoded-path}/memory/`
- Count files in directory + lines in `MEMORY.md`

### 9. Rate limit (Claude Code)
### 10. Rate limits (account quota)

Quota is account-level and is not present in session transcript JSONL.

**Claude Code** uses its StatusLine mechanism. `abtop --setup` creates `~/.claude/abtop-statusline.sh`, registers it in `~/.claude/settings.json`, and the hook writes `~/.claude/abtop-rate-limits.json` after Claude responses.

NOT in transcript JSONL. Collected via StatusLine mechanism.
**Kimi Code** has no reliable local account-quota file. `abtop --setup` explicitly installs `~/.kimi-code/abtop-usages.sh` (or under `KIMI_CODE_HOME`). While the full TUI runs, abtop may execute that already-installed companion in the background at most once every two minutes. The companion uses the existing `kimi login` OAuth credentials, refreshes them when necessary, calls `https://api.kimi.com/coding/v1/usages`, and atomically writes `abtop-rate-limits.json`. It requires `python3`. The TUI never auto-installs the helper, never blocks on refresh, reaps every helper process, and never invokes it from `--once`, `--json`, or when Kimi is hidden.

`abtop --setup` automates this: creates a script at `~/.claude/abtop-statusline.sh` that writes rate limit JSON to `~/.claude/abtop-rate-limits.json`, and registers it in `~/.claude/settings.json`.
**Codex** quota comes from local `token_count` events and is cached under `~/.cache/abtop/`.

File format read by abtop:
```json
Expand All @@ -209,12 +223,11 @@ File format read by abtop:
"updated_at": 1774714400
}
```
- Rejects stale data (> 10 minutes old).
- `rate_limits` only present for Pro/Max subscribers.
- Account-level metric, shared across all sessions.
- Claude hook data older than ten minutes is rejected. Kimi's last valid cache remains visible but is dimmed after ten minutes so transient refresh failures do not erase the gauge.
- Account-level metrics are shared across all sessions of that provider.
- Show "—" when not configured or data unavailable.

### 10. Other files
### 11. Other files
- `~/.claude/stats-cache.json` — daily aggregates. Only updated on `/stats`, NOT real-time.
- `~/.claude/history.jsonl` — prompt history with sessionId.

Expand Down Expand Up @@ -386,8 +399,10 @@ Parsing/registry logic is unit-tested in `jump/mod.rs`; the thin `ps`/`osascript
abtop reads transcripts, prompts, tool inputs, and memory files. These may contain secrets.
- **`--once` output**: redact file contents from tool_use inputs. Show tool name + file path only, not content.
- **TUI mode**: show tool name + first arg (file path), never show file contents or prompt text in session list.
- **No network**: abtop never sends data anywhere. All local reads.
- **Exception**: summary generation calls `claude --print` locally (no network by abtop itself, but claude may use its API).
- **Session discovery is local-only**: filesystem + `ps` / `lsof`. No abtop-owned API keys.
- **Network exceptions**:
- Summary generation calls `claude --print` locally; Claude may use its API.
- After explicit `abtop --setup`, the Kimi quota companion uses the existing Kimi OAuth login and sends only an authenticated quota request to Kimi. It never receives transcript or prompt content from abtop.

## Gotchas

Expand All @@ -405,5 +420,7 @@ abtop reads transcripts, prompts, tool inputs, and memory files. These may conta
- **Undocumented internals**: all data sources are Claude Code/Codex implementation details, not stable APIs. Schema may change without notice. Defensive parsing with `serde(default)` everywhere.
- **Terminal size**: minimum 80x24. Panels degrade gracefully when small (context panel hidden first).
- **PID reuse in port cache**: invalidate cached ports when the set of tracked PIDs changes.
- **Rate limit staleness**: reject rate limit data older than 10 minutes.
- **Rate limit staleness**: reject Claude hook data older than 10 minutes; retain and dim stale Kimi cache data.
- **`/clear` + multi-PID same cwd**: after `/clear`, Claude Code mints a new `sessionId` + `.jsonl` without rewriting `sessions/{PID}.json`. abtop overrides the stale sid by picking the newest transcript in the project dir, but this heuristic can't disambiguate ownership when two live `claude` PIDs share a cwd — so the override is disabled in that case and both sessions keep their original sid until exit. Use separate worktrees if live tracking is needed on both simultaneously.
- **Kimi multi-PID same cwd**: sessions are matched newest-first per workspace; two live `kimi` PIDs in one cwd claim the two newest session dirs. Prefer separate workspaces if both must be tracked precisely.
- **Kimi quota companion**: optional, installed only by `abtop --setup`, and may refresh the same OAuth credentials file used by Kimi Code.
36 changes: 20 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

**Like [btop](https://github.com/aristocratos/btop), but for your AI coding agents.**

See every Claude Code, Codex CLI, and OpenCode session at a glance — token usage, context window %, rate limits, child processes, open ports, and more.
Claude Code, Codex CLI, and OpenCode sessions are discovered from local process/file state, so multiple active profiles are supported across macOS, Linux, and Windows.
See every Claude Code, Codex CLI, OpenCode, and Kimi Code session at a glance — token usage, context window %, rate limits, child processes, open ports, and more.
Sessions are discovered from local process/file state, so multiple active profiles are supported across macOS, Linux, and Windows.

![demo](https://raw.githubusercontent.com/graykode/abtop/main/assets/demo.gif)

Expand All @@ -14,7 +14,7 @@ Claude Code, Codex CLI, and OpenCode sessions are discovered from local process/
- Agent spawned a server and forgot to kill it? Orphan port detection.
- Context window filling up? Per-session % bars with warnings.

All read-only. No API keys. No auth.
Session monitoring is read-only and local. Optional Kimi quota support is enabled explicitly with `abtop --setup`; its companion reuses the user's existing Kimi login and never reads session content.

## Install

Expand Down Expand Up @@ -53,7 +53,7 @@ Pre-built binaries for all platforms are available on the [GitHub Releases](http
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 --setup # Install Claude and Kimi quota companions
abtop --theme dracula # Launch with a specific theme
abtop --mouse # Enable mouse click/scroll navigation
```
Expand All @@ -75,21 +75,25 @@ tmux new -s work

## Supported Agents

| Feature | Claude Code | Codex CLI | OpenCode |
| ----------------- | :---------: | :-------: | :------: |
| Session Discovery | ✅ | ✅ | ✅ |
| Token Tracking | ✅ | ✅ | ✅ |
| Context Window % | ✅ | ✅ | ❌ |
| Status Detection | ✅ | ✅ | ✅ |
| Current Task | ✅ | ✅ | ❌ |
| Rate Limit | ✅ | ✅ | ❌ |
| Git Status | ✅ | ✅ | ✅ |
| Children / Ports | ✅ | ✅ | ✅ |
| Subagents | ✅ | ❌ | ❌ |
| Memory Status | ✅ | ❌ | ❌ |
| Feature | Claude Code | Codex CLI | OpenCode | Kimi Code |
| ----------------- | :---------: | :-------: | :------: | :-------: |
| Session Discovery | ✅ | ✅ | ✅ | ✅ |
| Token Tracking | ✅ | ✅ | ✅ | ✅ |
| Context Window % | ✅ | ✅ | ❌ | ✅ |
| Status Detection | ✅ | ✅ | ✅ | ✅ |
| Current Task | ✅ | ✅ | ❌ | ✅ |
| Rate Limit | ✅ | ✅ | ❌ | ✅* |
| Git Status | ✅ | ✅ | ✅ | ✅ |
| Children / Ports | ✅ | ✅ | ✅ | ✅ |
| Subagents | ✅ | ❌ | ❌ | names* |
| Memory Status | ✅ | ❌ | ❌ | ❌ |

\*Kimi lists subagent names from session state; per-subagent tokens/status are not polled yet. Kimi quota requires `abtop --setup`, `python3`, a working network connection, and an existing `kimi login`.

OpenCode support reads the local SQLite database at `~/.local/share/opencode/opencode.db` (also the default location on Windows; `%LOCALAPPDATA%\opencode` and `%APPDATA%\opencode` are probed as fallbacks) and requires `sqlite3` in `PATH` (on Windows: `winget install SQLite.SQLite`).

Kimi Code sessions are discovered from live `kimi` processes and `~/.kimi-code/sessions/**/wire.jsonl` (override root with `KIMI_CODE_HOME`). Because Kimi does not write account quota locally, `abtop --setup` can explicitly install `~/.kimi-code/abtop-usages.sh`. The TUI then runs that companion in the background every two minutes; it reuses the existing Kimi OAuth login, calls Kimi's usage endpoint, and writes only quota percentages and reset times to `abtop-rate-limits.json`. Headless snapshots never run the companion. Hide Kimi sessions and quota refreshes with `hidden_agents = ["kimi"]`.

## Themes

12 built-in themes, including 4 colorblind-friendly options (`high-contrast`, `protanopia`, `deuteranopia`, `tritanopia`). Press `t` to cycle at runtime, or launch with `--theme <name>`. Your choice is saved to `~/.config/abtop/config.toml`.
Expand Down
14 changes: 13 additions & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ pub struct App {
pub help_open: bool,
/// View leader overlay (`v`) visibility.
pub view_open: bool,
/// Skip optional Kimi quota refreshes when Kimi is hidden.
kimi_quota_enabled: bool,
}

impl App {
Expand All @@ -172,6 +174,7 @@ impl App {
let mut collector =
MultiCollector::with_hidden_and_claude_config_dirs(hidden_agents, claude_config_dirs);
collector.set_mcp_suppress(true);
let kimi_quota_enabled = !hidden_agents.iter().any(|h| h.eq_ignore_ascii_case("kimi"));
Self {
sessions: Vec::new(),
selected: 0,
Expand Down Expand Up @@ -215,6 +218,7 @@ impl App {
agent_aggregate: AgentAggregate::default(),
help_open: false,
view_open: false,
kimi_quota_enabled,
}
}

Expand Down Expand Up @@ -495,6 +499,12 @@ impl App {
/// retry session summaries. Equivalent to [`App::tick_no_summaries`] followed
/// by [`App::drain_and_retry_summaries`].
pub fn tick(&mut self) {
// Only runs a companion previously installed by the explicit
// `abtop --setup` flow. It is non-blocking and omitted from headless
// snapshots and hidden Kimi configurations.
if self.kimi_quota_enabled {
crate::setup::maybe_refresh_kimi_quota();
}
self.tick_no_summaries();
self.drain_and_retry_summaries();
}
Expand Down Expand Up @@ -1002,6 +1012,7 @@ fn is_supported_agent_command(cmd: &str) -> bool {
crate::collector::process::cmd_has_binary(cmd, "claude")
|| crate::collector::process::cmd_has_binary(cmd, "codex")
|| crate::collector::process::cmd_has_binary(cmd, "opencode")
|| crate::collector::process::cmd_has_binary(cmd, "kimi")
}

fn is_killable_agent_command(cmd: &str) -> bool {
Expand Down Expand Up @@ -1097,10 +1108,11 @@ mod tests {
}

#[test]
fn supported_agent_command_accepts_opencode() {
fn supported_agent_command_accepts_all_collectors() {
assert!(is_supported_agent_command("/usr/local/bin/claude"));
assert!(is_supported_agent_command("codex --resume abc"));
assert!(is_supported_agent_command("/opt/homebrew/bin/opencode"));
assert!(is_supported_agent_command("/usr/local/bin/kimi"));
assert!(!is_supported_agent_command("node server.js"));
}

Expand Down
Loading