tokenbill 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. tokenbill-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +58 -0
  2. tokenbill-0.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
  3. tokenbill-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +40 -0
  4. tokenbill-0.1.0/.github/pull_request_template.md +25 -0
  5. tokenbill-0.1.0/.github/workflows/ci.yml +52 -0
  6. tokenbill-0.1.0/.github/workflows/release.yml +88 -0
  7. tokenbill-0.1.0/.gitignore +23 -0
  8. tokenbill-0.1.0/CHANGELOG.md +52 -0
  9. tokenbill-0.1.0/CODE_OF_CONDUCT.md +52 -0
  10. tokenbill-0.1.0/CONTRIBUTING.md +93 -0
  11. tokenbill-0.1.0/DESIGN.md +338 -0
  12. tokenbill-0.1.0/LICENSE +21 -0
  13. tokenbill-0.1.0/Makefile +17 -0
  14. tokenbill-0.1.0/PKG-INFO +286 -0
  15. tokenbill-0.1.0/README.md +258 -0
  16. tokenbill-0.1.0/SECURITY.md +70 -0
  17. tokenbill-0.1.0/docs/SPEC.md +471 -0
  18. tokenbill-0.1.0/pyproject.toml +67 -0
  19. tokenbill-0.1.0/tests/test_analyzer.py +200 -0
  20. tokenbill-0.1.0/tests/test_breakers.py +398 -0
  21. tokenbill-0.1.0/tests/test_cli.py +247 -0
  22. tokenbill-0.1.0/tests/test_demo_recovers_planted_waste.py +388 -0
  23. tokenbill-0.1.0/tests/test_demo_traces.py +205 -0
  24. tokenbill-0.1.0/tests/test_instrument.py +470 -0
  25. tokenbill-0.1.0/tests/test_performance.py +91 -0
  26. tokenbill-0.1.0/tests/test_pricing.py +137 -0
  27. tokenbill-0.1.0/tests/test_report.py +400 -0
  28. tokenbill-0.1.0/tests/test_simulator.py +321 -0
  29. tokenbill-0.1.0/tests/test_trace.py +439 -0
  30. tokenbill-0.1.0/tokenbill/__init__.py +12 -0
  31. tokenbill-0.1.0/tokenbill/__main__.py +6 -0
  32. tokenbill-0.1.0/tokenbill/analyzer.py +187 -0
  33. tokenbill-0.1.0/tokenbill/breakers.py +369 -0
  34. tokenbill-0.1.0/tokenbill/cli.py +255 -0
  35. tokenbill-0.1.0/tokenbill/common.py +46 -0
  36. tokenbill-0.1.0/tokenbill/demo_traces.py +558 -0
  37. tokenbill-0.1.0/tokenbill/instrument.py +310 -0
  38. tokenbill-0.1.0/tokenbill/pricing.py +139 -0
  39. tokenbill-0.1.0/tokenbill/py.typed +0 -0
  40. tokenbill-0.1.0/tokenbill/report.py +788 -0
  41. tokenbill-0.1.0/tokenbill/simulator.py +313 -0
  42. tokenbill-0.1.0/tokenbill/trace.py +455 -0
  43. tokenbill-0.1.0/uv.lock +183 -0
