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.
- cctop_tui-0.1.0/.claude/skills/cctop-setup/SKILL.md +102 -0
- cctop_tui-0.1.0/.github/workflows/ci.yml +40 -0
- cctop_tui-0.1.0/.gitignore +8 -0
- cctop_tui-0.1.0/.pre-commit-config.yaml +15 -0
- cctop_tui-0.1.0/CHANGELOG.md +34 -0
- cctop_tui-0.1.0/LICENSE +21 -0
- cctop_tui-0.1.0/PKG-INFO +173 -0
- cctop_tui-0.1.0/README.md +142 -0
- cctop_tui-0.1.0/SECURITY.md +56 -0
- cctop_tui-0.1.0/docs/hero.png +0 -0
- cctop_tui-0.1.0/pyproject.toml +75 -0
- cctop_tui-0.1.0/src/cctop/__init__.py +10 -0
- cctop_tui-0.1.0/src/cctop/app.py +501 -0
- cctop_tui-0.1.0/src/cctop/authctl.py +219 -0
- cctop_tui-0.1.0/src/cctop/cli.py +599 -0
- cctop_tui-0.1.0/src/cctop/codex.py +209 -0
- cctop_tui-0.1.0/src/cctop/codex_stats.py +131 -0
- cctop_tui-0.1.0/src/cctop/codex_usage.py +173 -0
- cctop_tui-0.1.0/src/cctop/collect.py +244 -0
- cctop_tui-0.1.0/src/cctop/manage.py +173 -0
- cctop_tui-0.1.0/src/cctop/models.py +146 -0
- cctop_tui-0.1.0/src/cctop/monitor.py +230 -0
- cctop_tui-0.1.0/src/cctop/pricing.py +94 -0
- cctop_tui-0.1.0/src/cctop/registry.py +73 -0
- cctop_tui-0.1.0/src/cctop/stats.py +154 -0
- cctop_tui-0.1.0/src/cctop/stats_screen.py +333 -0
- cctop_tui-0.1.0/src/cctop/status.py +50 -0
- cctop_tui-0.1.0/src/cctop/transcript.py +147 -0
- cctop_tui-0.1.0/src/cctop/usage.py +271 -0
- cctop_tui-0.1.0/tests/test_accounts.py +45 -0
- cctop_tui-0.1.0/tests/test_authctl.py +81 -0
- cctop_tui-0.1.0/tests/test_backoff.py +121 -0
- cctop_tui-0.1.0/tests/test_core.py +81 -0
- cctop_tui-0.1.0/tests/test_doctor.py +23 -0
- cctop_tui-0.1.0/tests/test_manage.py +179 -0
- cctop_tui-0.1.0/tests/test_parsers.py +231 -0
- cctop_tui-0.1.0/tests/test_privacy.py +65 -0
- 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,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
|
cctop_tui-0.1.0/LICENSE
ADDED
|
@@ -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.
|
cctop_tui-0.1.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://github.com/ryanirl/cctop/actions/workflows/ci.yml)
|
|
35
|
+
[](LICENSE)
|
|
36
|
+

|
|
37
|
+

|
|
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
|
+

|
|
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
|
+
[](https://github.com/ryanirl/cctop/actions/workflows/ci.yml)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+

|
|
6
|
+

|
|
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
|
+

|
|
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
|