cctop-tui 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. cctop_tui-0.1.0/.claude/skills/cctop-setup/SKILL.md +102 -0
  2. cctop_tui-0.1.0/.github/workflows/ci.yml +40 -0
  3. cctop_tui-0.1.0/.gitignore +8 -0
  4. cctop_tui-0.1.0/.pre-commit-config.yaml +15 -0
  5. cctop_tui-0.1.0/CHANGELOG.md +34 -0
  6. cctop_tui-0.1.0/LICENSE +21 -0
  7. cctop_tui-0.1.0/PKG-INFO +173 -0
  8. cctop_tui-0.1.0/README.md +142 -0
  9. cctop_tui-0.1.0/SECURITY.md +56 -0
  10. cctop_tui-0.1.0/docs/hero.png +0 -0
  11. cctop_tui-0.1.0/pyproject.toml +75 -0
  12. cctop_tui-0.1.0/src/cctop/__init__.py +10 -0
  13. cctop_tui-0.1.0/src/cctop/app.py +501 -0
  14. cctop_tui-0.1.0/src/cctop/authctl.py +219 -0
  15. cctop_tui-0.1.0/src/cctop/cli.py +599 -0
  16. cctop_tui-0.1.0/src/cctop/codex.py +209 -0
  17. cctop_tui-0.1.0/src/cctop/codex_stats.py +131 -0
  18. cctop_tui-0.1.0/src/cctop/codex_usage.py +173 -0
  19. cctop_tui-0.1.0/src/cctop/collect.py +244 -0
  20. cctop_tui-0.1.0/src/cctop/manage.py +173 -0
  21. cctop_tui-0.1.0/src/cctop/models.py +146 -0
  22. cctop_tui-0.1.0/src/cctop/monitor.py +230 -0
  23. cctop_tui-0.1.0/src/cctop/pricing.py +94 -0
  24. cctop_tui-0.1.0/src/cctop/registry.py +73 -0
  25. cctop_tui-0.1.0/src/cctop/stats.py +154 -0
  26. cctop_tui-0.1.0/src/cctop/stats_screen.py +333 -0
  27. cctop_tui-0.1.0/src/cctop/status.py +50 -0
  28. cctop_tui-0.1.0/src/cctop/transcript.py +147 -0
  29. cctop_tui-0.1.0/src/cctop/usage.py +271 -0
  30. cctop_tui-0.1.0/tests/test_accounts.py +45 -0
  31. cctop_tui-0.1.0/tests/test_authctl.py +81 -0
  32. cctop_tui-0.1.0/tests/test_backoff.py +121 -0
  33. cctop_tui-0.1.0/tests/test_core.py +81 -0
  34. cctop_tui-0.1.0/tests/test_doctor.py +23 -0
  35. cctop_tui-0.1.0/tests/test_manage.py +179 -0
  36. cctop_tui-0.1.0/tests/test_parsers.py +231 -0
  37. cctop_tui-0.1.0/tests/test_privacy.py +65 -0
  38. cctop_tui-0.1.0/uv.lock +556 -0