@@ -0,0 +1,58 @@
1
+ name: Bug report
2
+ description: Something is wrong, or a number in the report looks off
3
+ labels: ["bug"]
4
+ body:
5
+ - type: input
6
+ id: version
7
+ attributes:
8
+ label: Token Bill version
9
+ description: Output of `tokenbill --version`.
10
+ placeholder: "0.1.0"
11
+ validations:
12
+ required: true
13
+ - type: input
14
+ id: environment
15
+ attributes:
16
+ label: Python version and OS
17
+ placeholder: "Python 3.12.4, macOS 15"
18
+ validations:
19
+ required: true
20
+ - type: textarea
21
+ id: command
22
+ attributes:
23
+ label: Exact command
24
+ description: The full command line you ran, including flags and seed.
25
+ render: shell
26
+ placeholder: tokenbill analyze trace.jsonl -o report.html
27
+ validations:
28
+ required: true
29
+ - type: dropdown
30
+ id: trace-source
31
+ attributes:
32
+ label: Trace source
33
+ options:
34
+ - Bundled demo scenario (synthetic)
35
+ - Recorded with tokenbill.instrument
36
+ - Converted or hand-written JSONL
37
+ validations:
38
+ required: true
39
+ - type: textarea
40
+ id: trace-line
41
+ attributes:
42
+ label: One redacted trace line (if not the bundled demo)
43
+ description: >
44
+ One representative line of your trace JSONL. IMPORTANT — trace lines
45
+ contain your prompts, tool definitions, and conversation history:
46
+ redact all text content before pasting. Field names, shapes, and the
47
+ `usage` numbers are what matter for debugging; keep those intact.
48
+ render: json
49
+ - type: textarea
50
+ id: what-happened
51
+ attributes:
52
+ label: What happened vs. what you expected
53
+ description: >
54
+ Include the full traceback or the wrong output verbatim. If a dollar
55
+ or token number looks wrong, say which number and what you expected
56
+ it to be (and why).
57
+ validations:
58
+ required: true
@@ -0,0 +1,5 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Security vulnerability
4
+ url: https://github.com/sedai77/tokenbill-llm-agent-cost-profiler/security/advisories/new
5
+ about: Please report security issues privately — see SECURITY.md.
@@ -0,0 +1,40 @@
1
+ name: Feature request
2
+ description: Propose a new breaker kind, scenario, adapter, or capability
3
+ labels: ["enhancement"]
4
+ body:
5
+ - type: textarea
6
+ id: problem
7
+ attributes:
8
+ label: What waste are you trying to find, or what workflow are you unblocking?
9
+ description: >
10
+ The underlying need, not the mechanism — e.g. "my agent alternates
11
+ between two models and I can't see which one burns the cache", not
12
+ just "add flag X".
13
+ validations:
14
+ required: true
15
+ - type: textarea
16
+ id: proposal
17
+ attributes:
18
+ label: Proposed solution
19
+ description: >
20
+ Sketch the interface (CLI flag, function signature, report section).
21
+ For a new breaker kind: give the detection rule precisely enough to
22
+ unit-test, plus the one-sentence fix it would attach. For a new
23
+ simulator scenario: state its assumptions the way DESIGN.md's
24
+ assumption table does. Say how a planted-waste demo scenario could
25
+ validate it end to end.
26
+ validations:
27
+ required: true
28
+ - type: textarea
29
+ id: alternatives
30
+ attributes:
31
+ label: Alternatives considered
32
+ - type: checkboxes
33
+ id: constraints
34
+ attributes:
35
+ label: Project constraints
36
+ options:
37
+ - label: This can be built with the Python standard library only (no new runtime dependencies; the recorder keeps duck-typing provider SDKs, never importing them).
38
+ required: true
39
+ - label: Every dollar figure this produces is either derived from real billed usage (exact) or clearly labeled approximate — never an approximation presented as billed.
40
+ required: true
@@ -0,0 +1,25 @@
1
+ ## What & why
2
+
3
+ <!-- One or two sentences. Link the issue if there is one. -->
4
+
5
+ ## Checklist
6
+
7
+ - [ ] `uv run --python 3.12 --extra dev pytest -q` passes locally (includes the
8
+ flagship planted-waste recovery test).
9
+ - [ ] `uv run --python 3.12 --extra dev ruff check .` is clean.
10
+ - [ ] No new runtime dependencies (stdlib only; `instrument.py` duck-types the
11
+ SDK client and never imports `anthropic`).
12
+ - [ ] Tests added/updated for the change; nothing here hits the network or
13
+ needs an API key — CI has no secrets and never will.
14
+ - [ ] Every new number surfaced to users is either derived from billed `usage`
15
+ (exact) or labeled approximate; no approximation is presented as billed.
16
+ - [ ] `CHANGELOG.md` updated under `[Unreleased]` for user-visible changes.
17
+ - [ ] If a metric definition, cache-rule constant, or pricing entry changed:
18
+ `DESIGN.md`, the report's methodology footnotes, and the flagship test's
19
+ derived expectations are updated together — and pricing changes cite the
20
+ published pricing doc in the source comment.
21
+
22
+ ## Anything reviewers should focus on?
23
+
24
+ <!-- Subtle spots, e.g. canonical-rendering byte stability, approx-vs-billed
25
+ labeling, TTL edge cases in the simulator, seed scoping. -->
@@ -0,0 +1,52 @@
1
+ # CI: lint + unit tests + the offline demo as pipeline proof.
2
+ #
3
+ # Deliberately NO live-API job: Token Bill needs no API keys at all. The
4
+ # `demo` job runs the entire real pipeline (synthetic planted-waste traces ->
5
+ # analyzer -> cache simulator -> breaker detection -> HTML report) offline,
6
+ # and the unit matrix includes tests/test_demo_recovers_planted_waste.py,
7
+ # which asserts the analyzer recovers the exact waste the demo traces plant.
8
+ # That is the correctness proof, and it needs zero keys and zero network.
9
+ name: CI
10
+
11
+ on:
12
+ push:
13
+ branches: [main]
14
+ pull_request:
15
+
16
+ jobs:
17
+ lint:
18
+ name: lint (ruff)
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ - uses: astral-sh/setup-uv@v5
23
+ with:
24
+ python-version: "3.12"
25
+ - name: ruff check
26
+ run: uv run --extra dev ruff check .
27
+
28
+ unit:
29
+ name: unit (py ${{ matrix.python }})
30
+ runs-on: ubuntu-latest
31
+ strategy:
32
+ fail-fast: false
33
+ matrix:
34
+ python: ["3.10", "3.13"]
35
+ steps:
36
+ - uses: actions/checkout@v4
37
+ - uses: astral-sh/setup-uv@v5
38
+ - name: pytest
39
+ run: uv run --python ${{ matrix.python }} --extra dev pytest -q
40
+
41
+ demo:
42
+ name: offline demo (no keys, no network)
43
+ runs-on: ubuntu-latest
44
+ steps:
45
+ - uses: actions/checkout@v4
46
+ - uses: astral-sh/setup-uv@v5
47
+ with:
48
+ python-version: "3.12"
49
+ - name: Run the full pipeline offline
50
+ run: uv run tokenbill demo -o /tmp/report.html
51
+ - name: Report must exist and be non-empty
52
+ run: test -s /tmp/report.html
@@ -0,0 +1,88 @@
1
+ # Release pipeline, triggered by pushing a v* tag (e.g. v0.1.0).
2
+ #
3
+ # No API tokens anywhere: PyPI uses Trusted Publishing (OIDC).
4
+ # One-time setup by the repo owner:
5
+ # 1. pypi.org -> account -> Publishing -> add a (pending) trusted publisher:
6
+ # project "tokenbill", owner "sedai77",
7
+ # repository "tokenbill-llm-agent-cost-profiler",
8
+ # workflow "release.yml", environment "pypi".
9
+ # 2. GitHub repo Settings -> Environments -> create "pypi".
10
+ # Then: bump __version__ in tokenbill/__init__.py, move the CHANGELOG.md
11
+ # Unreleased entries into a dated section, and push the matching tag.
12
+ name: Release
13
+
14
+ on:
15
+ push:
16
+ tags: ["v*"]
17
+
18
+ jobs:
19
+ build:
20
+ name: build (wheel + sdist)
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+ - uses: astral-sh/setup-uv@v5
25
+ with:
26
+ python-version: "3.12"
27
+ - name: Build
28
+ run: uv build
29
+ - name: Check metadata
30
+ run: uvx twine check dist/*
31
+ - name: Tag must match the package version
32
+ run: |
33
+ version="${GITHUB_REF_NAME#v}"
34
+ if [ ! -f "dist/tokenbill-${version}.tar.gz" ]; then
35
+ echo "tag ${GITHUB_REF_NAME} does not match the built version:" >&2
36
+ ls dist/ >&2
37
+ echo "bump __version__ in tokenbill/__init__.py before tagging" >&2
38
+ exit 1
39
+ fi
40
+ - uses: actions/upload-artifact@v4
41
+ with:
42
+ name: dist
43
+ path: dist/
44
+
45
+ pypi:
46
+ name: publish to PyPI (trusted publishing)
47
+ needs: build
48
+ runs-on: ubuntu-latest
49
+ environment: pypi
50
+ permissions:
51
+ id-token: write # OIDC is the whole credential — no token, no secret
52
+ steps:
53
+ - uses: actions/download-artifact@v4
54
+ with:
55
+ name: dist
56
+ path: dist/
57
+ - uses: pypa/gh-action-pypi-publish@release/v1
58
+
59
+ github-release:
60
+ name: GitHub release
61
+ needs: [build, pypi]
62
+ runs-on: ubuntu-latest
63
+ permissions:
64
+ contents: write
65
+ steps:
66
+ - uses: actions/checkout@v4
67
+ - uses: actions/download-artifact@v4
68
+ with:
69
+ name: dist
70
+ path: dist/
71
+ - name: Extract this version's notes from CHANGELOG.md
72
+ run: |
73
+ awk -v heading="## [${GITHUB_REF_NAME#v}]" '
74
+ index($0, heading) == 1 { found = 1; next }
75
+ found && (/^## / || /^\[.*\]: /) { exit }
76
+ found { print }
77
+ ' CHANGELOG.md > notes.md
78
+ if [ ! -s notes.md ]; then
79
+ echo "no CHANGELOG.md section for ${GITHUB_REF_NAME#v}" >&2
80
+ echo "See CHANGELOG.md." > notes.md
81
+ fi
82
+ - name: Create the release
83
+ env:
84
+ GH_TOKEN: ${{ github.token }}
85
+ run: |
86
+ gh release create "$GITHUB_REF_NAME" dist/* \
87
+ --title "Token Bill ${GITHUB_REF_NAME#v}" \
88
+ --notes-file notes.md
@@ -0,0 +1,23 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .venv/
8
+ venv/
9
+ .pytest_cache/
10
+ .ruff_cache/
11
+ .coverage
12
+ htmlcov/
13
+
14
+ # Token Bill artifacts (root-anchored — never swallow tests/data/)
15
+ /report.html
16
+ /trace*.jsonl
17
+ /data/
18
+
19
+ # Environment / OS / editor
20
+ .env
21
+ .DS_Store
22
+ .idea/
23
+ .vscode/
@@ -0,0 +1,52 @@
1
+ # Changelog
2
+
3
+ All notable changes to Token Bill are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-07-26
11
+
12
+ ### Added
13
+
14
+ - Trace schema `tokenbill/trace@1`: JSONL, one API call per line (full request
15
+ payload + real billed usage), with strict validation and precise
16
+ `TraceError` messages, plus canonical rendering (tools → system → messages)
17
+ as the byte-level substrate for prefix comparison.
18
+ - Per-call and per-run **token waterfalls** computed from real billed `usage`:
19
+ cache reads / cache writes / uncached input / output, in tokens and dollars.
20
+ - **Redundancy analysis**: the share of cumulative billed input spend that was
21
+ re-sending byte-identical prefix the model had already seen and that was
22
+ *not* served from cache — labeled approximate, since it splits exact billed
23
+ totals by character fractions.
24
+ - **Cache simulator** replaying each run under the provider's documented
25
+ prompt-caching rules in four priced scenarios: as-billed, no-cache,
26
+ optimal-cache, and fixed-cache (after repairing detected breakers), with a
27
+ validation hook comparing predicted vs. billed cache reads whenever the
28
+ trace shows real cache activity.
29
+ - **Cache-breaker detection** with classified causes — volatile system prompt,
30
+ tool churn, history rewrite, model switch, missing breakpoint — each with
31
+ the evidence span, a one-sentence concrete fix, and the estimated dollars
32
+ recovered.
33
+ - `tokenbill demo`: four deterministic synthetic coding-agent scenarios with
34
+ *planted* waste patterns — zero keys, zero network. The flagship test
35
+ (`tests/test_demo_recovers_planted_waste.py`) asserts the analyzer recovers
36
+ exactly the waste the traces plant, so the demo doubles as the correctness
37
+ certificate.
38
+ - `tokenbill analyze`: aligned terminal summary plus a single self-contained
39
+ HTML report (inline CSS, inline SVG waterfalls and scenario bars,
40
+ dark/light via `prefers-color-scheme`, zero external resources);
41
+ `--model-price MODEL=IN,OUT` for pricing unknown or self-hosted models.
42
+ - `tokenbill.instrument.Recorder`: records real traces by duck-type-wrapping
43
+ an Anthropic-SDK-shaped client (`messages.create` and `messages.stream`,
44
+ streaming via `get_final_message()`), appending one crash-safe JSONL line
45
+ per completed call — without ever importing the SDK.
46
+ - Versioned pricing and cache-rule tables with per-entry source comments
47
+ (verified 2026-07 against the published pricing doc).
48
+ - Zero runtime dependencies (pure standard library); Python 3.10+; typed
49
+ (PEP 561 `py.typed`).
50
+
51
+ [Unreleased]: https://github.com/sedai77/tokenbill-llm-agent-cost-profiler/compare/v0.1.0...HEAD
52
+ [0.1.0]: https://github.com/sedai77/tokenbill-llm-agent-cost-profiler/releases/tag/v0.1.0
@@ -0,0 +1,52 @@
1
+ # Code of Conduct
2
+
3
+ This project adopts the spirit and structure of the
4
+ [Contributor Covenant, version 2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/).
5
+ The short version follows; the linked document governs anything this summary
6
+ leaves ambiguous.
7
+
8
+ ## Our pledge
9
+
10
+ We as members, contributors, and maintainers pledge to make participation in
11
+ Token Bill a harassment-free experience for everyone, regardless of age, body
12
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
13
+ identity and expression, level of experience, education, socio-economic status,
14
+ nationality, personal appearance, race, caste, religion, or sexual identity
15
+ and orientation.
16
+
17
+ ## Our standards
18
+
19
+ Behavior that keeps this a good place to work:
20
+
21
+ - Being kind and welcoming to newcomers and disagreeing with ideas, not people.
22
+ - Giving and gracefully accepting constructive review feedback.
23
+ - Owning mistakes, apologizing to those affected, and learning from them.
24
+ - Focusing on what is best for the project and its users.
25
+
26
+ Behavior that is not acceptable:
27
+
28
+ - Sexualized language or imagery, and sexual attention of any kind.
29
+ - Trolling, insults, personal or political attacks, and sustained disruption.
30
+ - Public or private harassment.
31
+ - Publishing others' private information (physical or email address, etc.)
32
+ without their explicit permission.
33
+ - Any other conduct that would reasonably be considered inappropriate in a
34
+ professional setting.
35
+
36
+ ## Scope and enforcement
37
+
38
+ This code applies in all project spaces (issues, pull requests, discussions)
39
+ and whenever someone is representing the project in public.
40
+
41
+ Report violations privately to the maintainers via
42
+ [GitHub's report-abuse flow](https://github.com/contact/report-abuse) or by
43
+ opening a [private security advisory](https://github.com/sedai77/tokenbill-llm-agent-cost-profiler/security/advisories/new)
44
+ marked "conduct". Maintainers will review every report, keep reporters'
45
+ identities confidential, and respond with actions proportionate to the
46
+ violation — from a warning, to temporary interaction limits, to a permanent
47
+ ban — following the Contributor Covenant's enforcement guidelines.
48
+
49
+ ## Attribution
50
+
51
+ Adapted from the [Contributor Covenant](https://www.contributor-covenant.org),
52
+ version 2.1, available under CC BY 4.0.
@@ -0,0 +1,93 @@
1
+ # Contributing to Token Bill
2
+
3
+ Thanks for helping make agent bills explainable. This document covers the
4
+ mechanics; [docs/SPEC.md](docs/SPEC.md) is the authoritative contract for the
5
+ internals — when this file and the spec disagree, the spec wins.
6
+
7
+ ## Ground rules
8
+
9
+ - **Zero runtime dependencies is a feature, not an accident.** The package
10
+ must import and run on a bare Python 3.10+ install — no exceptions, not
11
+ even optional extras. The recorder (`tokenbill/instrument.py`) duck-types
12
+ the Anthropic SDK client and must never import `anthropic`. PRs that add a
13
+ runtime dependency will be declined.
14
+ - **The honesty rules are product law.** Every dollar total comes from real
15
+ billed `usage` fields — exact. Character-based tokenization
16
+ (`approx_tokens`, chars ÷ 3.7) is used *only* for proportional attribution
17
+ and divergence localization, is always rescaled so segments sum to the
18
+ billed total, and is labeled "approx" everywhere it surfaces. A PR that
19
+ presents an approximate number as billed will be declined regardless of how
20
+ useful the number is.
21
+ - **Determinism is load-bearing.** Every stochastic step takes an explicit
22
+ `seed` and derives its stream via `common.rng(seed, *scope)`. Never call
23
+ `random` module-level functions or `hash()` for anything reproducible.
24
+ - **The flagship test is the contract.**
25
+ `tests/test_demo_recovers_planted_waste.py` asserts the analyzer, simulator,
26
+ and breaker detector recover the demo traces' planted waste, with
27
+ expectations derived from the documented usage construction. If your change
28
+ breaks it, either the instruments are wrong or the traces changed — either
29
+ way, that is a design conversation, not a tolerance bump.
30
+
31
+ ## Development setup
32
+
33
+ We use [uv](https://docs.astral.sh/uv/). No install step is needed beyond
34
+ cloning:
35
+
36
+ ```bash
37
+ git clone https://github.com/sedai77/tokenbill-llm-agent-cost-profiler
38
+ cd tokenbill-llm-agent-cost-profiler
39
+
40
+ # Run the full offline pipeline
41
+ uv run tokenbill demo -o report.html
42
+
43
+ # Tests (any supported interpreter; CI runs 3.10 and 3.13)
44
+ uv run --python 3.12 --extra dev pytest -q
45
+
46
+ # Lint
47
+ uv run --python 3.12 --extra dev ruff check .
48
+ ```
49
+
50
+ Or via the Makefile: `make demo`, `make test`, `make lint`.
51
+
52
+ ## Tests
53
+
54
+ Everything passes offline — no network, no API keys, ever. CI has no secrets
55
+ and never will. Tests for the recorder use a tiny fake client double (a few
56
+ lines), never the real SDK. Tests that need trace input generate it from
57
+ `tokenbill/demo_traces.py` or build `Call` objects inline — do not add tests
58
+ that download anything or check in large trace fixtures containing real
59
+ prompts.
60
+
61
+ ## Style
62
+
63
+ - `from __future__ import annotations`, full type hints, frozen dataclasses
64
+ for value types.
65
+ - Loggers are named `logging.getLogger("tokenbill.<module>")`.
66
+ - Docstrings on public functions; comments only for invariants the code
67
+ cannot express.
68
+ - `ruff check .` must pass (config in `pyproject.toml`; line length 100).
69
+
70
+ ## Submitting changes
71
+
72
+ 1. Fork, branch, and keep the change focused.
73
+ 2. Add or update tests next to the code you touched.
74
+ 3. Update `CHANGELOG.md` under `[Unreleased]` for user-visible changes.
75
+ 4. Confirm `pytest -q` and `ruff check .` are clean.
76
+ 5. Open the PR — the template walks through the checklist.
77
+
78
+ Metric definitions, cache-rule constants, and pricing entries are public API:
79
+ changing one changes what a dollar figure means. Such changes need a matching
80
+ update to [DESIGN.md](DESIGN.md), the report's methodology footnotes, and the
81
+ flagship test's derived expectations — together, in the same PR. Pricing and
82
+ cache-rule edits must cite the published pricing doc in the entry's source
83
+ comment; the values are re-verified before each release.
84
+
85
+ ## Releasing (maintainers)
86
+
87
+ 1. Re-verify the pricing table against the published pricing doc (see the
88
+ comment in `tokenbill/pricing.py`).
89
+ 2. Move `[Unreleased]` entries into a new dated section in `CHANGELOG.md`.
90
+ 3. Bump `__version__` in `tokenbill/__init__.py`.
91
+ 4. Tag `v<version>` and push the tag. `release.yml` builds, verifies the tag
92
+ matches the package version, publishes to PyPI via Trusted Publishing (no
93
+ tokens), and creates a GitHub release with the changelog section as notes.