clawmeter 0.7.4__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 (71) hide show
  1. clawmeter-0.7.4/.dockerignore +10 -0
  2. clawmeter-0.7.4/.github/workflows/ci.yml +30 -0
  3. clawmeter-0.7.4/.github/workflows/publish.yml +148 -0
  4. clawmeter-0.7.4/.gitignore +211 -0
  5. clawmeter-0.7.4/CHANGELOG.md +135 -0
  6. clawmeter-0.7.4/CLAUDE.md +101 -0
  7. clawmeter-0.7.4/Dockerfile +24 -0
  8. clawmeter-0.7.4/LICENSE +21 -0
  9. clawmeter-0.7.4/PKG-INFO +621 -0
  10. clawmeter-0.7.4/README.md +586 -0
  11. clawmeter-0.7.4/assets/clawmeter.png +0 -0
  12. clawmeter-0.7.4/docker-compose.yml +19 -0
  13. clawmeter-0.7.4/docs/SPEC.md +2734 -0
  14. clawmeter-0.7.4/docs/research/ollama-v0.7.0-research.md +649 -0
  15. clawmeter-0.7.4/pyproject.toml +70 -0
  16. clawmeter-0.7.4/src/clawmeter/__init__.py +8 -0
  17. clawmeter-0.7.4/src/clawmeter/__main__.py +39 -0
  18. clawmeter-0.7.4/src/clawmeter/_version.py +24 -0
  19. clawmeter-0.7.4/src/clawmeter/cache.py +277 -0
  20. clawmeter-0.7.4/src/clawmeter/cli.py +1151 -0
  21. clawmeter-0.7.4/src/clawmeter/config.py +256 -0
  22. clawmeter-0.7.4/src/clawmeter/core.py +205 -0
  23. clawmeter-0.7.4/src/clawmeter/daemon.py +410 -0
  24. clawmeter-0.7.4/src/clawmeter/formatters/__init__.py +13 -0
  25. clawmeter-0.7.4/src/clawmeter/formatters/json_fmt.py +107 -0
  26. clawmeter-0.7.4/src/clawmeter/formatters/monitor_fmt.py +713 -0
  27. clawmeter-0.7.4/src/clawmeter/formatters/table_fmt.py +157 -0
  28. clawmeter-0.7.4/src/clawmeter/history.py +901 -0
  29. clawmeter-0.7.4/src/clawmeter/migrate.py +123 -0
  30. clawmeter-0.7.4/src/clawmeter/models.py +107 -0
  31. clawmeter-0.7.4/src/clawmeter/providers/__init__.py +44 -0
  32. clawmeter-0.7.4/src/clawmeter/providers/base.py +98 -0
  33. clawmeter-0.7.4/src/clawmeter/providers/claude.py +311 -0
  34. clawmeter-0.7.4/src/clawmeter/providers/grok.py +560 -0
  35. clawmeter-0.7.4/src/clawmeter/providers/ollama.py +525 -0
  36. clawmeter-0.7.4/src/clawmeter/providers/openai.py +402 -0
  37. clawmeter-0.7.4/src/clawmeter/security.py +142 -0
  38. clawmeter-0.7.4/tests/__init__.py +0 -0
  39. clawmeter-0.7.4/tests/conftest.py +67 -0
  40. clawmeter-0.7.4/tests/fixtures/claude_credentials.json +7 -0
  41. clawmeter-0.7.4/tests/fixtures/claude_usage_response.json +27 -0
  42. clawmeter-0.7.4/tests/fixtures/config_full.toml +56 -0
  43. clawmeter-0.7.4/tests/fixtures/grok_invoice_preview.json +52 -0
  44. clawmeter-0.7.4/tests/fixtures/grok_prepaid_balance.json +39 -0
  45. clawmeter-0.7.4/tests/fixtures/grok_spending_limits.json +9 -0
  46. clawmeter-0.7.4/tests/fixtures/grok_usage_analytics.json +31 -0
  47. clawmeter-0.7.4/tests/fixtures/ollama_cloud_usage.json +11 -0
  48. clawmeter-0.7.4/tests/fixtures/ollama_ps.json +20 -0
  49. clawmeter-0.7.4/tests/fixtures/ollama_ps_cpu_only.json +19 -0
  50. clawmeter-0.7.4/tests/fixtures/ollama_tags.json +49 -0
  51. clawmeter-0.7.4/tests/fixtures/openai_costs.json +48 -0
  52. clawmeter-0.7.4/tests/fixtures/openai_usage_completions.json +51 -0
  53. clawmeter-0.7.4/tests/formatters/__init__.py +0 -0
  54. clawmeter-0.7.4/tests/formatters/test_json_fmt.py +227 -0
  55. clawmeter-0.7.4/tests/formatters/test_monitor_fmt.py +460 -0
  56. clawmeter-0.7.4/tests/formatters/test_table_fmt.py +206 -0
  57. clawmeter-0.7.4/tests/providers/__init__.py +0 -0
  58. clawmeter-0.7.4/tests/providers/test_base.py +218 -0
  59. clawmeter-0.7.4/tests/providers/test_claude.py +542 -0
  60. clawmeter-0.7.4/tests/providers/test_grok.py +639 -0
  61. clawmeter-0.7.4/tests/providers/test_ollama.py +502 -0
  62. clawmeter-0.7.4/tests/providers/test_ollama_cloud.py +322 -0
  63. clawmeter-0.7.4/tests/providers/test_openai.py +579 -0
  64. clawmeter-0.7.4/tests/test_cache.py +246 -0
  65. clawmeter-0.7.4/tests/test_cli.py +653 -0
  66. clawmeter-0.7.4/tests/test_config.py +474 -0
  67. clawmeter-0.7.4/tests/test_daemon.py +628 -0
  68. clawmeter-0.7.4/tests/test_history.py +1123 -0
  69. clawmeter-0.7.4/tests/test_models.py +163 -0
  70. clawmeter-0.7.4/tests/test_security.py +292 -0
  71. clawmeter-0.7.4/uv.lock +672 -0
