claudemon 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.
@@ -0,0 +1,143 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ pip-wheel-metadata/
24
+ share/python-wheels/
25
+ *.egg-info/
26
+ .installed.cfg
27
+ *.egg
28
+ MANIFEST
29
+
30
+ # PyInstaller
31
+ *.manifest
32
+ *.spec
33
+
34
+ # Installer logs
35
+ pip-log.txt
36
+ pip-delete-this-directory.txt
37
+
38
+ # Unit test / coverage reports
39
+ htmlcov/
40
+ .tox/
41
+ .nox/
42
+ .coverage
43
+ .coverage.*
44
+ .cache
45
+ nosetests.xml
46
+ coverage.xml
47
+ *.cover
48
+ *.py,cover
49
+ .hypothesis/
50
+ .pytest_cache/
51
+
52
+ # Translations
53
+ *.mo
54
+ *.pot
55
+
56
+ # Django stuff:
57
+ *.log
58
+ local_settings.py
59
+ db.sqlite3
60
+ db.sqlite3-journal
61
+
62
+ # Flask stuff:
63
+ instance/
64
+ .webassets-cache
65
+
66
+ # Scrapy stuff:
67
+ .scrapy
68
+
69
+ # Sphinx documentation
70
+ docs/_build/
71
+
72
+ # PyBuilder
73
+ target/
74
+
75
+ # Jupyter Notebook
76
+ .ipynb_checkpoints
77
+
78
+ # IPython
79
+ profile_default/
80
+ ipython_config.py
81
+
82
+ # pyenv
83
+ .python-version
84
+
85
+ # pipenv
86
+ Pipfile.lock
87
+
88
+ # PEP 582
89
+ __pypackages__/
90
+
91
+ # Celery stuff
92
+ celerybeat-schedule
93
+ celerybeat.pid
94
+
95
+ # SageMath parsed files
96
+ *.sage.py
97
+
98
+ # Environments
99
+ .env
100
+ .venv
101
+ env/
102
+ venv/
103
+ ENV/
104
+ env.bak/
105
+ venv.bak/
106
+
107
+ # uv
108
+ # Note: uv.lock should be committed to ensure reproducible builds
109
+
110
+ # User skills directory (skills created with sutras new)
111
+ .claude/
112
+
113
+ # Project
114
+ plan.md
115
+
116
+ # Spyder project settings
117
+ .spyderproject
118
+ .spyproject
119
+
120
+ # Rope project settings
121
+ .ropeproject
122
+
123
+ # mkdocs documentation
124
+ /site
125
+
126
+ # mypy
127
+ .mypy_cache/
128
+ .dmypy.json
129
+ dmypy.json
130
+
131
+ # Pyre type checker
132
+ .pyre/
133
+
134
+ # IDEs
135
+ .vscode/
136
+ .idea/
137
+ *.swp
138
+ *.swo
139
+ *~
140
+
141
+ # OS
142
+ .DS_Store
143
+ Thumbs.db
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kumar Anirudha
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,59 @@
1
+ Metadata-Version: 2.4
2
+ Name: claudemon
3
+ Version: 0.1.0
4
+ Summary: Claude Usage Monitor TUI - monitor your Claude usage in real-time
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.14
8
+ Requires-Dist: httpx>=0.28.0
9
+ Requires-Dist: term-piechart>=0.1.0
10
+ Requires-Dist: textual>=1.0.0
11
+ Description-Content-Type: text/markdown
12
+
13
+ # claudemon
14
+
15
+ Claude Usage Monitor TUI - monitor your Claude Pro/Max plan quota in real-time.
16
+
17
+ [![PyPI - Version](https://img.shields.io/pypi/v/claudemon)](https://pypi.org/project/claudemon/)
18
+ [![PyPI Downloads](https://static.pepy.tech/badge/claudemon/month)](https://pypi.org/project/claudemon/)
19
+ ![PyPI - Status](https://img.shields.io/pypi/status/claudemon)
20
+ [![Open Source](https://img.shields.io/badge/open-source-brightgreen)](https://github.com/anistark/claudemon)
21
+ ![maintenance-status](https://img.shields.io/badge/maintenance-actively--developed-brightgreen.svg)
22
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
23
+
24
+ ## Install
25
+
26
+ Requires Python 3.14+ and [uv](https://docs.astral.sh/uv/).
27
+
28
+ ```sh
29
+ uv sync
30
+ ```
31
+
32
+ ## Setup
33
+
34
+ ```sh
35
+ # Full setup: OAuth token + optional Admin API key
36
+ uv run claudemon setup
37
+
38
+ # Admin API key only
39
+ uv run claudemon setup --api
40
+ ```
41
+
42
+ ## Usage
43
+
44
+ ```sh
45
+ # Launch the TUI dashboard
46
+ uv run claudemon
47
+
48
+ # Launch in API monitoring mode
49
+ uv run claudemon --mode api
50
+ ```
51
+
52
+ ### Keybindings
53
+
54
+ | Key | Action |
55
+ |-----|--------|
56
+ | `q` | Quit |
57
+ | `r` | Force refresh |
58
+ | `m` | Toggle API mode |
59
+ | `?` | Show help |
@@ -0,0 +1,47 @@
1
+ # claudemon
2
+
3
+ Claude Usage Monitor TUI - monitor your Claude Pro/Max plan quota in real-time.
4
+
5
+ [![PyPI - Version](https://img.shields.io/pypi/v/claudemon)](https://pypi.org/project/claudemon/)
6
+ [![PyPI Downloads](https://static.pepy.tech/badge/claudemon/month)](https://pypi.org/project/claudemon/)
7
+ ![PyPI - Status](https://img.shields.io/pypi/status/claudemon)
8
+ [![Open Source](https://img.shields.io/badge/open-source-brightgreen)](https://github.com/anistark/claudemon)
9
+ ![maintenance-status](https://img.shields.io/badge/maintenance-actively--developed-brightgreen.svg)
10
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
11
+
12
+ ## Install
13
+
14
+ Requires Python 3.14+ and [uv](https://docs.astral.sh/uv/).
15
+
16
+ ```sh
17
+ uv sync
18
+ ```
19
+
20
+ ## Setup
21
+
22
+ ```sh
23
+ # Full setup: OAuth token + optional Admin API key
24
+ uv run claudemon setup
25
+
26
+ # Admin API key only
27
+ uv run claudemon setup --api
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ```sh
33
+ # Launch the TUI dashboard
34
+ uv run claudemon
35
+
36
+ # Launch in API monitoring mode
37
+ uv run claudemon --mode api
38
+ ```
39
+
40
+ ### Keybindings
41
+
42
+ | Key | Action |
43
+ |-----|--------|
44
+ | `q` | Quit |
45
+ | `r` | Force refresh |
46
+ | `m` | Toggle API mode |
47
+ | `?` | Show help |
@@ -0,0 +1,44 @@
1
+ # Claudemon - Development Commands
2
+ # https://github.com/casey/just
3
+
4
+ # Default recipe to display help
5
+ default:
6
+ @just --list
7
+
8
+ # Format code with ruff
9
+ format:
10
+ uv run ruff format src/claudemon/
11
+
12
+ # Lint code with ruff
13
+ lint:
14
+ uv run ruff check src/claudemon/
15
+
16
+ # Fix linting issues automatically
17
+ fix:
18
+ uv run ruff check --fix src/claudemon/
19
+
20
+ # Type check with ty
21
+ check:
22
+ uv run ty check src/claudemon/
23
+
24
+ # Run all quality checks (format, lint, type check)
25
+ qa: format lint check
26
+
27
+ # Run tests
28
+ test:
29
+ uv run python -m pytest
30
+
31
+ # Build distribution packages
32
+ build:
33
+ uv build
34
+
35
+ # Clean build artifacts
36
+ clean:
37
+ rm -rf dist/ build/ *.egg-info
38
+ find . -type d -name __pycache__ -exec rm -rf {} +
39
+ find . -type f -name "*.pyc" -delete
40
+
41
+ # Publish to PyPI (requires credentials in ~/.pypirc)
42
+ publish: clean build
43
+ @echo "Publishing to PyPI..."
44
+ uv publish --token "$(grep -A2 '\[pypi\]' ~/.pypirc | grep password | cut -d'=' -f2- | xargs)"
@@ -0,0 +1,29 @@
1
+ [project]
2
+ name = "claudemon"
3
+ version = "0.1.0"
4
+ description = "Claude Usage Monitor TUI - monitor your Claude usage in real-time"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ requires-python = ">=3.14"
8
+ dependencies = [
9
+ "textual>=1.0.0",
10
+ "httpx>=0.28.0",
11
+ "term-piechart>=0.1.0",
12
+ ]
13
+
14
+ [project.scripts]
15
+ claudemon = "claudemon.__main__:main"
16
+
17
+ [build-system]
18
+ requires = ["hatchling"]
19
+ build-backend = "hatchling.build"
20
+
21
+ [tool.hatch.build.targets.wheel]
22
+ packages = ["src/claudemon"]
23
+
24
+ [dependency-groups]
25
+ dev = [
26
+ "pytest>=9.0.2",
27
+ "ruff>=0.15.0",
28
+ "ty>=0.0.15",
29
+ ]
@@ -0,0 +1,3 @@
1
+ """Claudemon — Claude Usage Monitor TUI."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,65 @@
1
+ """CLI entry point for claudemon."""
2
+
3
+ import sys
4
+
5
+
6
+ def main() -> None:
7
+ args = sys.argv[1:]
8
+
9
+ if args and args[0] == "setup":
10
+ _run_setup(args[1:])
11
+ return
12
+
13
+ mode = "quota"
14
+ if "--mode" in args:
15
+ idx = args.index("--mode")
16
+ if idx + 1 < len(args):
17
+ mode = args[idx + 1]
18
+
19
+ if "--help" in args or "-h" in args:
20
+ _print_help()
21
+ return
22
+
23
+ if "--version" in args:
24
+ from . import __version__
25
+
26
+ print(f"claudemon {__version__}")
27
+ return
28
+
29
+ from .app import ClaudemonApp
30
+
31
+ app = ClaudemonApp(mode=mode)
32
+ app.run()
33
+
34
+
35
+ def _run_setup(args: list[str]) -> None:
36
+ api_only = "--api" in args
37
+ from .auth import interactive_setup
38
+
39
+ interactive_setup(api_only=api_only)
40
+
41
+
42
+ def _print_help() -> None:
43
+ print(
44
+ """claudemon — Claude Usage Monitor TUI
45
+
46
+ Usage:
47
+ claudemon Launch the TUI dashboard
48
+ claudemon setup Interactive OAuth + API key setup
49
+ claudemon setup --api Configure Admin API key only
50
+ claudemon --mode api Launch in API monitoring mode
51
+
52
+ Options:
53
+ --help, -h Show this help message
54
+ --version Show version
55
+
56
+ Keybindings (in TUI):
57
+ q Quit
58
+ r Force refresh
59
+ m Toggle API mode
60
+ ? Show help"""
61
+ )
62
+
63
+
64
+ if __name__ == "__main__":
65
+ main()
@@ -0,0 +1,153 @@
1
+ """API clients for Claude quota and admin usage endpoints."""
2
+
3
+ from datetime import datetime
4
+
5
+ import httpx
6
+
7
+ from .models import ApiUsageData, ModelQuota, QuotaData, TokenData
8
+
9
+ OAUTH_USAGE_URL = "https://api.anthropic.com/api/oauth/usage"
10
+ ADMIN_MESSAGES_URL = "https://api.anthropic.com/v1/organizations/usage_report/messages"
11
+ ADMIN_COSTS_URL = "https://api.anthropic.com/v1/organizations/cost_report"
12
+
13
+
14
+ class QuotaFetchError(Exception):
15
+ pass
16
+
17
+
18
+ class AuthenticationError(QuotaFetchError):
19
+ pass
20
+
21
+
22
+ async def fetch_quota(oauth_token: str) -> QuotaData:
23
+ """Fetch quota usage from the OAuth usage endpoint."""
24
+ headers = {
25
+ "Authorization": f"Bearer {oauth_token}",
26
+ "Content-Type": "application/json",
27
+ }
28
+
29
+ async with httpx.AsyncClient(timeout=15.0) as client:
30
+ try:
31
+ resp = await client.get(OAUTH_USAGE_URL, headers=headers)
32
+ except httpx.RequestError as e:
33
+ raise QuotaFetchError(f"Network error: {e}") from e
34
+
35
+ if resp.status_code == 401:
36
+ raise AuthenticationError(
37
+ "OAuth token is invalid or expired. Run 'claudemon setup' to re-authenticate."
38
+ )
39
+ if resp.status_code == 403:
40
+ raise AuthenticationError(
41
+ "Access denied. Your token may lack the required permissions."
42
+ )
43
+ if resp.status_code != 200:
44
+ raise QuotaFetchError(
45
+ f"API returned status {resp.status_code}: {resp.text}"
46
+ )
47
+
48
+ data = resp.json()
49
+
50
+ return _parse_quota_response(data)
51
+
52
+
53
+ def _parse_quota_response(data: dict) -> QuotaData:
54
+ """Parse the OAuth usage API response into QuotaData."""
55
+ quota = QuotaData()
56
+
57
+ # Parse 5-hour window
58
+ five_hour = data.get("five_hour", data.get("fiveHour", {}))
59
+ if five_hour:
60
+ quota.five_hour_usage_pct = (
61
+ five_hour.get("utilization", five_hour.get("usage_pct", 0.0)) * 100
62
+ )
63
+ reset_at = five_hour.get("reset_at", five_hour.get("resetAt"))
64
+ if reset_at:
65
+ quota.five_hour_reset_time = _parse_iso_time(reset_at)
66
+
67
+ # Parse 7-day window
68
+ seven_day = data.get("seven_day", data.get("sevenDay", {}))
69
+ if seven_day:
70
+ quota.seven_day_usage_pct = (
71
+ seven_day.get("utilization", seven_day.get("usage_pct", 0.0)) * 100
72
+ )
73
+ reset_at = seven_day.get("reset_at", seven_day.get("resetAt"))
74
+ if reset_at:
75
+ quota.seven_day_reset_time = _parse_iso_time(reset_at)
76
+
77
+ # Parse model-specific quotas
78
+ models = data.get("models", data.get("model_quotas", []))
79
+ for m in models:
80
+ name = m.get("model", m.get("name", "unknown"))
81
+ usage = m.get("utilization", m.get("usage_pct", 0.0)) * 100
82
+ quota.model_quotas.append(ModelQuota(model_name=name, usage_pct=usage))
83
+
84
+ # Plan type
85
+ quota.plan_type = data.get("plan_type", data.get("planType", "pro"))
86
+
87
+ return quota
88
+
89
+
90
+ def _parse_iso_time(time_str: str) -> datetime:
91
+ """Parse an ISO 8601 timestamp."""
92
+ time_str = time_str.replace("Z", "+00:00")
93
+ return datetime.fromisoformat(time_str)
94
+
95
+
96
+ async def fetch_api_usage(admin_api_key: str) -> ApiUsageData:
97
+ """Fetch usage from the Admin API endpoints."""
98
+ headers = {
99
+ "x-api-key": admin_api_key,
100
+ "Content-Type": "application/json",
101
+ }
102
+
103
+ usage = ApiUsageData()
104
+
105
+ async with httpx.AsyncClient(timeout=15.0) as client:
106
+ # Fetch token usage
107
+ try:
108
+ resp = await client.get(ADMIN_MESSAGES_URL, headers=headers)
109
+ if resp.status_code == 200:
110
+ msg_data = resp.json()
111
+ usage.token_counts = _parse_token_usage(msg_data)
112
+ elif resp.status_code in (401, 403):
113
+ raise AuthenticationError(
114
+ "Admin API key is invalid. Run 'claudemon setup --api' to reconfigure."
115
+ )
116
+ except httpx.RequestError as e:
117
+ raise QuotaFetchError(f"Network error fetching token usage: {e}") from e
118
+
119
+ # Fetch costs
120
+ try:
121
+ resp = await client.get(ADMIN_COSTS_URL, headers=headers)
122
+ if resp.status_code == 200:
123
+ cost_data = resp.json()
124
+ usage.costs_usd = _parse_costs(cost_data)
125
+ except httpx.RequestError:
126
+ pass # Costs are optional
127
+
128
+ return usage
129
+
130
+
131
+ def _parse_token_usage(data: dict) -> TokenData:
132
+ """Parse token usage from admin API response."""
133
+ tokens = TokenData()
134
+ items = data.get("data", data.get("messages", []))
135
+ for item in items:
136
+ tokens.input_tokens += item.get("input_tokens", 0)
137
+ tokens.output_tokens += item.get("output_tokens", 0)
138
+ tokens.cache_read += item.get(
139
+ "cache_read_input_tokens", item.get("cache_read", 0)
140
+ )
141
+ tokens.cache_write += item.get(
142
+ "cache_creation_input_tokens", item.get("cache_write", 0)
143
+ )
144
+ return tokens
145
+
146
+
147
+ def _parse_costs(data: dict) -> float:
148
+ """Parse cost data from admin API response."""
149
+ items = data.get("data", data.get("costs", []))
150
+ total = 0.0
151
+ for item in items:
152
+ total += item.get("cost_usd", item.get("amount", 0.0))
153
+ return total