@@ -0,0 +1,102 @@
1
+ ---
2
+ name: cctop-setup
3
+ description: Install and configure cctop, the multi-account Claude Code usage monitor, for the user. Use when the user wants to set up / install cctop, get it running, or add a Claude account (subscription) for it to track. STRICTLY ADDITIVE: this skill only installs and adds; it must never delete, remove, rewrite, or reconfigure any existing files, credentials, sessions, aliases, or shell config.
4
+ ---
5
+
6
+ # cctop setup
7
+
8
+ Set up cctop (a live TUI that monitors multiple Claude Code sessions and
9
+ subscription usage) for the user. Everything here is additive and safe.
10
+
11
+ ## Absolute safety rules (do not violate)
12
+
13
+ 1. **Never delete or remove anything** — no `rm`, no removing files, dirs,
14
+ sessions, accounts, aliases, or credentials. cctop itself has no delete
15
+ operation; do not add one or work around its absence.
16
+ 2. **Never modify existing configuration in place** — do not edit, rewrite, or
17
+ reorder existing lines in `~/.zshrc`, `~/.bashrc`, `settings.json`,
18
+ `.claude.json`, `config.toml`, or any other config the user already has. The
19
+ only permitted change to a shell rc file is the **append** performed by
20
+ `cctop add-account --alias NAME` (which backs the file up first and is
21
+ idempotent); without `--alias`, add-account never touches a shell rc file.
22
+ 3. **Never log out, revoke, or touch credentials** — do not run `claude logout`,
23
+ `codex logout`, delete `.credentials.json`, or read/print any token.
24
+ 4. **Confirm before any change outside the repo** — installing into the repo's
25
+ own `.venv` is fine without asking. Adding an account (which appends a shell
26
+ alias and creates a new `~/.claude-N` dir) requires explicit user
27
+ confirmation first; show the dry-run plan and wait for a yes.
28
+ 5. If the user asks to **remove/delete/reset** an account or setup, decline and
29
+ explain that cctop is intentionally additive-only to avoid ever breaking a
30
+ working setup. Offer to point them at the manual steps instead, but do not do
31
+ it for them.
32
+
33
+ ## Step 1 — Install
34
+
35
+ Work in the cloned repo directory. Prefer `uv`; fall back to `python -m venv`.
36
+
37
+ ```bash
38
+ # from the repo root
39
+ uv venv --python 3.12 && uv pip install -e .
40
+ # fallback if uv is unavailable:
41
+ # python3 -m venv .venv && .venv/bin/pip install -e .
42
+ ```
43
+
44
+ Verify it imports and runs (read-only):
45
+
46
+ ```bash
47
+ .venv/bin/cctop --once # one-shot snapshot; should print the table
48
+ .venv/bin/cctop accounts # lists discovered accounts (read-only)
49
+ ```
50
+
51
+ If `cctop --once` works, installation is done. Tell the user how to launch the
52
+ live TUI: `.venv/bin/cctop` (keys: `s` stats, `r` refresh limits, `R` refresh
53
+ token, `a` add account, `q` quit), and suggest they add a shell alias like
54
+ `alias cctop='<repo>/.venv/bin/cctop'` — but only append it, and only if they
55
+ want it.
56
+
57
+ ## Step 2 — Accounts (only if the user wants to add one)
58
+
59
+ cctop auto-discovers every `~/.claude` (cc-0) and `~/.claude-<N>` (cc-<N>)
60
+ account, so existing accounts need no setup — just confirm they show up in
61
+ `cctop accounts`. No shell alias is required for cctop to see an account.
62
+
63
+ To add a **new** subscription/account, use the built-in additive command. Always
64
+ show the dry-run first, get confirmation, then apply:
65
+
66
+ ```bash
67
+ .venv/bin/cctop add-account # dry-run: prints the plan, changes nothing
68
+ .venv/bin/cctop add-account --login # ONLY after the user confirms: create dir + sign in
69
+ .venv/bin/cctop add-account --from cc-0 --login # ...also clone cc-0's user config into the new account
70
+ .venv/bin/cctop add-account --alias cc-2 --login # ...and set an explicit cc-2 shell alias
71
+ ```
72
+
73
+ What each flag does, all additive:
74
+
75
+ - **No flag** creates only the next `~/.claude-N` dir (or reuses an existing
76
+ logged-out one). It does **not** write a shell alias.
77
+ - **`--alias NAME`** (opt-in only) backs up the rc file and appends one alias
78
+ line. Never automatic.
79
+ - **`--from ACCOUNT`** copies an allowlist of user config only (`CLAUDE.md`,
80
+ `settings.json`, `commands/ agents/ skills/ hooks/ output-styles/`); it never
81
+ copies `.claude.json`, credentials, or session state, never overwrites an
82
+ existing file, and never modifies the source.
83
+ - **`--login`** runs `claude auth login` scoped to the new dir so the user signs
84
+ in themselves (browser flow); it is isolated to the new account and cannot
85
+ touch another. This is a sign-**in**, never a logout.
86
+
87
+ After applying, if an alias was set tell the user to `source ~/.zshrc` (or open a
88
+ new shell) to use it. cctop picks the account up automatically either way.
89
+
90
+ ## Step 3 — Confirm
91
+
92
+ Run `cctop accounts` once more and show the user the result: each account with
93
+ its tier, whether a token is present, and its live session count. Point out that
94
+ usage limits and stats are read for free (no tokens consumed), and that nothing
95
+ was deleted or modified.
96
+
97
+ ## What NOT to do (recap)
98
+
99
+ - No `rm`, no `logout`, no editing existing config lines, no deleting sessions
100
+ or accounts, no reading/printing tokens.
101
+ - The only writes you may perform: create the repo `.venv`, and (with
102
+ confirmation) run `cctop add-account --apply`, which is additive and backed up.
@@ -0,0 +1,40 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ check:
10
+ name: ${{ matrix.os }} / py${{ matrix.python-version }}
11
+ runs-on: ${{ matrix.os }}
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ os: [macos-latest]
16
+ python-version: ["3.11", "3.12", "3.13"]
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - name: Install uv
21
+ uses: astral-sh/setup-uv@v3
22
+ with:
23
+ python-version: ${{ matrix.python-version }}
24
+
25
+ - name: Install project (with dev extras) into a venv
26
+ run: |
27
+ uv venv
28
+ uv pip install -e ".[dev]"
29
+
30
+ - name: Ruff lint
31
+ run: uv run ruff check src tests
32
+
33
+ - name: Ruff format
34
+ run: uv run ruff format --check src tests
35
+
36
+ - name: Mypy
37
+ run: uv run mypy
38
+
39
+ - name: Tests
40
+ run: uv run pytest -q
@@ -0,0 +1,8 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .DS_Store
8
+ references/
@@ -0,0 +1,15 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v4.6.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ - id: check-yaml
8
+ - id: check-toml
9
+ - id: check-added-large-files
10
+ - repo: https://github.com/astral-sh/ruff-pre-commit
11
+ rev: v0.6.9
12
+ hooks:
13
+ - id: ruff
14
+ args: [--fix]
15
+ - id: ruff-format
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project aims to
5
+ follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Added
10
+ - The USAGE panel now wraps account blocks onto multiple rows and sizes bars to
11
+ the terminal width, reflowing live on resize instead of overflowing when many
12
+ accounts or a narrow window are in play.
13
+ - Live TUI monitoring Claude Code and OpenAI Codex sessions side by side, with
14
+ real usage-limit gauges from the free `/api/oauth/usage` and Codex usage GETs.
15
+ - GitHub-style activity heatmaps and lifetime stats, per provider.
16
+ - Delegated token refresh (`R`): renews an account's OAuth token via the owning
17
+ binary, without cctop ever writing a credential.
18
+ - One-step account provisioning (`add-account --login` / the `a` key): create,
19
+ optionally clone user config from another account (`--from`, allowlisted), set
20
+ an explicit alias (`--alias`, never automatic), and sign in.
21
+ - `cctop doctor`: a read-only self-check (platform, claude/codex binaries, and
22
+ each account's credential, token expiry, tier, and live-session count).
23
+ - A never-started usage window (0% with no reset) now renders "no usage yet"
24
+ instead of a misleading empty 0% bar, and a `percent` value carrying a reset
25
+ epoch (a known upstream bug) is guarded instead of shown as a full bar.
26
+ - Rate-limit resilience: per-account backoff (Retry-After aware) that keeps the
27
+ last good usage numbers instead of blanking on a transient 429/5xx/network
28
+ failure, and a refresh that will not "refresh into another 429".
29
+ - Test suite, `ruff`/`mypy` gates, and CI across Python 3.11-3.13 on macOS
30
+ (macOS-only for the first release; Linux support is future work).
31
+ - `LICENSE` (MIT), `SECURITY.md`, and packaging metadata (distribution
32
+ `cctop-tui`; the command stays `cctop`).
33
+
34
+ [Unreleased]: https://github.com/ryanirl/cctop/commits/main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ryan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,173 @@
1
+ Metadata-Version: 2.4
2
+ Name: cctop-tui
3
+ Version: 0.1.0
4
+ Summary: A live terminal monitor for multiple Claude Code and OpenAI Codex accounts, in the spirit of nvtop/htop.
5
+ Project-URL: Homepage, https://github.com/ryanirl/cctop
6
+ Project-URL: Repository, https://github.com/ryanirl/cctop
7
+ Project-URL: Issues, https://github.com/ryanirl/cctop/issues
8
+ Author: Ryan 'RyanIRL' Peters
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: anthropic,claude,claude-code,cli,codex,monitoring,nvtop,openai,tui,usage
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: MacOS
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: System :: Monitoring
21
+ Classifier: Topic :: Utilities
22
+ Requires-Python: >=3.11
23
+ Requires-Dist: rich>=13.0
24
+ Requires-Dist: textual>=0.60
25
+ Provides-Extra: dev
26
+ Requires-Dist: mypy>=1.11; extra == 'dev'
27
+ Requires-Dist: pre-commit>=3.7; extra == 'dev'
28
+ Requires-Dist: pytest>=8.0; extra == 'dev'
29
+ Requires-Dist: ruff>=0.6; extra == 'dev'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # cctop
33
+
34
+ [![CI](https://github.com/ryanirl/cctop/actions/workflows/ci.yml/badge.svg)](https://github.com/ryanirl/cctop/actions/workflows/ci.yml)
35
+ [![License: MIT](https://img.shields.io/badge/License-MIT-teal.svg)](LICENSE)
36
+ ![Python](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13-blue.svg)
37
+ ![macOS](https://img.shields.io/badge/macOS-only-lightgrey.svg)
38
+
39
+ A live terminal monitor for your **Claude Code** and **OpenAI Codex** usage, in
40
+ the spirit of `nvtop`/`htop`. It **auto-detects your accounts** (your Claude
41
+ account and your Codex account both work out of the box, no setup), shows real
42
+ usage-limit gauges and every running session, and gives a GitHub-style view of
43
+ your activity over time. Running more than one Claude subscription? It picks
44
+ those up too.
45
+
46
+ Everything is read from files the tools already write plus a couple of free,
47
+ read-only usage reads; nothing here ever spends message quota. cctop touches
48
+ your credentials only to make those reads, never logs or transmits a token, and
49
+ is read-only except for two explicit, additive account actions. See
50
+ [SECURITY.md](SECURITY.md) for the full trust statement.
51
+
52
+ ![cctop](https://raw.githubusercontent.com/ryanirl/cctop/main/docs/hero.png)
53
+
54
+ ## What it shows
55
+
56
+ - **Usage limits** (top panel), per account, side by side: the 5-hour, weekly
57
+ (all models), and weekly (Fable) windows with utilization, reset countdown,
58
+ and the currently-binding window marked. These are the real numbers from
59
+ Anthropic's `GET /api/oauth/usage` endpoint (the same one `/usage` uses) — a
60
+ free read, no tokens consumed.
61
+ - **Sessions** (table): every live Claude Code process, tagged by account, with
62
+ status, model, cwd, context %, cumulative tokens, cost, and age. Selecting a
63
+ row opens a detail panel (full cwd, pid, version, token/cost/context
64
+ breakdown).
65
+ - **Statistics** (press `s`): a GitHub-style daily-activity heatmap, lifetime
66
+ totals (messages, sessions, tool calls, active days, longest streak, busiest
67
+ day), a top-models-by-tokens chart, and a per-account breakdown, all merged
68
+ from each account's `stats-cache.json`.
69
+
70
+ ## Install
71
+
72
+ Requires **macOS** and **Python 3.11+** (Linux support is planned).
73
+
74
+ Once published to PyPI (distribution name `cctop-tui`), the one-liners are:
75
+
76
+ ```bash
77
+ uv tool install cctop-tui # or: pipx install cctop-tui
78
+ uvx cctop-tui # run without installing (starts the `cctop` TUI)
79
+ ```
80
+
81
+ From a clone (current default while pre-release):
82
+
83
+ ```bash
84
+ cd cctop
85
+ uv venv --python 3.12
86
+ uv pip install -e . # add ".[dev]" for the test/lint tooling
87
+ cctop
88
+ ```
89
+
90
+ ## Usage
91
+
92
+ ```bash
93
+ cctop # launch the live TUI (default)
94
+ cctop --once # print a one-shot snapshot and exit
95
+ cctop --json # emit the snapshot as JSON (for scripting)
96
+ cctop --no-limits # skip the usage fetch (no network, session table only)
97
+
98
+ cctop accounts # list discovered accounts (read-only)
99
+ cctop doctor # read-only self-check (platform, binaries, token/expiry)
100
+ cctop add-account # provision a new account (dry-run; see Accounts below)
101
+ ```
102
+
103
+ TUI keys: `s` stats · `r` refresh limits · `R` refresh token · `a` add account ·
104
+ `q` quit.
105
+
106
+ ### Token refresh (`R`)
107
+
108
+ A Claude Code OAuth access token lives only ~12-15h, and the CLI refreshes it
109
+ lazily when you *use* an account, so an account you are merely monitoring drifts
110
+ past expiry and the usage read starts failing (`token expired`). Press `R` and
111
+ cctop asks the tool that owns the credential to renew it: it runs
112
+ `claude mcp list` under each account's config dir (a quota-free command whose
113
+ startup renews and rewrites the Keychain record). **cctop never writes a
114
+ credential itself** — it only triggers the owner binary and reads the result —
115
+ and it is a no-op on tokens that are still valid. An account whose refresh token
116
+ is itself dead reports "needs re-login" (only a fresh `/login` can fix that).
117
+
118
+ ### Accounts
119
+
120
+ cctop **auto-detects your accounts** with zero setup: your Claude Code account
121
+ (`~/.claude`) and your Codex account (`~/.codex`) are picked up automatically.
122
+ For most people that is the whole story: install, run `cctop`, done. Run
123
+ `cctop accounts` to see what it found.
124
+
125
+ **Multiple Claude accounts (optional).** If you run more than one Claude
126
+ subscription in separate config dirs (via `CLAUDE_CONFIG_DIR`), cctop shows them
127
+ all side by side. To add one without leaving cctop, press `a` (or run
128
+ `cctop add-account --login`): it creates the config dir, optionally clones your
129
+ existing config, and signs you in. It is **strictly additive** — it never
130
+ deletes, overwrites, or modifies existing config, credentials, or sessions, and
131
+ only writes a shell alias if you explicitly ask for one.
132
+
133
+ ## How it works
134
+
135
+ | Data | Claude source | Codex source | Cost |
136
+ |---|---|---|---|
137
+ | Live sessions | `~/.claude*/sessions/<pid>.json` | `ps` + `~/.codex/sessions/` rollouts | free (file/ps) |
138
+ | Tokens / context | transcript JSONL, tailed | rollout `token_count` events | free (file) |
139
+ | Usage limits | `GET /api/oauth/usage` (Keychain token) | `GET chatgpt.com/backend-api/codex/usage` (`~/.codex/auth.json` token) | free (GET) |
140
+ | Statistics | `~/.claude*/stats-cache.json` | aggregated from rollout files | free (file) |
141
+
142
+ ## Providers
143
+
144
+ cctop tracks both **Claude Code** and **OpenAI Codex** side by side. Claude
145
+ accounts are `cc-0`/`cc-1`/…; a Codex account (`cx-0`, from `~/.codex`) is added
146
+ automatically when present. Usage windows and stats are read per provider (they
147
+ differ in shape: Codex exposes primary/secondary windows plus per-model limits,
148
+ and its stats come from rollout files rather than a stats cache). Codex sessions
149
+ show token/context but no dollar cost, since Codex is subscription-based.
150
+
151
+ The credential for the usage read is resolved per account from the macOS
152
+ Keychain service `Claude Code-credentials-<sha256(config_dir)[:8]>`, used only to
153
+ authenticate to Anthropic's own API, never logged or persisted. The usage
154
+ response's org is verified against the account so one account can never show
155
+ another's numbers.
156
+
157
+ Undocumented, version-internal formats (the sessions registry, transcript
158
+ schema, usage JSON) are all read defensively and isolated to the collector core
159
+ (`registry`, `transcript`, `usage`, `stats`, `monitor`, `pricing`, `status`,
160
+ `codex*` for the Codex provider, `authctl` for delegated token refresh, and
161
+ `manage` for additive account provisioning), with a thin Textual presentation
162
+ layer on top.
163
+
164
+ ## Notes
165
+
166
+ - Pricing (`pricing.py`) is a small editable table; update it as rates change.
167
+ - The palette is the house teal (`#20B2AA`) on monochrome terminal. Usage bars
168
+ stay teal even when maxed (fullness is the signal); red is reserved for a
169
+ blocked or dead session.
170
+
171
+ ## Author
172
+
173
+ Ryan 'RyanIRL' Peters
@@ -0,0 +1,142 @@
1
+ # cctop
2
+
3
+ [![CI](https://github.com/ryanirl/cctop/actions/workflows/ci.yml/badge.svg)](https://github.com/ryanirl/cctop/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-teal.svg)](LICENSE)
5
+ ![Python](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13-blue.svg)
6
+ ![macOS](https://img.shields.io/badge/macOS-only-lightgrey.svg)
7
+
8
+ A live terminal monitor for your **Claude Code** and **OpenAI Codex** usage, in
9
+ the spirit of `nvtop`/`htop`. It **auto-detects your accounts** (your Claude
10
+ account and your Codex account both work out of the box, no setup), shows real
11
+ usage-limit gauges and every running session, and gives a GitHub-style view of
12
+ your activity over time. Running more than one Claude subscription? It picks
13
+ those up too.
14
+
15
+ Everything is read from files the tools already write plus a couple of free,
16
+ read-only usage reads; nothing here ever spends message quota. cctop touches
17
+ your credentials only to make those reads, never logs or transmits a token, and
18
+ is read-only except for two explicit, additive account actions. See
19
+ [SECURITY.md](SECURITY.md) for the full trust statement.
20
+
21
+ ![cctop](https://raw.githubusercontent.com/ryanirl/cctop/main/docs/hero.png)
22
+
23
+ ## What it shows
24
+
25
+ - **Usage limits** (top panel), per account, side by side: the 5-hour, weekly
26
+ (all models), and weekly (Fable) windows with utilization, reset countdown,
27
+ and the currently-binding window marked. These are the real numbers from
28
+ Anthropic's `GET /api/oauth/usage` endpoint (the same one `/usage` uses) — a
29
+ free read, no tokens consumed.
30
+ - **Sessions** (table): every live Claude Code process, tagged by account, with
31
+ status, model, cwd, context %, cumulative tokens, cost, and age. Selecting a
32
+ row opens a detail panel (full cwd, pid, version, token/cost/context
33
+ breakdown).
34
+ - **Statistics** (press `s`): a GitHub-style daily-activity heatmap, lifetime
35
+ totals (messages, sessions, tool calls, active days, longest streak, busiest
36
+ day), a top-models-by-tokens chart, and a per-account breakdown, all merged
37
+ from each account's `stats-cache.json`.
38
+
39
+ ## Install
40
+
41
+ Requires **macOS** and **Python 3.11+** (Linux support is planned).
42
+
43
+ Once published to PyPI (distribution name `cctop-tui`), the one-liners are:
44
+
45
+ ```bash
46
+ uv tool install cctop-tui # or: pipx install cctop-tui
47
+ uvx cctop-tui # run without installing (starts the `cctop` TUI)
48
+ ```
49
+
50
+ From a clone (current default while pre-release):
51
+
52
+ ```bash
53
+ cd cctop
54
+ uv venv --python 3.12
55
+ uv pip install -e . # add ".[dev]" for the test/lint tooling
56
+ cctop
57
+ ```
58
+
59
+ ## Usage
60
+
61
+ ```bash
62
+ cctop # launch the live TUI (default)
63
+ cctop --once # print a one-shot snapshot and exit
64
+ cctop --json # emit the snapshot as JSON (for scripting)
65
+ cctop --no-limits # skip the usage fetch (no network, session table only)
66
+
67
+ cctop accounts # list discovered accounts (read-only)
68
+ cctop doctor # read-only self-check (platform, binaries, token/expiry)
69
+ cctop add-account # provision a new account (dry-run; see Accounts below)
70
+ ```
71
+
72
+ TUI keys: `s` stats · `r` refresh limits · `R` refresh token · `a` add account ·
73
+ `q` quit.
74
+
75
+ ### Token refresh (`R`)
76
+
77
+ A Claude Code OAuth access token lives only ~12-15h, and the CLI refreshes it
78
+ lazily when you *use* an account, so an account you are merely monitoring drifts
79
+ past expiry and the usage read starts failing (`token expired`). Press `R` and
80
+ cctop asks the tool that owns the credential to renew it: it runs
81
+ `claude mcp list` under each account's config dir (a quota-free command whose
82
+ startup renews and rewrites the Keychain record). **cctop never writes a
83
+ credential itself** — it only triggers the owner binary and reads the result —
84
+ and it is a no-op on tokens that are still valid. An account whose refresh token
85
+ is itself dead reports "needs re-login" (only a fresh `/login` can fix that).
86
+
87
+ ### Accounts
88
+
89
+ cctop **auto-detects your accounts** with zero setup: your Claude Code account
90
+ (`~/.claude`) and your Codex account (`~/.codex`) are picked up automatically.
91
+ For most people that is the whole story: install, run `cctop`, done. Run
92
+ `cctop accounts` to see what it found.
93
+
94
+ **Multiple Claude accounts (optional).** If you run more than one Claude
95
+ subscription in separate config dirs (via `CLAUDE_CONFIG_DIR`), cctop shows them
96
+ all side by side. To add one without leaving cctop, press `a` (or run
97
+ `cctop add-account --login`): it creates the config dir, optionally clones your
98
+ existing config, and signs you in. It is **strictly additive** — it never
99
+ deletes, overwrites, or modifies existing config, credentials, or sessions, and
100
+ only writes a shell alias if you explicitly ask for one.
101
+
102
+ ## How it works
103
+
104
+ | Data | Claude source | Codex source | Cost |
105
+ |---|---|---|---|
106
+ | Live sessions | `~/.claude*/sessions/<pid>.json` | `ps` + `~/.codex/sessions/` rollouts | free (file/ps) |
107
+ | Tokens / context | transcript JSONL, tailed | rollout `token_count` events | free (file) |
108
+ | Usage limits | `GET /api/oauth/usage` (Keychain token) | `GET chatgpt.com/backend-api/codex/usage` (`~/.codex/auth.json` token) | free (GET) |
109
+ | Statistics | `~/.claude*/stats-cache.json` | aggregated from rollout files | free (file) |
110
+
111
+ ## Providers
112
+
113
+ cctop tracks both **Claude Code** and **OpenAI Codex** side by side. Claude
114
+ accounts are `cc-0`/`cc-1`/…; a Codex account (`cx-0`, from `~/.codex`) is added
115
+ automatically when present. Usage windows and stats are read per provider (they
116
+ differ in shape: Codex exposes primary/secondary windows plus per-model limits,
117
+ and its stats come from rollout files rather than a stats cache). Codex sessions
118
+ show token/context but no dollar cost, since Codex is subscription-based.
119
+
120
+ The credential for the usage read is resolved per account from the macOS
121
+ Keychain service `Claude Code-credentials-<sha256(config_dir)[:8]>`, used only to
122
+ authenticate to Anthropic's own API, never logged or persisted. The usage
123
+ response's org is verified against the account so one account can never show
124
+ another's numbers.
125
+
126
+ Undocumented, version-internal formats (the sessions registry, transcript
127
+ schema, usage JSON) are all read defensively and isolated to the collector core
128
+ (`registry`, `transcript`, `usage`, `stats`, `monitor`, `pricing`, `status`,
129
+ `codex*` for the Codex provider, `authctl` for delegated token refresh, and
130
+ `manage` for additive account provisioning), with a thin Textual presentation
131
+ layer on top.
132
+
133
+ ## Notes
134
+
135
+ - Pricing (`pricing.py`) is a small editable table; update it as rates change.
136
+ - The palette is the house teal (`#20B2AA`) on monochrome terminal. Usage bars
137
+ stay teal even when maxed (fullness is the signal); red is reserved for a
138
+ blocked or dead session.
139
+
140
+ ## Author
141
+
142
+ Ryan 'RyanIRL' Peters
@@ -0,0 +1,56 @@
1
+ # Security & trust
2
+
3
+ cctop reads local files that Claude Code and Codex already write, plus a small
4
+ number of free, read-only API calls. It touches OAuth tokens only to the extent
5
+ required to make those reads. This document states exactly what it does and does
6
+ not do, because a tool that sits next to your credentials should be auditable.
7
+
8
+ ## What cctop reads
9
+
10
+ | Source | Purpose |
11
+ |---|---|
12
+ | `~/.claude*/sessions/<pid>.json`, transcripts, `stats-cache.json` | live sessions, tokens, cost, activity |
13
+ | `~/.codex/sessions/**` rollout files, `ps` | live Codex sessions and stats |
14
+ | `GET https://api.anthropic.com/api/oauth/usage` | real Claude usage limits (the endpoint `/usage` uses) |
15
+ | `GET https://chatgpt.com/backend-api/codex/usage` | real Codex usage limits |
16
+ | macOS Keychain item `Claude Code-credentials-<hash>`, or `~/.claude*/.credentials.json` | the OAuth token used to authenticate the two GETs above |
17
+ | `~/.codex/auth.json` | the token used to authenticate the Codex usage GET |
18
+
19
+ All of it is read-only. The two usage GETs are plain reads that consume no
20
+ message quota.
21
+
22
+ ## What cctop does with your token
23
+
24
+ - It reads the token for an account **only from that account's own store** (its
25
+ per-config-dir Keychain service or its own credentials file), never from a
26
+ shared default, so one account can never be authenticated with another's
27
+ token. The usage response's organization is additionally verified against the
28
+ account.
29
+ - The token is placed in the `Authorization` header of the usage GET and used
30
+ nowhere else.
31
+ - The token is **never** logged, printed, written to disk by cctop, included in
32
+ `--json` output, or transmitted anywhere except the provider's own API
33
+ (`api.anthropic.com` / `chatgpt.com`) that issued it.
34
+
35
+ ## The only things cctop ever writes
36
+
37
+ cctop is read-only except for two explicit, additive actions you invoke:
38
+
39
+ 1. **Account provisioning** (`add-account`, the `a` key): creates a new
40
+ `~/.claude-N` directory; with `--alias` appends one shell-rc line (after
41
+ backing the file up); with `--from` copies an allowlist of *user config*
42
+ (`CLAUDE.md`, `settings.json`, `commands/agents/skills/hooks/output-styles`)
43
+ — never credentials, identity (`.claude.json`), or session state. It never
44
+ deletes, overwrites, or edits existing lines.
45
+ 2. **Token refresh** (the `R` key): runs `claude mcp list` for an account so the
46
+ **Claude Code binary itself** renews its own token at startup (a quota-free,
47
+ non-interactive command). cctop does not write the credential; it delegates
48
+ to the tool that owns it.
49
+
50
+ There is deliberately **no** delete, logout, or credential-writing path in
51
+ cctop's own code.
52
+
53
+ ## Reporting
54
+
55
+ This is a personal project; open an issue for anything that looks wrong. If you
56
+ find a genuine credential-handling flaw, please report it privately first.
Binary file
@@ -0,0 +1,75 @@
1
+ [project]
2
+ name = "cctop-tui"
3
+ version = "0.1.0"
4
+ description = "A live terminal monitor for multiple Claude Code and OpenAI Codex accounts, in the spirit of nvtop/htop."
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ authors = [{ name = "Ryan 'RyanIRL' Peters" }]
10
+ keywords = [
11
+ "claude",
12
+ "claude-code",
13
+ "codex",
14
+ "anthropic",
15
+ "openai",
16
+ "tui",
17
+ "monitoring",
18
+ "usage",
19
+ "nvtop",
20
+ "cli",
21
+ ]
22
+ classifiers = [
23
+ "Development Status :: 4 - Beta",
24
+ "Environment :: Console",
25
+ "Intended Audience :: Developers",
26
+ "Operating System :: MacOS",
27
+ "Programming Language :: Python :: 3 :: Only",
28
+ "Programming Language :: Python :: 3.11",
29
+ "Programming Language :: Python :: 3.12",
30
+ "Programming Language :: Python :: 3.13",
31
+ "Topic :: Utilities",
32
+ "Topic :: System :: Monitoring",
33
+ ]
34
+ dependencies = ["rich>=13.0", "textual>=0.60"]
35
+
36
+ [project.optional-dependencies]
37
+ dev = ["pytest>=8.0", "ruff>=0.6", "mypy>=1.11", "pre-commit>=3.7"]
38
+
39
+ [project.urls]
40
+ Homepage = "https://github.com/ryanirl/cctop"
41
+ Repository = "https://github.com/ryanirl/cctop"
42
+ Issues = "https://github.com/ryanirl/cctop/issues"
43
+
44
+ [project.scripts]
45
+ cctop = "cctop.cli:main"
46
+
47
+ [tool.pytest.ini_options]
48
+ testpaths = ["tests"]
49
+
50
+ [build-system]
51
+ requires = ["hatchling"]
52
+ build-backend = "hatchling.build"
53
+
54
+ [tool.hatch.build.targets.wheel]
55
+ packages = ["src/cctop"]
56
+
57
+ [tool.ruff]
58
+ line-length = 100
59
+ target-version = "py311"
60
+ src = ["src", "tests"]
61
+
62
+ [tool.ruff.lint]
63
+ select = ["E", "F", "I", "UP", "B", "W", "C4"]
64
+ # B008: several call sites legitimately use defaults from calls.
65
+ # UP017: the code consistently uses timezone.utc; rewriting to datetime.UTC is
66
+ # churn without benefit.
67
+ ignore = ["B008", "UP017"]
68
+
69
+ [tool.mypy]
70
+ python_version = "3.11"
71
+ files = ["src/cctop"]
72
+ ignore_missing_imports = true
73
+ check_untyped_defs = true
74
+ warn_redundant_casts = true
75
+ no_implicit_optional = true