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.
- clawmeter-0.7.4/.dockerignore +10 -0
- clawmeter-0.7.4/.github/workflows/ci.yml +30 -0
- clawmeter-0.7.4/.github/workflows/publish.yml +148 -0
- clawmeter-0.7.4/.gitignore +211 -0
- clawmeter-0.7.4/CHANGELOG.md +135 -0
- clawmeter-0.7.4/CLAUDE.md +101 -0
- clawmeter-0.7.4/Dockerfile +24 -0
- clawmeter-0.7.4/LICENSE +21 -0
- clawmeter-0.7.4/PKG-INFO +621 -0
- clawmeter-0.7.4/README.md +586 -0
- clawmeter-0.7.4/assets/clawmeter.png +0 -0
- clawmeter-0.7.4/docker-compose.yml +19 -0
- clawmeter-0.7.4/docs/SPEC.md +2734 -0
- clawmeter-0.7.4/docs/research/ollama-v0.7.0-research.md +649 -0
- clawmeter-0.7.4/pyproject.toml +70 -0
- clawmeter-0.7.4/src/clawmeter/__init__.py +8 -0
- clawmeter-0.7.4/src/clawmeter/__main__.py +39 -0
- clawmeter-0.7.4/src/clawmeter/_version.py +24 -0
- clawmeter-0.7.4/src/clawmeter/cache.py +277 -0
- clawmeter-0.7.4/src/clawmeter/cli.py +1151 -0
- clawmeter-0.7.4/src/clawmeter/config.py +256 -0
- clawmeter-0.7.4/src/clawmeter/core.py +205 -0
- clawmeter-0.7.4/src/clawmeter/daemon.py +410 -0
- clawmeter-0.7.4/src/clawmeter/formatters/__init__.py +13 -0
- clawmeter-0.7.4/src/clawmeter/formatters/json_fmt.py +107 -0
- clawmeter-0.7.4/src/clawmeter/formatters/monitor_fmt.py +713 -0
- clawmeter-0.7.4/src/clawmeter/formatters/table_fmt.py +157 -0
- clawmeter-0.7.4/src/clawmeter/history.py +901 -0
- clawmeter-0.7.4/src/clawmeter/migrate.py +123 -0
- clawmeter-0.7.4/src/clawmeter/models.py +107 -0
- clawmeter-0.7.4/src/clawmeter/providers/__init__.py +44 -0
- clawmeter-0.7.4/src/clawmeter/providers/base.py +98 -0
- clawmeter-0.7.4/src/clawmeter/providers/claude.py +311 -0
- clawmeter-0.7.4/src/clawmeter/providers/grok.py +560 -0
- clawmeter-0.7.4/src/clawmeter/providers/ollama.py +525 -0
- clawmeter-0.7.4/src/clawmeter/providers/openai.py +402 -0
- clawmeter-0.7.4/src/clawmeter/security.py +142 -0
- clawmeter-0.7.4/tests/__init__.py +0 -0
- clawmeter-0.7.4/tests/conftest.py +67 -0
- clawmeter-0.7.4/tests/fixtures/claude_credentials.json +7 -0
- clawmeter-0.7.4/tests/fixtures/claude_usage_response.json +27 -0
- clawmeter-0.7.4/tests/fixtures/config_full.toml +56 -0
- clawmeter-0.7.4/tests/fixtures/grok_invoice_preview.json +52 -0
- clawmeter-0.7.4/tests/fixtures/grok_prepaid_balance.json +39 -0
- clawmeter-0.7.4/tests/fixtures/grok_spending_limits.json +9 -0
- clawmeter-0.7.4/tests/fixtures/grok_usage_analytics.json +31 -0
- clawmeter-0.7.4/tests/fixtures/ollama_cloud_usage.json +11 -0
- clawmeter-0.7.4/tests/fixtures/ollama_ps.json +20 -0
- clawmeter-0.7.4/tests/fixtures/ollama_ps_cpu_only.json +19 -0
- clawmeter-0.7.4/tests/fixtures/ollama_tags.json +49 -0
- clawmeter-0.7.4/tests/fixtures/openai_costs.json +48 -0
- clawmeter-0.7.4/tests/fixtures/openai_usage_completions.json +51 -0
- clawmeter-0.7.4/tests/formatters/__init__.py +0 -0
- clawmeter-0.7.4/tests/formatters/test_json_fmt.py +227 -0
- clawmeter-0.7.4/tests/formatters/test_monitor_fmt.py +460 -0
- clawmeter-0.7.4/tests/formatters/test_table_fmt.py +206 -0
- clawmeter-0.7.4/tests/providers/__init__.py +0 -0
- clawmeter-0.7.4/tests/providers/test_base.py +218 -0
- clawmeter-0.7.4/tests/providers/test_claude.py +542 -0
- clawmeter-0.7.4/tests/providers/test_grok.py +639 -0
- clawmeter-0.7.4/tests/providers/test_ollama.py +502 -0
- clawmeter-0.7.4/tests/providers/test_ollama_cloud.py +322 -0
- clawmeter-0.7.4/tests/providers/test_openai.py +579 -0
- clawmeter-0.7.4/tests/test_cache.py +246 -0
- clawmeter-0.7.4/tests/test_cli.py +653 -0
- clawmeter-0.7.4/tests/test_config.py +474 -0
- clawmeter-0.7.4/tests/test_daemon.py +628 -0
- clawmeter-0.7.4/tests/test_history.py +1123 -0
- clawmeter-0.7.4/tests/test_models.py +163 -0
- clawmeter-0.7.4/tests/test_security.py +292 -0
- clawmeter-0.7.4/uv.lock +672 -0
|
@@ -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"]
|
clawmeter-0.7.4/LICENSE
ADDED
|
@@ -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.
|