@@ -0,0 +1,10 @@
1
+ .github
2
+ __pycache__
3
+ *.egg-info
4
+ *.pyc
5
+ .pytest_cache
6
+ tests/
7
+ docs/
8
+ .venv
9
+ *.md
10
+ !README.md
@@ -0,0 +1,30 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.10", "3.12", "3.13"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ with:
19
+ fetch-depth: 0
20
+
21
+ - uses: astral-sh/setup-uv@v4
22
+
23
+ - name: Set up Python ${{ matrix.python-version }}
24
+ run: uv python install ${{ matrix.python-version }}
25
+
26
+ - name: Install dependencies
27
+ run: uv sync --group dev
28
+
29
+ - name: Run tests
30
+ run: uv run pytest -v
@@ -0,0 +1,148 @@
1
+ # ============================================
2
+ # CLAWMETER - Publish to PyPI
3
+ # ============================================
4
+ # Triggered on version tags (v*) or manual dispatch
5
+ # Builds and publishes to PyPI via trusted publishing
6
+ # ============================================
7
+
8
+ name: Publish
9
+
10
+ on:
11
+ push:
12
+ tags:
13
+ - 'v*'
14
+ workflow_dispatch:
15
+ inputs:
16
+ target:
17
+ description: 'Publish target'
18
+ required: true
19
+ default: 'testpypi'
20
+ type: choice
21
+ options:
22
+ - testpypi
23
+ - pypi
24
+
25
+ env:
26
+ PYTHON_VERSION: "3.12"
27
+
28
+ jobs:
29
+ # ============================================
30
+ # BUILD
31
+ # ============================================
32
+ build:
33
+ name: Build Distribution
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - name: Checkout code
37
+ uses: actions/checkout@v4
38
+ with:
39
+ fetch-depth: 0 # Full history for version detection
40
+
41
+ - name: Set up Python
42
+ uses: actions/setup-python@v5
43
+ with:
44
+ python-version: ${{ env.PYTHON_VERSION }}
45
+
46
+ - name: Install build tools
47
+ run: |
48
+ python -m pip install --upgrade pip
49
+ pip install build twine
50
+
51
+ - name: Determine version from tag
52
+ if: startsWith(github.ref, 'refs/tags/v')
53
+ run: echo "SETUPTOOLS_SCM_PRETEND_VERSION=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_ENV"
54
+
55
+ - name: Build package
56
+ run: python -m build
57
+
58
+ - name: Check package
59
+ run: twine check dist/*
60
+
61
+ - name: List artifacts
62
+ run: ls -la dist/
63
+
64
+ - name: Upload build artifacts
65
+ uses: actions/upload-artifact@v4
66
+ with:
67
+ name: dist
68
+ path: dist/
69
+ retention-days: 30
70
+
71
+ # ============================================
72
+ # PUBLISH TO TEST PYPI
73
+ # ============================================
74
+ publish-testpypi:
75
+ name: Publish to TestPyPI
76
+ runs-on: ubuntu-latest
77
+ needs: build
78
+ if: |
79
+ (github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'testpypi')
80
+ environment:
81
+ name: testpypi
82
+ url: https://test.pypi.org/project/clawmeter/
83
+ permissions:
84
+ id-token: write # For trusted publishing
85
+
86
+ steps:
87
+ - name: Download artifacts
88
+ uses: actions/download-artifact@v4
89
+ with:
90
+ name: dist
91
+ path: dist/
92
+
93
+ - name: Publish to TestPyPI
94
+ uses: pypa/gh-action-pypi-publish@release/v1
95
+ with:
96
+ repository-url: https://test.pypi.org/legacy/
97
+
98
+ # ============================================
99
+ # PUBLISH TO PYPI
100
+ # ============================================
101
+ publish-pypi:
102
+ name: Publish to PyPI
103
+ runs-on: ubuntu-latest
104
+ needs: build
105
+ if: |
106
+ (github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'pypi') ||
107
+ (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v'))
108
+ environment:
109
+ name: pypi
110
+ url: https://pypi.org/project/clawmeter/
111
+ permissions:
112
+ id-token: write # For trusted publishing
113
+
114
+ steps:
115
+ - name: Download artifacts
116
+ uses: actions/download-artifact@v4
117
+ with:
118
+ name: dist
119
+ path: dist/
120
+
121
+ - name: Publish to PyPI
122
+ uses: pypa/gh-action-pypi-publish@release/v1
123
+
124
+ # ============================================
125
+ # POST-PUBLISH VERIFICATION
126
+ # ============================================
127
+ verify:
128
+ name: Verify Installation
129
+ runs-on: ubuntu-latest
130
+ needs: [publish-pypi]
131
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
132
+ steps:
133
+ - name: Wait for PyPI propagation
134
+ run: sleep 60
135
+
136
+ - name: Set up Python
137
+ uses: actions/setup-python@v5
138
+ with:
139
+ python-version: ${{ env.PYTHON_VERSION }}
140
+
141
+ - name: Install from PyPI
142
+ run: |
143
+ pip install clawmeter
144
+ clawmeter --version
145
+ clawmeter --help
146
+
147
+ - name: Verify import
148
+ run: python -c "import clawmeter; print(f'clawmeter {clawmeter.__version__}')"
@@ -0,0 +1,211 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
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
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+ #poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ #pdm.lock
116
+ #pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ #pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # SageMath parsed files
135
+ *.sage.py
136
+
137
+ # Environments
138
+ .env
139
+ .envrc
140
+ .venv
141
+ env/
142
+ venv/
143
+ ENV/
144
+ env.bak/
145
+ venv.bak/
146
+
147
+ # Spyder project settings
148
+ .spyderproject
149
+ .spyproject
150
+
151
+ # Rope project settings
152
+ .ropeproject
153
+
154
+ # mkdocs documentation
155
+ /site
156
+
157
+ # mypy
158
+ .mypy_cache/
159
+ .dmypy.json
160
+ dmypy.json
161
+
162
+ # Pyre type checker
163
+ .pyre/
164
+
165
+ # pytype static type analyzer
166
+ .pytype/
167
+
168
+ # Cython debug symbols
169
+ cython_debug/
170
+
171
+ # PyCharm
172
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
173
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
174
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
175
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
176
+ #.idea/
177
+
178
+ # Abstra
179
+ # Abstra is an AI-powered process automation framework.
180
+ # Ignore directories containing user credentials, local state, and settings.
181
+ # Learn more at https://abstra.io/docs
182
+ .abstra/
183
+
184
+ # Visual Studio Code
185
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
186
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
187
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
188
+ # you could uncomment the following to ignore the entire vscode folder
189
+ # .vscode/
190
+
191
+ # Ruff stuff:
192
+ .ruff_cache/
193
+
194
+ # PyPI configuration file
195
+ .pypirc
196
+
197
+ # Cursor
198
+ # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
199
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
200
+ # refer to https://docs.cursor.com/context/ignore-files
201
+ .cursorignore
202
+ .cursorindexingignore
203
+
204
+ # Marimo
205
+ marimo/_static/
206
+ marimo/_lsp/
207
+ __marimo__/
208
+
209
+ # Version py created by hatch-vcs
210
+ src/clawmeter/_version.py
211
+
@@ -0,0 +1,135 @@
1
+ # Changelog
2
+
3
+ All notable changes to clawmeter are documented in this file.
4
+
5
+ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
+ Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.7.1] - 2026-04-10
9
+
10
+ ### Added
11
+
12
+ - **Claude extra usage spend** (alpha) — tracks dollar spend beyond the subscription cap via undocumented `extra_usage` field in `/api/oauth/usage` response
13
+ - Extra Usage window with percentage utilisation, raw spend and limit in user's billing currency
14
+ - `extras` dict includes `extra_usage_enabled`, `extra_usage_spent`, `extra_usage_limit`
15
+ - **Weekly Sonnet (7d)** window — new per-model utilisation window from Claude API (`seven_day_sonnet`)
16
+ - **`credits` unit type** in formatters — displays as `$X.XX` with no currency qualifier, for values in the user's billing currency
17
+ - Alpha warning utility (`emit_alpha_warning`) extracted to `config.py` as shared infrastructure for all alpha providers
18
+
19
+ ### Changed
20
+
21
+ - Claude test fixture updated with `seven_day_sonnet`, `seven_day_cowork`, and `extra_usage` fields matching current API response
22
+ - OQ-001, OQ-002, OQ-004, OQ-008 closed with research findings in SPEC.md
23
+
24
+ ## [0.7.0] - 2026-04-10
25
+
26
+ ### Added
27
+
28
+ - **Ollama provider** (`providers/ollama.py`) — local instance monitoring with model inventory, loaded model state, VRAM/RAM usage reporting
29
+ - Multi-host support: single `host` config or `[[providers.ollama.hosts]]` array for monitoring multiple Ollama instances across a network
30
+ - Per-host polling via `GET /api/tags` (model inventory + health) and `GET /api/ps` (loaded models + memory allocation)
31
+ - Cloud model detection via `:cloud` tag suffix in model names (labelling only)
32
+ - Error isolation per host — one host being unreachable does not affect others
33
+ - **Alpha features framework** (D-053) — `enable_alpha_features` config flag gates unstable data sources with stderr warning, `alpha: true` labelling in extras, and graceful failure
34
+ - Ollama Cloud session/weekly usage windows (alpha) — probes `ollama.com/api/account/usage` when `cloud_enabled = true` and alpha flag set
35
+ - Cloud API key authentication via credential chain (`api_key_command` > `api_key_env`/`$OLLAMA_API_KEY` > keyring)
36
+ - `is_alpha_enabled()` config helper for providers to check alpha flag
37
+ - `[providers.ollama]` config section with `host`/`hosts` mutual exclusivity validation
38
+ - Research report: `docs/research/ollama-v0.7.0-research.md`
39
+
40
+ ### Fixed
41
+
42
+ - Table and monitor formatters now render count and MB values (e.g. "3", "5,086 MB") instead of showing "0%" for non-percentage usage windows
43
+
44
+ ## [0.6.0] - 2026-04-10
45
+
46
+ ### Added
47
+
48
+ - **OpenAI provider** (`providers/openai.py`) — organisation-level spend and per-model usage monitoring via the Administration API
49
+ - Usage API: `GET /v1/organization/usage/completions` with `group_by=model` for per-model token counts (input, output, cached, requests)
50
+ - Costs API: `GET /v1/organization/costs` with `group_by=line_item` for per-model cost in USD and total MTD spend
51
+ - Admin key credential resolution via `$OPENAI_ADMIN_KEY` (`sk-admin-*`) with 4-tier chain
52
+ - Token and cost merge into unified `ModelUsage` entries with `top_model_spend` extras
53
+ - Config section for OpenAI (`providers.openai` with `admin_key_env`)
54
+
55
+ ### Changed
56
+
57
+ - SPEC.md Section 3.3 rewritten with correct admin key auth, response schemas, and query parameters
58
+ - OQ-012 closed: undocumented billing endpoints confirmed dead, admin key required
59
+ - A-006 falsified: standard API keys cannot access org usage endpoints
60
+
61
+ ## [0.5.0] - 2026-04-09
62
+
63
+ ### Added
64
+
65
+ - **xAI Grok provider** (`providers/grok.py`) — spend monitoring via the xAI Management API
66
+ - Invoice preview endpoint for month-to-date spend with per-model cost breakdown
67
+ - Spending limits endpoint with utilisation percentage and threshold detection
68
+ - Prepaid balance endpoint
69
+ - Usage analytics endpoint for per-model time-series token and spend data
70
+ - Dual credential support: management key (primary, 4-tier resolution) and optional API key
71
+ - Team ID resolution from config (`providers.grok.team_id`) or `$XAI_TEAM_ID` env var
72
+ - Redaction pattern for `xai-*` keys in security module
73
+ - Config section for Grok provider
74
+ - Test fixtures for all four Management API responses
75
+
76
+ ### Fixed
77
+
78
+ - Table and monitor formatters now render USD-based usage windows as dollar values instead of percentages
79
+
80
+ ## [0.4.0] - 2026-04-08
81
+
82
+ ### Added
83
+
84
+ - **Monitor TUI** (`--monitor` flag) — Rich Live dashboard with auto-refreshing provider status
85
+ - Per-provider status panels with usage bars, model breakdowns, and sparklines
86
+ - Compact mode for smaller terminals
87
+ - Configurable refresh interval
88
+ - Colour-coded threshold indicators (normal/warning/critical)
89
+
90
+ ## [0.3.0] - 2026-04-07
91
+
92
+ ### Added
93
+
94
+ - **Background daemon** (`clawmeter daemon start/stop/status/install`) — polls providers on a schedule and writes to the shared SQLite history database
95
+ - PID file management with stale PID detection
96
+ - `daemon install` generates a systemd user unit for automatic startup
97
+ - CLI reads from daemon's database when available, falls back to direct fetching
98
+ - **Docker deployment** — `Dockerfile` and `docker-compose.yml` for containerised daemon operation
99
+ - Container mode detection (skips permission checks, uses env var paths)
100
+ - Per-provider poll interval override in config
101
+ - Daemon-aware provider base class with backoff state persistence
102
+
103
+ ## [0.2.0] - 2026-04-07
104
+
105
+ ### Added
106
+
107
+ - **SQLite history store** (`history.py`) — records provider snapshots with WAL mode and retention pruning
108
+ - `clawmeter history` — query historical usage data with time range filters
109
+ - `clawmeter report` — generate usage summaries (daily/weekly/monthly) with trend analysis
110
+ - `clawmeter export` — export history to CSV or JSON
111
+ - Meaningful-change detection to avoid storing duplicate snapshots
112
+ - Per-model usage tracking in dedicated `model_usage` table
113
+
114
+ ## [0.1.0] - 2026-04-06
115
+
116
+ ### Added
117
+
118
+ - **Anthropic Claude provider** — subscription utilisation tracking via Claude Code OAuth credentials
119
+ - Read-only consumer of `~/.claude/.credentials.json` (never writes to the file)
120
+ - Five usage windows: Session (5h), Daily, Weekly, Monthly, and Opus-specific
121
+ - Per-model usage breakdown (Opus, Sonnet, Haiku token counts)
122
+ - **CLI with three output modes**: JSON (default, no flag), `--now` (Rich table), `--monitor` (TUI, added in v0.4.0)
123
+ - **Provider architecture** — abstract `Provider` base class with `@register_provider` decorator
124
+ - 4-tier credential resolution: `key_command` > env var > keyring > provider-specific file
125
+ - `SecretStr` wrapper with safe `__repr__` — secrets never leak in logs or output
126
+ - **JSON cache** with `poll_interval`-based TTL and `fcntl.flock()` file locking
127
+ - **TOML configuration** from `~/.config/clawmeter/config.toml` with env var path overrides
128
+ - **Security module** — credential sanitisation, secure file I/O (`0o600`/`0o700`), atomic writes
129
+ - Allowed-hosts enforcement for credential-bearing HTTP requests
130
+ - `key_command` execution with `shell=False` and 10s timeout
131
+ - Rich table formatter with TTY-adaptive column widths and colour-coded thresholds
132
+ - JSON formatter with stable schema (stdout-only, stderr for messaging)
133
+ - Exit codes: 0 (ok), 1 (config error), 2 (all auth fail), 3 (partial), 4 (all network fail)
134
+ - CI pipeline via GitHub Actions (lint, test, type check)
135
+ - Full test suite: providers, formatters, cache, config, security, CLI, models
@@ -0,0 +1,101 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ clawmeter is a Linux-native CLI tool for monitoring LLM service usage, costs, and performance across multiple providers. It uses a pluggable provider architecture, launching with Anthropic Claude support and expanding to Grok (xAI), OpenAI, Ollama, and local system metrics. A GTK/GNOME desktop widget is planned for v2.
8
+
9
+ The tool operates in two modes: **standalone** (CLI fetches directly) and **daemon** (background service collects, CLI reads from DB). The daemon is recommended for continuous history collection.
10
+
11
+ The full specification lives in `docs/SPEC.md` - consult it for detailed provider specs, security requirements, CLI interface design, and open questions.
12
+
13
+ ## Build & Development
14
+
15
+ ```bash
16
+ # Setup (from source)
17
+ uv sync
18
+
19
+ # Run
20
+ uv run clawmeter
21
+
22
+ # Run tests
23
+ uv run pytest
24
+ uv run pytest tests/test_security.py # single test file
25
+ uv run pytest tests/test_security.py::test_name # single test
26
+
27
+ # Build
28
+ uv build
29
+
30
+ # Docker
31
+ docker build -t clawmeter .
32
+ docker compose up -d
33
+ ```
34
+
35
+ Build backend is `hatchling` + `hatch-vcs`. Version is derived from git tags (no VERSION file). Tag format: `v0.1.0`.
36
+
37
+ ## Architecture
38
+
39
+ **Daemon + thin clients:** The daemon polls providers on a schedule, writes to a SQLite history database. CLI/TUI/GTK are thin readers of that database. Without the daemon, the CLI falls back to direct fetching (standalone mode).
40
+
41
+ **Provider abstraction** is the core pattern: every LLM service implements `Provider` (ABC) with `name()`, `display_name()`, `is_configured()`, `fetch_usage()`, and `auth_instructions()`. The base class handles credential resolution. Providers return a unified `ProviderStatus` dataclass containing `UsageWindow` and `ModelUsage` entries.
42
+
43
+ Key components and their locations under `src/clawmeter/`:
44
+
45
+ - `daemon.py` - Background service: poll loop, PID file management, systemd install
46
+ - `models.py` - `SecretStr`, `UsageWindow`, `ModelUsage`, `ProviderStatus` dataclasses
47
+ - `providers/base.py` - Abstract `Provider` class with `resolve_credential()` (4-tier: key_command > env var > keyring > provider-specific file). key_command failure raises `CredentialError` (no silent fallthrough). Provider registry via `@register_provider` decorator and module-level dict.
48
+ - `providers/claude.py` - Read-only consumer of `~/.claude/.credentials.json` (`claudeAiOauth.accessToken` + `expiresAt`). Does NOT refresh tokens or write to the file.
49
+ - `core.py` - Orchestrator: loads providers, aggregates results
50
+ - `cli.py` - Click/Typer CLI with modes: JSON (default, no flag), `--now` (table), `--monitor` (Rich Live TUI)
51
+ - `security.py` - Credential sanitisation, secure file I/O (`secure_write`, `secure_mkdir`), permission warnings
52
+ - `cache.py` - Per-provider JSON cache (standalone mode only), `poll_interval`-based TTL, `fcntl.flock()`
53
+ - `config.py` - TOML loader from `~/.config/clawmeter/config.toml`, permission warnings (not hard failures)
54
+ - `history.py` - SQLite at `~/.local/share/clawmeter/history.db` (WAL mode), retention pruning, reporting
55
+ - `formatters/` - JSON, Rich table (TTY-adaptive), Rich Live TUI
56
+
57
+ ## Critical Design Decisions
58
+
59
+ - **stdout is data only, stderr is messaging only** - JSON/table output to stdout; errors, warnings, spinners to stderr. No exceptions.
60
+ - **JSON is the default output mode** (no flag needed). `--now` for table, `--monitor` for TUI.
61
+ - **Daemon decouples collection from presentation** - CLI/TUI/GTK read from the shared SQLite DB. Standalone mode still works without daemon.
62
+ - **No plaintext secrets in config files** - credentials resolved via keyring, env vars, or `key_command` (executed with `shell=False`).
63
+ - **All secrets wrapped in `SecretStr`** - `__repr__` always returns `SecretStr('***')` (never leaks real characters). Never log/serialize raw secrets. Failures raise `CredentialError` (defined in models.py).
64
+ - **Read-only consumer of Claude credentials** - the tool NEVER writes to `~/.claude/.credentials.json`. On token expiry, direct user to `claude /login`.
65
+ - **key_command failure is hard, not silent** - if explicitly configured and it fails/times out, raise `CredentialError`. Don't silently fall through to env vars.
66
+ - **Global poll_interval (10m default)** - one setting replaces the old `cache_ttl`/`refresh_interval` split. Per-provider override available. Local providers default to 60s.
67
+ - **Async concurrency** - `Provider.fetch_usage()` is async. CLI uses `asyncio.run()` + `asyncio.gather()` for concurrent provider fetches. Daemon uses a persistent asyncio event loop. `return_exceptions=True` for error isolation.
68
+ - **Rate-limit backoff** - exponential backoff on 429s (10m -> 20m -> 40m, cap 60m), persisted in cache file, resets on success.
69
+ - **Files created with `0o600`/dirs `0o700` from the start** - use `os.open()` + `os.fdopen()`, not `open()` then `chmod()`.
70
+ - **Atomic file writes** - write to `.tmp` then `os.rename()`.
71
+ - **Permission checks are warnings, not hard failures** - config contains no secrets (by design). Container mode skips checks entirely.
72
+ - **Provider errors are isolated** - one provider failing doesn't block others. Exit codes: 0=all ok, 1=config error, 2=all auth fail, 3=partial, 4=all network fail.
73
+ - **Australian English in docs/UI** ("utilisation", "colour") but US English for Python API identifiers where convention requires it.
74
+ - **Lightweight base install, additive extras** - base has no C extensions. `[local]` adds psutil/pynvml. `[gtk]` adds PyGObject. `[all]` is everything.
75
+ - **Env var overrides for all paths** - `CLAWMETER_CONFIG`, `CLAWMETER_DATA_DIR`, `CLAWMETER_CACHE_DIR`. Essential for Docker.
76
+
77
+ ## Security Checklist
78
+
79
+ When touching credential or network code, verify:
80
+ - `SecretStr` wraps all secrets immediately on read — `__repr__` is always `SecretStr('***')`
81
+ - Redaction patterns applied to any user-visible output (see SPEC Section 7.3)
82
+ - `httpx` requests: `verify=True` (TLS), `follow_redirects=False` on auth-bearing requests
83
+ - `key_command`: `shell=False`, timeout=10s with `TimeoutExpired` handling, only log stderr on failure (never stdout)
84
+ - Credential-bearing requests only sent to provider's `allowed_hosts`
85
+ - No secrets in cache files, JSON output, `extras` dict, or error messages
86
+ - Never write to Claude's credential file — read-only consumer
87
+
88
+ ## Tech Stack
89
+
90
+ Python 3.10+, httpx, rich, click/typer, keyring, tomllib/tomli, sqlite3, hatchling+hatch-vcs, uv, pytest+respx, Docker, systemd user units. Optional: psutil, pynvml (via `[local]` extra), PyGObject (via `[gtk]` extra).
91
+
92
+ ## Release Milestones
93
+
94
+ v0.1.0: CLI MVP — Claude provider, standalone, JSON + table output, CI from day one
95
+ v0.2.0: History + reporting — SQLite store, meaningful-change detection, report/export commands
96
+ v0.3.0: Daemon + Docker — background collection, systemd, container deployment
97
+ v0.4.0: Monitor TUI — Rich Live dashboard, sparklines, compact mode
98
+ v0.5.0–v0.8.0: Providers — Grok, OpenAI, Ollama, Local system metrics
99
+ v0.9.0: Notifications and polish
100
+ v1.0.0: Stable release — security audit, schema freeze, fuzz testing
101
+ v2.0.0: GTK4/libadwaita desktop widget
@@ -0,0 +1,24 @@
1
+ FROM python:3.12-slim AS builder
2
+
3
+ RUN apt-get update && apt-get install -y --no-install-recommends git && rm -rf /var/lib/apt/lists/*
4
+ COPY . /tmp/build
5
+ RUN pip install --no-cache-dir build && python -m build --wheel /tmp/build -o /tmp/dist
6
+
7
+ FROM python:3.12-slim
8
+
9
+ # Create non-root user
10
+ RUN useradd --create-home --shell /bin/bash monitor
11
+
12
+ COPY --from=builder /tmp/dist/*.whl /tmp/
13
+ RUN pip install --no-cache-dir /tmp/*.whl && rm -f /tmp/*.whl
14
+
15
+ USER monitor
16
+
17
+ # Default data directory
18
+ ENV CLAWMETER_DATA_DIR=/data
19
+ ENV CLAWMETER_CACHE_DIR=/data/cache
20
+ ENV CLAWMETER_CONTAINER=1
21
+
22
+ VOLUME /data
23
+
24
+ ENTRYPOINT ["clawmeter", "daemon", "run"]
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel Thomas
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.