{"id":49425387,"url":"https://github.com/mbrassey/agtop","last_synced_at":"2026-05-14T21:01:15.565Z","repository":{"id":354990348,"uuid":"1225266305","full_name":"MBrassey/agtop","owner":"MBrassey","description":"Terminal UI for monitoring AI coding agents — like top, but for Claude Code, Codex, Aider, Gemini, Goose. Reads /proc + session transcripts.","archived":false,"fork":false,"pushed_at":"2026-05-04T07:37:42.000Z","size":12733,"stargazers_count":6,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-04T14:37:23.621Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://github.com/MBrassey/agtop","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MBrassey.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-30T05:38:34.000Z","updated_at":"2026-05-04T10:34:57.000Z","dependencies_parsed_at":null,"dependency_job_id":"624425e6-012a-4b43-a174-5af1698a8294","html_url":"https://github.com/MBrassey/agtop","commit_stats":null,"previous_names":["mbrassey/agtop"],"tags_count":37,"template":false,"template_full_name":null,"purl":"pkg:github/MBrassey/agtop","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MBrassey%2Fagtop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MBrassey%2Fagtop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MBrassey%2Fagtop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MBrassey%2Fagtop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MBrassey","download_url":"https://codeload.github.com/MBrassey/agtop/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MBrassey%2Fagtop/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32654618,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-05T11:29:49.557Z","status":"ssl_error","status_checked_at":"2026-05-05T11:29:48.587Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agent","ai","ai-coding","aider","btop","claude-code","cli","codex","crossterm","cursor","htop","monitor","observability","ratatui","rust","terminal","top","tui"],"created_at":"2026-04-29T09:09:00.363Z","updated_at":"2026-05-10T05:30:58.454Z","avatar_url":"https://github.com/MBrassey.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# agtop\n\nProcess monitor for AI coding agents.\n\nReads `/proc` (`sysinfo` on macOS / Windows / *BSD) plus the on-disk\nsession transcripts of Claude Code, OpenAI Codex, Block Goose, Aider,\nand Google Gemini. For each detected agent it reports CPU, RSS,\nstatus, current tool / task, in-flight subagents, cumulative token\nusage, estimated cost, context-window fill, and loaded skills.\n\n[![Crate](https://img.shields.io/crates/v/agtop.svg)](https://crates.io/crates/agtop)\n[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n[![Rust](https://img.shields.io/badge/rust-1.74%2B-orange.svg)](https://www.rust-lang.org)\n[![CI](https://github.com/MBrassey/agtop/actions/workflows/ci.yml/badge.svg)](https://github.com/MBrassey/agtop/actions/workflows/ci.yml)\n\n\u003cimg src=\"docs/screenshot-tui.png\" alt=\"agtop TUI\" width=\"100%\" /\u003e\n\nDetail popup (Enter on any row): current tool, model, in-flight\nsubagents, token usage, context-window fill, loaded skills, live\ntranscript preview.\n\n\u003cimg src=\"docs/screenshot.png\" alt=\"agtop agent detail popup\" width=\"100%\" /\u003e\n\n## Contents\n\n- [Install](#install)\n- [Usage](#usage)\n- [What it reads](#what-it-reads)\n- [Status badges](#status-badges)\n- [TUI controls](#tui-controls)\n- [Architecture](#architecture)\n- [JSON output](#json-output)\n- [Cost estimation](#cost-estimation)\n- [Context window and skills](#context-window-and-skills)\n- [Detail popup](#detail-popup)\n- [Custom matchers](#custom-matchers)\n- [Platforms](#platforms)\n- [Implementation notes](#implementation-notes)\n- [Repo layout](#repo-layout)\n- [Distribution channels](#distribution-channels)\n- [Troubleshooting](#troubleshooting)\n- [FAQ](#faq)\n- [License](#license)\n\n---\n\n## Install\n\n| Platform                       | Command                                |\n| ------------------------------ | -------------------------------------- |\n| Arch / CachyOS                 | `yay -S agtop`                         |\n| Ubuntu / Mint / Pop!_OS        | Launchpad PPA — `sudo add-apt-repository ppa:mbrassey/agtop \u0026\u0026 sudo apt install agtop` |\n| Debian                         | apt source — see [below](#debian-apt-source) |\n| macOS                          | `brew install mbrassey/tap/agtop`      |\n| Windows                        | `winget install agtop`                 |\n| FreeBSD                        | `sudo pkg install agtop`               |\n| Cargo                          | `cargo install agtop`                  |\n| npm                            | `npm install -g @mbrassey/agtop`       |\n| Prebuilts                      | [latest release][rel] — linux x86_64 / aarch64, macOS x86_64 / aarch64, windows x86_64 |\n\n[rel]: https://github.com/MBrassey/agtop/releases/latest\n\n#### Ubuntu / Mint / Pop!_OS — Launchpad PPA\n\nThe package is built and signed by Launchpad's amd64 / arm64 farm\nstraight from the upstream tag, so updates land within minutes of a\nrelease without touching this repo's CI.\n\n```sh\nsudo add-apt-repository ppa:mbrassey/agtop\nsudo apt update \u0026\u0026 sudo apt install agtop\n```\n\nWatch the build queue at\n\u003chttps://launchpad.net/~mbrassey/+archive/ubuntu/agtop\u003e.  Subsequent\nupdates flow through `sudo apt update \u0026\u0026 sudo apt upgrade` like any\nother apt package.\n\n#### Debian apt source\n\nLaunchpad PPAs target Ubuntu series only.  Debian users add the\nself-hosted apt repo:\n\n```sh\nsudo install -d -m 0755 /etc/apt/keyrings\ncurl -fsSL https://mbrassey.github.io/apt/pubkey.asc \\\n  | sudo gpg --dearmor --yes -o /etc/apt/keyrings/agtop.gpg\necho \"deb [signed-by=/etc/apt/keyrings/agtop.gpg] https://mbrassey.github.io/apt ./\" \\\n  | sudo tee /etc/apt/sources.list.d/agtop.list\nsudo apt update \u0026\u0026 sudo apt install agtop\n```\n\n\u003e The `install -d` line is required on older Debian releases where\n\u003e `/etc/apt/keyrings/` doesn't exist by default — without it the\n\u003e `gpg --dearmor` write fails silently and `apt update` skips the\n\u003e source.\n\nSubsequent updates flow through `sudo apt update \u0026\u0026 sudo apt upgrade`\nlike any other apt package.\n\nThe npm package is a Node shim that downloads the matching prebuilt\nfrom the GitHub Release and verifies it against the release's\n`SHA256SUMS` file before extracting. `cargo install` is the universal\nfallback.\n\n---\n\n## Usage\n\n```\nagtop                       full TUI\nagtop --once                one-shot snapshot, like `top -b -n 1`\nagtop -1 --top 10           top 10 agents and exit\nagtop --json                machine-readable JSON\nagtop --watch               one summary line per tick (no TUI, pipes cleanly)\nagtop --filter aider        only agents matching label / cmdline / cwd\nagtop --sort tokens         sort by token consumption\nagtop --prices prices.toml  override the bundled model price table\nagtop -m \"myagent=python.*my_agent\\.py\"   add a custom matcher\n```\n\nRun `agtop --help` for the full flag list.\n\n### CLI reference\n\n| Flag                            | Default | Purpose |\n| ------------------------------- | ------- | ------- |\n| `-1`, `--once`                  |         | Print a one-shot snapshot and exit (no TUI) |\n| `-j`, `--json`                  |         | Machine-readable JSON snapshot; implies `--once` |\n| `-i`, `--interval \u003cSECONDS\u003e`    | `1.5`   | TUI / iteration refresh interval |\n| `-n`, `--iterations \u003cCOUNT\u003e`    | `1`     | With `--once`, print N snapshots delimited by `---` |\n| `-f`, `--filter \u003cSUBSTR\u003e`       |         | Only agents matching label / cmdline / cwd / project / pid |\n| `-s`, `--sort \u003cKEY\u003e`            | `smart` | One of `smart` / `cpu` / `mem` / `tokens` / `uptime` / `agent` |\n| `-m`, `--match \u003cLABEL=REGEX\u003e`   |         | Add a custom agent matcher (repeatable) |\n| `--no-color`                    |         | Disable ANSI colors in `--once` / `--json` |\n| `--theme \u003cNAME\u003e`                | `default` | TUI palette: `default` / `dracula` / `nord` / `gruvbox` / `monochrome` |\n| `--top \u003cN\u003e`                     | `0`     | With `--once`, only show top N agents (0 = all) |\n| `--list-builtins`               |         | Print built-in matcher list and exit |\n| `--prices \u003cPATH\u003e`               |         | TOML file overriding / extending the bundled price table |\n| `--watch`                       |         | One summary line per tick to stdout (no TUI, pipes cleanly) |\n| `--threshold-cpu \u003cPERCENT\u003e`     |         | In `--watch`, exit 3 if aggregate CPU% exceeds N |\n| `--threshold-tokens-rate \u003cT\u003e`   |         | In `--watch`, exit 4 if average tokens/min exceeds N |\n| `--pid \u003cPID\u003e`                   |         | Open the TUI focused on a specific PID with the detail popup already showing — useful as a wrapper from other tooling: `agtop --pid $(pgrep claude)`. Falls back to the regular list view if the PID isn't a known agent. |\n| `-V`, `--version`               |         | Print version and exit |\n| `-h`, `--help`                  |         | Print help and exit |\n\n### Environment variables\n\n| Var             | Effect |\n| --------------- | ------ |\n| `AGTOP_MATCH`   | Semicolon-separated `label=regex` matchers (additive to built-ins). Equivalent to repeating `-m`. |\n| `AGTOP_PRICES`  | Path to a TOML price-table override file (equivalent to `--prices`). |\n| `NO_COLOR`      | When set, disables ANSI colors in `--once` / `--json` (honors the [no-color.org](https://no-color.org) convention). |\n\n---\n\n## What it reads\n\n### Process metrics\n\n| Source            | Linux      | macOS         | Windows                  | *BSD       |\n| ----------------- | ---------- | ------------- | ------------------------ | ---------- |\n| PID / cmdline / cwd / exe | `/proc/\u003cpid\u003e/*`    | sysinfo | sysinfo | sysinfo |\n| CPU% / RSS / vsize / threads / state / start-time | `/proc/\u003cpid\u003e/stat` | sysinfo | sysinfo | sysinfo |\n| Total / available system memory | `/proc/meminfo` | sysinfo | sysinfo | sysinfo |\n| Per-process read / write bytes  | `/proc/\u003cpid\u003e/io` | sysinfo `disk_usage()` | sysinfo `disk_usage()` | (sysinfo gap) |\n| Writable open files             | `/proc/\u003cpid\u003e/fdinfo` + `fd/` readlink | direct FFI to `proc_pidinfo` / `proc_pidfdinfo` (libSystem.dylib) | `NtQuerySystemInformation(SystemExtendedHandleInformation)` + `DuplicateHandle` + `GetFinalPathNameByHandleW` | libprocstat — `procstat_getfiles` |\n| Read-only open files            | `/proc/\u003cpid\u003e/fd` filtered by `fdinfo:flags` | libproc with BSD `FREAD`/`FWRITE` filter on `fi_openflags` | NtQSI + `FILE_READ_DATA` ∧ ¬ `FILE_WRITE_DATA` access mask | libprocstat read-flag filter |\n| Children process listing        | `/proc/\u003cpid\u003e/task/*/children` | sysinfo parent-walk | sysinfo parent-walk | sysinfo parent-walk |\n| Established TCP connections     | `/proc/\u003cpid\u003e/net/tcp{,6}` | libproc `PROC_PIDFDSOCKETINFO` + `tcpsi_state` | `GetExtendedTcpTable(TCP_TABLE_OWNER_PID_CONNECTIONS)` | libprocstat sockstat decode |\n\nThe Linux backend lives in `src/proc_.rs`; the cross-platform\nsysinfo shim is in `src/sysbackend.rs`. Native FD enumeration is\nsplit across `src/writing_files.rs` (write-mode), `src/reading_files.rs`\n(read-mode), and `src/net_count.rs` (ESTABLISHED TCP). See\n[Implementation notes](#implementation-notes) below for the FFI\ndetails.\n\n### Process classification\n\n20 built-in regex matchers covering Claude Code, OpenAI Codex, Goose,\nAider, Gemini, Cursor, Continue, Opencode, Copilot CLI, Cody, Amp,\nCrush, Mods, sgpt, llm, Ollama, Fabric. Extend via `-m LABEL=REGEX`\nor `$AGTOP_MATCH`. `agtop --list-builtins` prints the canonical list.\n\n### Session transcripts\n\n| Vendor       | Path | Format |\n| ------------ | ---- | ------ |\n| Claude Code  | `~/.claude/projects/\u003cencoded-cwd\u003e/\u003csession\u003e.jsonl` | newline-delimited JSON |\n| OpenAI Codex | `~/.codex/sessions/\u003cYYYY\u003e/\u003cMM\u003e/\u003cDD\u003e/\u003crollout\u003e.jsonl` | newline-delimited JSON |\n| Block Goose  | `~/.config/goose/sessions/` | newline-delimited JSON |\n| Aider        | `\u003ccwd\u003e/.aider.chat.history.md` | Markdown chat log |\n| Google Gemini| `~/.gemini/sessions/\u003cid\u003e.json` | single-object JSON |\n\nEach vendor's enricher (`src/{claude,codex,goose,aider,gemini}.rs`)\nextracts: current tool, current task, model name, in-flight `Task`\nsubagents, per-bucket token totals, latest-turn input window size,\nrecent-activity tail (assistant prose, tool calls, tool results),\nstop reason. Reads are tail-only (last 64 KiB by default, capped at\n64 MiB) so a multi-MB JSONL doesn't dominate the snapshot tick.\n\n---\n\n## Status badges\n\nEvery agent row carries one of seven badges. Process state and session\nactivity are blended so an agent mid-generation isn't reported as idle.\n\n| Badge      | Trigger |\n| ---------- | ------- |\n| ● **BUSY** | live process **and** transcript ≤ 30 s old, **or** any tool in flight, **or** CPU% ≥ 10 |\n| ● **SPWN** | live process with one or more `Task` / `Agent` *subagents* in flight |\n| ● **ACTV** | live process with transcript activity in the last 5 min, **or** CPU% ≥ 3 |\n| ○ **IDLE** | live process up but quiet for \u003e5 min and CPU% below threshold |\n| ◌ **WAIT** | no live process, but session activity in the last 24 h |\n| ✓ **DONE** | session ended (Claude `stop_reason: end_turn`, Codex `session_end`) |\n| · **STAL** | last activity older than 24 h |\n\nProcesses invoked with `--dangerously-skip-permissions`, `--no-permissions`,\n`--allow-dangerous`, `--yolo`, or `sudo {claude,codex}` are flagged with\na warm-amber `▍` left-edge bar before the agent label. The flag is also\nexposed in `--json` as `agents[].dangerous: bool`.\n\n---\n\n## TUI controls\n\n### Agents panel\n\n| Key                | Action |\n| ------------------ | ------ |\n| `q`, `Ctrl-C`      | Quit (closes popup first if open) |\n| `?`, `h`           | Toggle help overlay |\n| `p`                | Pause / resume refresh |\n| `r`                | Refresh now |\n| `s`                | Cycle sort: smart → cpu → mem → tokens → uptime → agent (▼ / ▲ indicator) |\n| `g`                | Toggle project grouping (sticky group header pins to row 0 when scrolled past) |\n| `t`                | Toggle tree mode (indented child processes under each agent) |\n| `C` (capital)      | Toggle compact rows (hides PID / uptime / chips, gives `DOING` the rest of the row) |\n| `1` – `7`          | Toggle individual columns: PID / CPU / MEM / UP / SUB / TOK / `▍` (dangerous marker) |\n| `/`, `f`           | Filter (`Ctrl-U` clears, `Ctrl-W` deletes word) |\n| `j` / `k`, ↓ / ↑   | Move selection (auto-scrolls the panel) |\n| `PgUp` / `PgDn`    | Move by 10 |\n| `Home` / `End`     | First / last agent |\n| `Enter`, `Space`   | Open / close detail popup |\n| `K` (capital)      | SIGTERM the selected agent (confirm `y` / `n`) |\n| `Esc`              | Close popup, clear filter |\n| Mouse              | Click row to select; double-click opens detail; wheel scrolls (over the sessions panel the wheel scrolls that panel instead) |\n\nThe agents table auto-scrolls to keep the selected row visible and\nrenders a themed scrollbar on the right edge whenever the row count\nexceeds the viewport.\n\n### Detail popup (`Enter` / `Space`)\n\n| Key                | Action |\n| ------------------ | ------ |\n| `j` / `k`, ↓ / ↑   | Scroll body line by line |\n| `PgUp` / `PgDn`    | Page through long content |\n| `g` / `G`          | Jump to top / bottom (`G` also re-engages live-tail) |\n| `Home` / `End`     | Jump to top / bottom |\n| `n` / `N`          | Next / previous section divider |\n| `/`                | Filter popup contents (Esc clears, Enter accepts) |\n| `y`                | Copy `agent / pid / cwd / cmd / session` to clipboard via OSC 52 |\n| Wheel              | Scroll while popup is open |\n| `Esc`, `Enter`     | Close (scroll position is remembered per-pid) |\n\nThe popup grows to ~80% × 90% of the viewport, scrolls when content\noverflows, and pins a `TAIL` pill in the top-right while live-tail\nis active. New events from the session transcript auto-stick to the\nbottom of the *Live preview* until the user scrolls up; pressing `G`\nor wheeling to the bottom re-engages live-tail.\n\nThe popup ends with a *Live preview* box showing the last entries\nfrom the session transcript — assistant prose (`›`), tool calls\n(`→`), and tool results (`←`).\n\n---\n\n## Architecture\n\n\u003cimg src=\"docs/architecture.svg\" alt=\"agtop architecture diagram\" width=\"100%\" /\u003e\n\n\n---\n\n## JSON output\n\n`agtop --json` writes one snake_case JSON object to stdout. Schema is\nstable across releases; new fields are additive. Suitable for `jq`,\ndashboards, or alerting.\n\n\u003cdetails\u003e\u003csummary\u003eShow full JSON sample (~50 lines)\u003c/summary\u003e\n\n```json\n{\n  \"now\": 1777439481861,\n  \"platform\": \"linux\",\n  \"note\": null,\n  \"sys_cpus\": 32,\n  \"mem_total\": 132499206144,\n  \"mem_available\": 78214098944,\n  \"aggregates\": {\n    \"cpu\": 17.2, \"mem_bytes\": 4257710080,\n    \"active\": 13, \"busy\": 1, \"waiting\": 4, \"completed\": 5,\n    \"subagents\": 2, \"project_count\": 11,\n    \"tokens_total\": 95199819,\n    \"tokens_input\": 94971751,\n    \"tokens_output\": 228068,\n    \"cost_usd\": 1441.68\n  },\n  \"agents\": [\n    {\n      \"pid\": 404872, \"label\": \"claude\", \"status\": \"busy\",\n      \"project\": \"zk-rollup-prover\",\n      \"model\": \"claude-opus-4-7\",\n      \"current_tool\": \"Bash\",\n      \"current_task\": \"nargo prove --witness witness.tr\",\n      \"subagents\": 1,\n      \"in_flight_subagents\": [\"code-reviewer: review the auth refactor\"],\n\n      \"tokens_total\":       5893647,\n      \"tokens_input\":       5847512,\n      \"tokens_output\":        46135,\n      \"tokens_cache_read\":  5712304,\n      \"tokens_cache_write\":   89008,\n\n      \"cost_usd\": 4.21,\n      \"cost_basis\": \"api\",\n\n      \"context_used\":   515708,\n      \"context_limit\": 1000000,\n      \"loaded_skills\": [\"frontend-design\", \"slack-tooler\"],\n      \"tool_counts\":   [[\"Bash\", 47], [\"Edit\", 23], [\"Read\", 12], [\"Grep\", 8]],\n\n      \"dangerous\": false,\n      \"dangerous_flag\": \"\",\n      \"cpu\": 16.3, \"rss\": 626491392,\n      \"ppid\": 12345, \"ppid_name\": \"zsh\",\n      \"read_bytes\": 482344960, \"write_bytes\": 12189440,\n      \"writing_files\": [\"/home/matt/code/zk-rollup-prover/circuits/main.rs\"],\n      \"writing_dirs\":  [\"/home/matt/code/zk-rollup-prover/circuits\"],\n      \"reading_files\": [\"/home/matt/code/zk-rollup-prover/Cargo.lock\",\n                        \"/home/matt/.claude/skills/plonk-prover/SKILL.md\"],\n      \"children\":      [[404873, \"bash\"], [404874, \"node\"]],\n      \"net_established\": 3,\n      \"read_rate_bps\":  3145728,\n      \"write_rate_bps\":  524288,\n      \"gpu_pct\":           42.0,\n      \"gpu_mem_bytes\": 4294967296,\n      \"uptime_sec\": 345600,\n      \"session_started_ms\": 1777094281861,\n      \"recent_activity\": [\n        \"› Reviewing the diff\",\n        \"→ Bash: nargo prove --witness witness.tr\",\n        \"← witness verified\"\n      ]\n    }\n  ],\n  \"projects\": [/* per-project rollups */],\n  \"sessions\": {/* counts + recent_tasks */},\n  \"history\": {/* up-to-240-tick series for cpu / mem / tokens_rate / etc. */},\n  \"activity\": [/* spawn / exit events */]\n}\n```\n\n\u003c/details\u003e\n\nPer-agent fields worth highlighting:\n\n| Field | Meaning |\n| ----- | ------- |\n| `cost_basis` | `api` (known per-token rate) · `local` (Ollama / vLLM / llama.cpp / LM Studio — `cost_usd = 0.0` by design) · `unknown` (no model name or no price-table match — also `0.0`, but treat as missing rather than free) |\n| `tokens_input` | Total input bucket: standard input + cache_read + cache_creation. The next two fields break that down. |\n| `tokens_cache_read` | Subset of `tokens_input` that hit the prompt cache; billed at ~10% of the input rate. |\n| `tokens_cache_write` | Subset of `tokens_input` that wrote to the prompt cache; billed at ~125% of the input rate. |\n| `context_used` | Latest assistant turn's input window size. Anthropic: `input_tokens + cache_read + cache_creation`. OpenAI / Codex: `prompt_tokens + cached_tokens`. |\n| `context_limit` | Model's `max_input_tokens` (LiteLLM-derived) or auto-promoted to the next standard window when an observed prompt exceeded it. |\n| `loaded_skills` | Names of Claude Code skills resolvable from `\u003ccwd\u003e/.claude/skills/\u003cname\u003e/SKILL.md` and `~/.claude/skills/\u003cname\u003e/SKILL.md`. Empty for non-Claude vendors. |\n| `read_bytes` / `write_bytes` | Cumulative IO since process start. Linux `/proc/\u003cpid\u003e/io`; macOS / Windows `sysinfo::Process::disk_usage().total_*`. 0 on *BSD (sysinfo gap). |\n| `writing_files` / `writing_dirs` | Open files with write access (and their parent dirs). Linux `/proc/\u003cpid\u003e/fdinfo`; macOS direct FFI to `proc_pidfdinfo`; Windows `NtQuerySystemInformation` + `DuplicateHandle`. Empty on *BSD. |\n| `reading_files`  | Files the process has open in read-only mode. Surfaces what the agent is reading right now (project files during context indexing, MCP server configs, hook scripts) — useful when CPU is up but no tokens are flowing. Native on Linux (`/proc/\u003cpid\u003e/fd`), macOS (libproc `proc_pidfdinfo` + BSD `FREAD`/`FWRITE` flags), Windows (`NtQuerySystemInformation` + `FILE_READ_DATA` access mask), FreeBSD (`libprocstat`). Empty on OpenBSD / NetBSD (kernel doesn't track per-fd paths). |\n| `children`       | Immediate child processes (`(pid, comm)` pairs) the agent has spawned. Captures hook invocations, MCP server processes, shell commands. Linux walks `/proc/\u003cpid\u003e/task/*/children`; macOS / Windows / *BSD reverse-walk sysinfo's parent map and bucket per pid. |\n| `net_established`| Count of established TCP connections the process owns. Non-zero indicates the agent is talking to an API / MCP server / network resource even when no tokens are visibly flowing. Linux parses `/proc/\u003cpid\u003e/net/tcp{,6}`; macOS uses libproc's `PROC_PIDFDSOCKETINFO` + `tcpsi_state` lookup; Windows calls `GetExtendedTcpTable` filtered to `MIB_TCP_STATE_ESTAB` + owning pid; FreeBSD walks libprocstat's filestat list and decodes the sockstat `proto` + `state` fields. 0 on OpenBSD / NetBSD. |\n| `read_rate_bps` / `write_rate_bps` | Per-tick disk-IO rate in bytes per second, computed as Δ(`read_bytes`/`write_bytes`) ÷ Δt against the previous snapshot. 0 on the first sample for any pid. Available wherever `read_bytes`/`write_bytes` is. |\n| `gpu_pct` / `gpu_mem_bytes` | NVIDIA GPU utilisation (0-100%) and VRAM usage attributed to this PID. Populated by parsing `nvidia-smi --query-compute-apps` once per snapshot; 0 on hosts without an NVIDIA GPU or when this PID isn't using it. AMD / Apple Silicon support is on the roadmap. |\n| `dangerous` | True when the cmdline includes `--dangerously-skip-permissions`, `--no-permissions`, `--allow-dangerous`, `--yolo`, or starts with `sudo claude` / `sudo codex`. |\n| `dangerous_flag` | When `dangerous` is true, the specific substring that triggered the classifier (e.g. `--dangerously-skip-permissions`). Empty otherwise. |\n| `tool_counts` | Top tools used in this session, sorted desc by call count: `[[name, count], ...]`. Capped at 8 entries. Vendor enrichers count `tool_use` records. |\n| `ppid_name` | Parent process command name — the launcher (`zsh`, `bash`, `fish`, `nu`, `tmux`, `code`, `kitty`, ...). Resolved from `/proc/\u003cppid\u003e/comm` on Linux, `sysinfo::Process::name()` elsewhere. |\n| `session_started_ms` | Unix ms timestamp of the session's first transcript record. Diverges from process start time when the agent was invoked with `--resume` against an older session. 0 if unknown. |\n\n---\n\n## Cost estimation\n\n### Price table\n\n`src/pricing_data.rs` is generated from\n[LiteLLM's `model_prices_and_context_window_backup.json`](https://github.com/BerriAI/litellm/blob/main/litellm/model_prices_and_context_window_backup.json)\nand contains roughly 1,800 model entries: input rate, output rate,\nand `max_input_tokens`. `.github/workflows/sync-prices.yml` re-runs\nthe sync nightly and opens a PR when upstream changes; each tagged\nrelease ships with the bundled snapshot. The `--once` footer and the\nhelp overlay stamp the snapshot date so the user knows its age:\n\n```\nprices as of 2026-04-30 (litellm community registry) — `--prices PATH` to override\n```\n\n`src/pricing.rs` layers a curated overlay on top of the generated\ntable for canonical Anthropic / OpenAI / Google SKUs (so the\ncanonical entries are stable across LiteLLM upstream churn), plus an\n**explicit local-model classifier**: model strings containing\n`ollama/`, `ollama:`, `lmstudio/`, `vllm/`, `llamacpp/`, `localhost:`,\n`127.0.0.1:`, or `huggingface/` short-circuit to `cost_basis = local`,\n`cost_usd = 0.0`. The popup labels these rows `local` instead of\n`$0` so it's clear there's no API expenditure happening (you may\nstill want to pair with `nvtop` / `powertop` to track local power\ndraw).\n\nLookup is **suffix-tolerant**: `claude-sonnet-4-7-20260101` resolves\nto `claude-sonnet-4-7` → `claude-sonnet-4` → `claude-sonnet` (up to\nfour hyphen segments stripped from the right) so dated revisions\ndon't need to be tracked individually.\n\n### Cache-aware pricing\n\nAnthropic's prompt-cache pricing has three rates per model:\n\n| Token bucket          | Rate vs standard input |\n| --------------------- | ---------------------- |\n| Standard input        | 1.00× |\n| Cache **write**       | 1.25× |\n| Cache **read**        | 0.10× |\n| Output                | per-model output rate |\n\nagtop tracks each bucket separately in `tokens_input`, `tokens_cache_read`,\nand `tokens_cache_write` (the first being the rolled-up sum of all\nthree). The cost computation in `pricing::cost_with_cache`:\n\n```\ncost = ((input - cache_read - cache_write) * input_per_mtok\n        +  cache_read                       * input_per_mtok * 0.10\n        +  cache_write                      * input_per_mtok * 1.25\n        +  output                           * output_per_mtok) / 1_000_000\n```\n\nPrior versions billed every input token at the full input rate and\noverestimated long-context Claude sessions by an order of magnitude\n(a 500K-token cache-heavy turn would otherwise cost ~$1.50 in the\nnaive accounting vs ~$0.18 in the correct one).\n\n### Overrides\n\nOverride the bundled table with `--prices PATH`:\n\n```toml\n# USD per 1,000,000 tokens.\n\n[models.\"my-private-model\"]\ninput_per_mtok   = 0.50\noutput_per_mtok  = 2.00\nmax_input_tokens = 200000   # optional; drives the context-window bar\n```\n\nUser entries merge over the bundled defaults; user values win on\ncollision. The same TOML is also accepted via the `AGTOP_PRICES` env\nvar.\n\nRegenerate the bundled table:\n\n```sh\npython3 scripts/sync_prices.py          # writes src/pricing_data.rs\npython3 scripts/sync_prices.py --check  # exit 1 if upstream drifted\n```\n\n---\n\n## Context window and skills\n\n### Context window\n\nFor each agent with a known model, agtop computes:\n\n- **`context_used`** — the *latest* assistant turn's input window\n  size. For Anthropic this is `usage.input_tokens +\n  cache_read_input_tokens + cache_creation_input_tokens` from the\n  most recent record. For OpenAI / Codex it's `usage.prompt_tokens +\n  input_tokens_details.cached_tokens`. This is the prompt size on\n  the *next* request, i.e. how full the model's window is right now.\n- **`context_limit`** — the model's `max_input_tokens` from the\n  bundled price table. Heuristics extend this:\n  - Model id contains `-1m` / `1m-context` / `-1000k` → 1,000,000\n  - Model id contains `-2m` → 2,000,000\n  - **Self-calibration**: when an observed prompt exceeds the\n    table-derived limit (which happens with undeclared 1M-context\n    variants — Claude Sonnet 4.5 1M ships under the same model id\n    `claude-sonnet-4-5` as the 200K SKU), the collector promotes\n    the limit to the next standard window — 128K → 200K → 256K →\n    400K → 1M → 2M — that contains the observed value plus 5%\n    headroom. The bar therefore never displays \u003e100%.\n\nThe detail popup renders these as a 24-cell bar with thresholds\ncalibrated against Claude Code's auto-compaction trigger:\n\n| Fill   | Bar colour | Meaning |\n| ------ | ---------- | ------- |\n| \u003c70%   | green      | comfortable |\n| 70–89% | amber      | starting to fill |\n| ≥90%   | red + \"approaching auto-compaction\" hint | act now if you want to control what's compacted |\n\nThe UI also clamps the displayed percentage at 100% as a final\ndefense; you should never see \"401%\" or similar.\n\n### Claude Code plugins\n\nagtop also surfaces the set of Claude Code **plugins** enabled for\nthe user (e.g. `caveman`, `frontend-design`, `wakatime`). These are\nresolved by parsing two host-level files and intersecting them:\n\n- `~/.claude/settings.json` — `enabledPlugins` map (`name@market: true`)\n- `~/.claude/plugins/installed_plugins.json` — installed list\n\nOnly plugins that are both **installed and enabled** show up. The\ndisplay name strips the `@\u003cmarketplace\u003e` suffix. Plugins are\nuser-global (not project-scoped), so the list is identical for every\nClaude session on the host. Implementation: `src/plugins.rs`.\n\n### Claude Code skills\n\nLoaded skills are detected by `src/skills.rs` scanning two roots in\npriority order:\n\n1. `\u003ccwd\u003e/.claude/skills/\u003cname\u003e/SKILL.md` — project-local\n2. `~/.claude/skills/\u003cname\u003e/SKILL.md` — user-global\n\nA skill is any subdirectory containing a `SKILL.md` file. Symlinks\nare skipped to keep the scan O(N) on the visible directory and to\nprevent a malicious skill dir symlinked to `/` from causing the\nscanner to walk the whole filesystem.\n\nThe detail popup always shows a `skills` line for Claude agents (even\nwhen zero are loaded — it tells you the feature is wired up but no\nskills are resolvable for that cwd) and lists the names when present.\nThe same data is in `--json` under `agents[].loaded_skills`.\n\nSkills detection is Claude Code-specific. Other vendors' skill\nformats aren't yet supported — PRs welcome.\n\n---\n\n## Detail popup\n\n`Enter` on any agent row opens the detail popup (or click the row).\nIt assembles every signal agtop has on that PID into one screen:\n\n```\n● BUSY claude  pid 404872  · zk-rollup-prover\nmodel       claude-opus-4-7\ncpu         16.3%  ▁▂▃▅▇█▇▅▃▂▁▂▄▆█▇▅\nmemory      598M rss · 2.1G vsize\nuptime      4m17s  ·  session 3d 7h (resumed)\nthreads     14    state R  ppid 12345 (zsh)\ndangerous   --dangerously-skip-permissions\ntokens      9.5M (5.8M in / 46k out)\nrate        ▁▂▁▄▇█▆▃▁▁▂▅▇█▇▅▃▂▁▁▁▂▃▅  84k/min avg · peak 312k\ncost        $4.21    api · prices as of 2026-04-30\ncache       97% hit  (5.7M of 5.8M input tok cached)  · saved $15.42 vs uncached\ncontext     ███████████████░░░░░░░░░  52%  (515k / 1M tok)  · ≈14m to compaction (+38k/min)\nskills      3 loaded   frontend-design, slack-tooler, sql-explorer\nplugins     3 enabled  caveman, frontend-design, wakatime\nsubagents   1 in flight\n              · code-reviewer: review the auth refactor\nsession     6163a95c-e18a-4a4c-a793\ntools       Bash 47 · Edit 23 · Read 12 · Grep 8 · Write 5\n\nbin         /usr/bin/claude\ncwd         /home/matt/code/zk-rollup-prover\ncmd         claude --dangerously-skip-permissions\nread        482M    write 12M\nwriting     /home/matt/code/zk-rollup-prover/circuits/main.rs\n\n  ─ Live preview ─────────────────────────────────────\n  › Reviewing the diff\n  → Bash: nargo prove --witness witness.tr\n  ← witness verified\n  → Edit: src/circuit/poseidon.rs\n```\n\nEach line is also accessible from `agtop --json` under\n`agents[].\u003cfield\u003e` so the same data drives dashboards.\n\nNotable computed values:\n\n- **`cache` line** — Anthropic prompt-caching saves ~90% on cached\n  input tokens. The \"saved\" figure is `cache_read × input_per_mtok ×\n  0.90` — the dollars you'd have spent at the standard input rate\n  minus what you actually spent at the discounted cache-read rate.\n- **`≈ Xm to compaction`** — collector keeps a per-PID\n  `(timestamp_ms, context_used)` ring (24 samples). When growth is\n  positive, slope-extrapolate to 95% of `context_limit` and render\n  the ETA + `+ tokens/min` rate. Goes silent when context isn't\n  growing.\n- **`uptime` vs `session`** — process uptime comes from `/proc`\n  (or sysinfo); session age comes from the JSONL's first record\n  timestamp. When they diverge by \u003e60s the line tags `(resumed)` —\n  the user invoked `claude --resume` and is continuing an older\n  conversation.\n- **`tools` line** — vendor enricher increments a counter on every\n  `tool_use` record; sorted desc, capped at 8, top 5 displayed.\n  Surfaces actual effort allocation (Bash-heavy session vs\n  Edit-heavy session vs Read-heavy session).\n- **`rate` sparkline** — per-tick token deltas for *this* PID,\n  rolling window of 24 samples. The collector keeps a per-pid\n  `prev_tokens_total` and pushes `(current - prev)` each tick;\n  pids that disappear are pruned. The right-side text reads\n  `\u003cavg\u003e/min · peak \u003cpeak\u003e` so a quiet baseline with bursts (typical\n  Claude session: idle while user types, spike per turn) reads\n  correctly. Hidden when no tokens have moved during the window.\n- **`plugins` line** — host-global plugin set (Claude Code only).\n  Resolved from `~/.claude/settings.json` (`enabledPlugins`)\n  intersected with `~/.claude/plugins/installed_plugins.json`. Same\n  list for every Claude row in the snapshot.\n- **`ppid_name`** — resolved from `/proc/\u003cppid\u003e/comm` (Linux) or\n  `sysinfo::Process::name()` (others). Reads the kernel's recorded\n  command name regardless of shell or launcher; works for `zsh`,\n  `bash`, `fish`, `nu`, `tmux`, `code`, `kitty`, `WindowsTerminal`,\n  whatever spawned the agent.\n- **`dangerous` line** — only present when the classifier flagged\n  the cmdline; shows the specific substring that triggered it\n  (e.g. `--yolo` vs `--dangerously-skip-permissions`) so the user\n  knows the exact permission level in play.\n\n---\n\n## Custom matchers\n\n```sh\n# repeatable -m flag\nagtop -m \"internal-bot=python.*src/agent\\.py\" \\\n      -m \"rag-worker=node.*workers/rag\\.js\"\n\n# or via env\nexport AGTOP_MATCH=\"internal-bot=python.*src/agent\\.py\"\n```\n\n`agtop --list-builtins` prints the canonical 20-pattern list.\n\n---\n\n## Platforms\n\nFull feature parity across Linux, macOS, Windows, and FreeBSD.\nOpenBSD / NetBSD lack kernel-level fd-path tracking so the\nwritable / reading file enumeration is empty there; everything\nelse works.\n\n|                        | Process metrics | Sessions / cost / context / skills | IO bytes | Writable files | Reading files | Children (tree) | Net established | `K` kill |\n| ---------------------- | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |\n| Linux x86_64 / aarch64 | `/proc` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |\n| macOS x86_64 / aarch64 | `sysinfo` | ✓ | ✓ | ✓ libproc | ✓ libproc | ✓ | ✓ libproc | ✓ |\n| Windows x86_64         | `sysinfo` | ✓ | ✓ | ✓ NtQSI    | ✓ NtQSI    | ✓ | ✓ GetExtendedTcpTable | ✓ TerminateProcess |\n| FreeBSD x86_64         | `sysinfo` | ✓ | ✓ | gap (sysinfo) | ✓ libprocstat | ✓ libprocstat | ✓ | ✓ libprocstat | ✓ |\n| OpenBSD / NetBSD       | `sysinfo` | ✓ | ✓ | gap (sysinfo) | gap (kernel)  | gap (kernel)  | ✓ | gap (priv) | ✓ |\n\nCI runs `cargo build --release \u0026\u0026 cargo test --release` on\nubuntu-latest, macos-latest, and windows-latest, plus\n`cargo check --release` on the cross-targets matrix\n(linux x86_64 + aarch64, macos x86_64 + aarch64, windows-msvc,\nwindows-gnu, freebsd-x86_64). Three integration tests exercise\nthe native FFI on real hardware every push:\n\n- `writing_files::tests::enumerates_self_open_writable_file` — opens\n  a tempfile O_WRONLY, asserts the path appears in `writing_files::read(self_pid)`.\n- `reading_files::tests::enumerates_self_open_readonly_file` — opens\n  the tempfile O_RDONLY, asserts it appears in `reading_files::read(self_pid)`.\n- `net_count::tests::counts_self_established_tcp` — opens a localhost\n  TCP loop (listener + connect + accept), asserts `net_count::established(self_pid) \u003e= 2`.\n\n24/24 tests pass on Linux. The macOS implementation was validated\non a real `mac-m4.metal` instance running macOS Sequoia 15.7.5\n(xnu-11417); the libproc `socket_fdinfo` layout (792 bytes,\n`tcpsi_state` at offset 320 within `socket_info`) and BSD-style\n`FREAD`/`FWRITE` flag interpretation in `fi_openflags` are pinned\nin code comments so future SDK drift surfaces as a test failure.\n\n---\n\n## Implementation notes\n\n### Linux: `/proc` walk\n\n`src/proc_.rs` reads `/proc/\u003cpid\u003e/{stat,cmdline,cwd,exe,io}` plus\n`/proc/\u003cpid\u003e/fdinfo/*` to enumerate writable FDs. CPU% is computed\nfrom `(utime + stime)` deltas against `/proc/stat`'s aggregate. PID\nreuse is guarded by keying the previous-sample cache on\n`(pid, starttime)` so a recycled pid can't produce a fictitious\ndelta. `read_writing_files` filters `/proc/\u003cpid\u003e/fd/*` by the\n`flags:` line in the matching `fdinfo` entry: anything with\n`O_WRONLY (1)` or `O_RDWR (2)` set is a write-mode handle. Pipes,\nsockets, anon-inodes, memfds, dmabufs, deleted files, and `/dev/`\nnodes are excluded.\n\n### macOS: direct FFI to libSystem\n\n`src/writing_files.rs` defines the four C structs needed\n(`proc_fdinfo`, `proc_fileinfo`, `vnode_info_path`,\n`vnode_fdinfowithpath`) and links directly against\n`libSystem.dylib`'s stable `proc_pidinfo` and `proc_pidfdinfo`\nsymbols (the `libproc` crate ships a typed wrapper for sockets only\nand gates the bindgen-generated vnode struct as `pub(crate)`, so\ndirect FFI is the simpler path). The flow:\n\n1. `proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0)` → required\n   buffer size.\n2. Allocate a `Vec\u003cproc_fdinfo\u003e` (capped at 4096 entries) and\n   re-call to fill it.\n3. For each entry where `proc_fdtype == PROX_FDTYPE_VNODE`, call\n   `proc_pidfdinfo(pid, fd, PROC_PIDFDVNODEPATHINFO, \u0026info, sizeof(info))`.\n4. Filter by `info.pfi.fi_openflags \u0026 (O_WRONLY | O_RDWR)`.\n5. Convert the NUL-terminated `vip_path` (1024-char buffer) into\n   `PathBuf`. Skip empty paths and `/dev/`.\n\n### Windows: NT handle table\n\nSame module, behind `cfg(windows)`:\n\n1. `NtQuerySystemInformation(SystemExtendedHandleInformation = 0x40, …)`\n   — global handle table for every process on the system. Loops on\n   `STATUS_INFO_LENGTH_MISMATCH` until the buffer is large enough\n   (capped at 64 MiB so a runaway query can't OOM agtop).\n2. Filter the entries by `unique_process_id == target_pid` and\n   `granted_access \u0026 FILE_GENERIC_WRITE != 0`.\n3. `OpenProcess(PROCESS_DUP_HANDLE, target_pid)` once per call.\n4. For each surviving entry, `DuplicateHandle` into the agtop\n   process so we can resolve the path. (This works without admin\n   for handles owned by processes the same user is running, which\n   is the agent-monitoring case.)\n5. `GetFileType(dup)` — non-blocking — must return `FILE_TYPE_DISK`\n   before we proceed.  Pipes (`FILE_TYPE_PIPE`), char devices and\n   network endpoints have `FILE_GENERIC_WRITE` in their granted\n   mask too, but `GetFinalPathNameByHandleW` will block\n   indefinitely on them (the kernel waits for the named-pipe server\n   to respond, which sometimes never happens — pre-2.4 this was\n   the root cause of the 6h CI timeout on `windows-latest`).\n6. `GetFinalPathNameByHandleW(dup, FILE_NAME_NORMALIZED)` →\n   wide-char path. Strip the `\\\\?\\` long-path prefix, drop\n   `\\Device\\…` paths.\n7. `CloseHandle(dup)` and `CloseHandle(proc_handle)`.\n\nThe whole call additionally runs inside a 2-second watchdog\nthread (`writing_files::read` on Windows spawns a worker via\n`std::thread::spawn` and uses `mpsc::recv_timeout`).  Even after\nthe `FILE_TYPE_DISK` filter, `GetFinalPathNameByHandleW` can still\nstall on remote / SMB mounts whose server is slow or down — the\nwatchdog returns an empty `Vec` rather than freezing the collector\ntick.\n\n### FreeBSD: libprocstat\n\n`writing_files.rs` links against `libprocstat` (shipped in the FreeBSD\nbase since 9.0) and walks the same data `fstat -p \u003cpid\u003e` exposes:\n\n1. `procstat_open_sysctl()` opens a procstat handle.\n2. `procstat_getprocs(ps, KERN_PROC_PID, target_pid, \u0026count)` looks\n   up the `kinfo_proc` for the target PID.\n3. `procstat_getfiles(ps, kproc, 0)` returns a `STAILQ` of\n   `filestat` structs.\n4. Iterate via the embedded `next.stqe_next` pointer; keep entries\n   with `fs_type == PS_FST_TYPE_VNODE` (real files) and\n   `fs_flags \u0026 PS_FST_FFLAG_WRITE`. Copy `fs_path` (skipping\n   `/dev/`).\n5. Free the lists, close the handle.\n\nThe FFI struct layout is bound to the public `\u003clibprocstat.h\u003e` ABI\nwhich has been stable since FreeBSD 9.0; `kinfo_proc` is treated\nopaquely so kernel-version drift can't corrupt our reads.\n\n### OpenBSD / NetBSD\n\nThe kvm_getfiles APIs return inode + dev pairs but no paths — the\nkernel never stores them. Reconstructing paths would need a\nfilesystem-wide reverse-walk per-tick which is both expensive and\nunreliable, so writable-FD enumeration is left empty on these\ntargets. Process metrics, sessions, cost, context, and skills all\nwork normally.\n\n---\n\n## Repo layout\n\n```\nagtop/\n├── Cargo.toml · Cargo.lock\n├── src/                              ~11 k lines · 24 tests\n│   ├── main.rs · cli.rs · theme.rs · collector.rs\n│   ├── ui/                          (multi-file module, since 2.4.0)\n│   │   ├── mod.rs                   App state, run(), key + mouse handlers, draw dispatcher\n│   │   ├── popup.rs                 detail / help / kill-confirm / filter-input draws\n│   │   ├── panels.rs                header / footer / cpu / mem / tokens / status / sessions\n│   │   └── agents.rs                agents table, sticky group header, column toggles, tree mode\n│   ├── pricing.rs · pricing_data.rs (auto-generated, ~1800 entries)\n│   ├── proc_.rs                     Linux /proc backend\n│   ├── sysbackend.rs                sysinfo backend (macOS / Windows / *BSD)\n│   ├── writing_files.rs             writable-FD enum (Linux + macOS FFI + Windows FFI + FreeBSD libprocstat)\n│   ├── reading_files.rs             read-only-FD enum, same per-OS dispatch (since 2.4.9)\n│   ├── net_count.rs                 ESTABLISHED-TCP count per pid (Linux /proc, macOS libproc, Windows GetExtendedTcpTable, FreeBSD libprocstat)\n│   ├── skills.rs                    Claude Code skill discovery\n│   ├── plugins.rs                   Claude Code plugin discovery (since 2.4.0)\n│   ├── claude.rs · codex.rs · goose.rs · aider.rs · gemini.rs · generic.rs\n│   └── sessions.rs · matchers.rs · model.rs · format.rs\n├── scripts/\n│   └── sync_prices.py                LiteLLM → pricing_data.rs sync\n├── packages/{npm,deb,pacman}/        build.sh per format\n├── homebrew/agtop.rb                 formula (templated by release.yml)\n├── .github/workflows/                ci.yml · release.yml · auto-tag.yml · sync-prices.yml\n└── docs/                             screenshots + capture pipeline\n```\n\n---\n\n## Distribution channels\n\nA version bump in `Cargo.toml` is the only manual step: `auto-tag.yml`\nwatches the file on `main`, pushes a matching `vX.Y.Z` tag, and the\nrelease workflow fans out to all three primary registries in parallel.\n\n| Channel        | Source of truth                          | Auto-published on tag |\n| -------------- | ---------------------------------------- | :-------------------: |\n| GitHub Release | `release.yml` build matrix (5 targets)   | ✓                     |\n| crates.io      | `Cargo.toml`                             | ✓                     |\n| npm            | `packages/npm/build.sh` (prebuilt shim)  | ✓                     |\n| AUR            | `packages/pacman/PKGBUILD`               | ✓                     |\n| Homebrew tap   | `homebrew/agtop.rb` → `MBrassey/homebrew-tap` | ✓                |\n| apt repo (deb) | `packages/deb/build.sh` → `MBrassey/apt` (gh-pages) | ✓                |\n| winget         | `~/code/agtop-winget-port/` → `microsoft/winget-pkgs`    | ✓ (one-line bump per release) |\n| FreeBSD ports  | `~/code/agtop-freebsd-port/` → `freebsd/freebsd-ports`   | ✓ (one-line bump per release) |\n\n\u003e **Snap Store** was retired in 2.4.2 — the `snap install lxd`\n\u003e bootstrap inside `snapcore/action-build` repeatedly failed with\n\u003e `error: unable to contact snap store` on the GitHub-hosted Ubuntu\n\u003e runner, breaking every release for no reason inside our control.\n\u003e The signed apt repo above covers the same audience (Debian /\n\u003e Ubuntu / Mint / Pop!_OS) and has been green every release.\n\u003e `snap/snapcraft.yaml` remains in the tree for anyone who still\n\u003e wants to build locally with `snapcraft pack`.\n\nCI publishes use repo secrets `CRATES_IO_TOKEN`, `NPM_TOKEN`,\n`AUR_SSH_PRIVATE_KEY`, `HOMEBREW_TAP_TOKEN`, `APT_REPO_TOKEN`, and\n`APT_REPO_GPG_PRIVATE_KEY`; the publish jobs idempotently skip when\nthe version is already on the destination registry, so re-pushing\nor re-tagging is safe.  The npm postinstall verifies the downloaded\nprebuilt against the `SHA256SUMS` file attached to each GitHub\nRelease before extracting.\n\nFor first-time install, Debian / Ubuntu users add the apt source\nonce:\n\n```sh\ncurl -fsSL https://mbrassey.github.io/apt/pubkey.asc \\\n  | sudo gpg --dearmor -o /etc/apt/keyrings/agtop.gpg\necho \"deb [signed-by=/etc/apt/keyrings/agtop.gpg] https://mbrassey.github.io/apt ./\" \\\n  | sudo tee /etc/apt/sources.list.d/agtop.list\nsudo apt update \u0026\u0026 sudo apt install agtop\n```\n\nSubsequent updates flow through `sudo apt update \u0026\u0026 sudo apt upgrade`\nlike any other apt package.  The repo's `Release` file is signed\nwith the key whose public half is at\n[mbrassey.github.io/apt/pubkey.asc](https://mbrassey.github.io/apt/pubkey.asc)\n(fingerprint `FC8BF673587134A114B205A0632F0658B478942A`).\n\n---\n\n## Troubleshooting\n\n| Symptom | Cause | Fix |\n| ------- | ----- | --- |\n| `agtop` shows \"0 active agents\" but Claude Code is running | The matcher didn't catch your launcher script | Add `-m \"claude=node.*claude\"` (or your binary's name) — `agtop --list-builtins` shows the canonical pattern. |\n| Cost / tokens / model columns empty for a Claude session | `~/.claude/projects/\u003cencoded-cwd\u003e/` not present yet (no turns since session started) | Wait for the first assistant response; agtop reads usage from JSONL only after Anthropic emits it. |\n| `local` cost on an Ollama row is correct but you want to track power draw | Outside agtop's scope — pair with `nvtop` / `powertop`. | n/a |\n| Header reads `mem 0/0B` on a non-Linux host | Pre-2.3 build (sysinfo backend hardcoded these to 0) | Upgrade to `agtop ≥ 2.3.0`. |\n| Per-process IO bytes / writing-files blank on macOS / Windows | Pre-2.3 build (Linux-only) | Upgrade to `agtop ≥ 2.3.0`; native FFI now populates both on macOS + Windows. |\n| Reading files / network count / children blank on macOS / Windows | Pre-2.4.9 build | Upgrade to `agtop ≥ 2.4.12`; macOS libproc + Windows GetExtendedTcpTable + sysinfo parent-walk now populate these fields on every OS. |\n| Per-process IO bytes / writing-files blank on FreeBSD | sysinfo doesn't expose disk IO and there's no portable cross-BSD FD-enumeration API | Out of scope — would need a FreeBSD-specific `procstat_getfiles` impl. |\n| Context-window bar shows \u003e100% on Claude Sonnet/Opus | Pre-2.3.1 build (didn't account for undeclared 1M-context variants) | Upgrade — the collector now self-calibrates the limit when an observed prompt exceeds the table-derived cap. |\n| Context-window bar amber/red but I can keep going | Fill = latest turn's prompt size; some agents trim cache between turns | Treat the bar as a leading indicator, not a hard threshold. |\n| Cost looks ~10× too high on long Claude sessions | Pre-2.3.1 build (cache_read tokens billed at full input rate) | Upgrade — cache reads are now billed at 0.1× input rate, cache writes at 1.25×, matching Anthropic's prompt-caching pricing. |\n| Skills line missing from popup | The agent isn't classified as `claude` (matched `node` or your custom matcher instead) | Verify with `agtop --json | jq '.agents[] | {label,model}'` — only `label == \"claude\"` rows scan for skills. |\n| Skills shows `0 loaded` but you have skills | Wrong cwd or skills are in a non-standard location | agtop scans `\u003ccwd\u003e/.claude/skills/\u003cname\u003e/SKILL.md` and `~/.claude/skills/\u003cname\u003e/SKILL.md`; symlinks are ignored by design. |\n| `--prices override.toml` silently ignored | TOML parse error went to stderr but `agtop` kept running on the bundled defaults | Re-run with `agtop --prices ./your.toml 2\u003e\u00261 | head -3` to see the parse error. |\n| `local` cost on an Ollama row is correct but you want to track power draw | Outside agtop's scope — pair with `nvtop` / `powertop` | n/a |\n| Tokens column shows the current session's count, not the project's all-time total | By design — `tokens_input` reflects the live session linked to the agent's PID | Sum `~/.claude/projects/\u003cencoded-cwd\u003e/*.jsonl` yourself with `jq` for project-cumulative totals. |\n\n---\n\n## FAQ\n\n**Does agtop make any network calls at runtime?** No. The only\nnetwork access is the npm postinstall, which downloads a prebuilt\nbinary from the GitHub Release and verifies its SHA256 against the\nrelease's `SHA256SUMS` before extracting.\n\n**Why is the context-window bar based on the latest turn?** Each\n`usage` block in a session transcript records the input window size\nat that turn — which is the prompt size on the *next* request. That\nsum is what counts against the model's context limit. Cached tokens\nhave a discounted price but still occupy context, so they're\nincluded.\n\n**Is there a config file?** No. Persistent settings live in shell\naliases, `AGTOP_MATCH` / `AGTOP_PRICES` env vars, or a `--prices`\nTOML.\n\n**Where are man pages / shell completions?** Not yet shipped.\n\n**Is the price table accurate?** It's a snapshot of LiteLLM's\ncommunity registry as of the date stamped in the `--once` footer and\nthe help overlay. Override with `--prices PATH` for private SKUs or\nwhen you need newer prices than the bundled snapshot.\n\n**How does this compare to `top` / `htop` / `btop` / `glances`?**\nThose are general-purpose process monitors and remain better at that\njob. agtop is narrower: it classifies and enriches AI-coding-agent\nprocesses specifically. Run both side by side if you want both views.\n\n---\n\n## License\n\nMIT — see [`LICENSE`](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmbrassey%2Fagtop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmbrassey%2Fagtop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmbrassey%2Fagtop/lists"}