ai-accountant 0.0.2__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 (60) hide show
  1. ai_accountant-0.0.2/LICENSE +202 -0
  2. ai_accountant-0.0.2/MANIFEST.in +16 -0
  3. ai_accountant-0.0.2/NOTICE +4 -0
  4. ai_accountant-0.0.2/PKG-INFO +399 -0
  5. ai_accountant-0.0.2/README.md +377 -0
  6. ai_accountant-0.0.2/accountant.py +794 -0
  7. ai_accountant-0.0.2/accountant_pricing/__init__.py +15 -0
  8. ai_accountant-0.0.2/accountant_pricing/campaigns/__init__.py +8 -0
  9. ai_accountant-0.0.2/accountant_pricing/campaigns/facade.py +84 -0
  10. ai_accountant-0.0.2/accountant_pricing/campaigns/interfaces.py +11 -0
  11. ai_accountant-0.0.2/accountant_pricing/campaigns/models.py +116 -0
  12. ai_accountant-0.0.2/accountant_pricing/campaigns/plugins/__init__.py +1 -0
  13. ai_accountant-0.0.2/accountant_pricing/campaigns/plugins/structured.py +47 -0
  14. ai_accountant-0.0.2/accountant_pricing/campaigns/plugins/text.py +96 -0
  15. ai_accountant-0.0.2/accountant_pricing/models.py +126 -0
  16. ai_accountant-0.0.2/accountant_pricing/providers/__init__.py +25 -0
  17. ai_accountant-0.0.2/accountant_pricing/providers/anthropic.py +26 -0
  18. ai_accountant-0.0.2/accountant_pricing/providers/deepseek.py +6 -0
  19. ai_accountant-0.0.2/accountant_pricing/providers/openai.py +19 -0
  20. ai_accountant-0.0.2/accountant_pricing/providers/openrouter.py +13 -0
  21. ai_accountant-0.0.2/accountant_pricing/providers/zai.py +6 -0
  22. ai_accountant-0.0.2/accountant_pricing/resolver.py +160 -0
  23. ai_accountant-0.0.2/accountant_pricing/sources.py +122 -0
  24. ai_accountant-0.0.2/ai_accountant/__init__.py +19 -0
  25. ai_accountant-0.0.2/ai_accountant/sessions/__init__.py +6 -0
  26. ai_accountant-0.0.2/ai_accountant/sessions/models.py +56 -0
  27. ai_accountant-0.0.2/ai_accountant/sessions/parsers/__init__.py +1 -0
  28. ai_accountant-0.0.2/ai_accountant/sessions/parsers/claude.py +270 -0
  29. ai_accountant-0.0.2/ai_accountant/sessions/parsers/codex.py +159 -0
  30. ai_accountant-0.0.2/ai_accountant/sessions/parsers/machina.py +301 -0
  31. ai_accountant-0.0.2/ai_accountant/sessions/reader.py +154 -0
  32. ai_accountant-0.0.2/ai_accountant.egg-info/PKG-INFO +399 -0
  33. ai_accountant-0.0.2/ai_accountant.egg-info/SOURCES.txt +58 -0
  34. ai_accountant-0.0.2/ai_accountant.egg-info/dependency_links.txt +1 -0
  35. ai_accountant-0.0.2/ai_accountant.egg-info/entry_points.txt +2 -0
  36. ai_accountant-0.0.2/ai_accountant.egg-info/scm_file_list.json +78 -0
  37. ai_accountant-0.0.2/ai_accountant.egg-info/top_level.txt +6 -0
  38. ai_accountant-0.0.2/claude_sessions.py +5 -0
  39. ai_accountant-0.0.2/docs/README.md +116 -0
  40. ai_accountant-0.0.2/docs/campaign-classification.md +110 -0
  41. ai_accountant-0.0.2/docs/pricing-library.md +213 -0
  42. ai_accountant-0.0.2/docs/releasing.md +169 -0
  43. ai_accountant-0.0.2/docs/session-library.md +125 -0
  44. ai_accountant-0.0.2/docs/source-map.md +48 -0
  45. ai_accountant-0.0.2/machina_sessions.py +5 -0
  46. ai_accountant-0.0.2/prices.json +145 -0
  47. ai_accountant-0.0.2/pricing_index.py +92 -0
  48. ai_accountant-0.0.2/pyproject.toml +41 -0
  49. ai_accountant-0.0.2/scripts/check_distribution.py +128 -0
  50. ai_accountant-0.0.2/scripts/smoke_installed.py +97 -0
  51. ai_accountant-0.0.2/setup.cfg +4 -0
  52. ai_accountant-0.0.2/tests/test_accountant.py +379 -0
  53. ai_accountant-0.0.2/tests/test_campaigns.py +164 -0
  54. ai_accountant-0.0.2/tests/test_claude_sessions.py +219 -0
  55. ai_accountant-0.0.2/tests/test_machina_sessions.py +198 -0
  56. ai_accountant-0.0.2/tests/test_multiclient.py +298 -0
  57. ai_accountant-0.0.2/tests/test_pricing_library.py +376 -0
  58. ai_accountant-0.0.2/tests/test_release_checks.py +134 -0
  59. ai_accountant-0.0.2/tests/test_scm_builds.py +97 -0
  60. ai_accountant-0.0.2/tests/test_session_library.py +157 -0
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
@@ -0,0 +1,16 @@
1
+ include LICENSE NOTICE README.md
2
+ include prices.json
3
+ include docs/README.md docs/source-map.md docs/pricing-library.md
4
+ include docs/campaign-classification.md docs/releasing.md docs/session-library.md
5
+ recursive-include tests test_*.py
6
+ recursive-include scripts *.py
7
+ prune .uv-cache
8
+ prune .venv
9
+ prune build
10
+ prune dist
11
+ prune docs/in-work
12
+ prune docs/historical
13
+ prune .github
14
+ exclude .gitattributes .gitignore uv.lock
15
+ exclude ai_accountant.egg-info/scm_version.json
16
+ global-exclude __pycache__ *.py[cod] *.sqlite* *.db *.db-* *.jsonl *.csv .env .env.*
@@ -0,0 +1,4 @@
1
+ Accountant (ai-accountant)
2
+ Copyright 2026 Joakim Henriksson
3
+
4
+ Licensed under the Apache License, Version 2.0.
@@ -0,0 +1,399 @@
1
+ Metadata-Version: 2.4
2
+ Name: ai-accountant
3
+ Version: 0.0.2
4
+ Summary: Provider pricing, token cost calculation, and local AI session accounting
5
+ Author: Joakim Henriksson
6
+ License-Expression: Apache-2.0
7
+ Keywords: ai,pricing,tokens,accounting
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Requires-Python: >=3.11
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ License-File: NOTICE
21
+ Dynamic: license-file
22
+
23
+ # AI Accountant
24
+
25
+ A Python library for reading AI session usage and calculating token costs, with
26
+ a bundled local accounting CLI. Licensed under
27
+ [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
28
+
29
+ The distribution name is `ai-accountant`; the primary import is `ai_accountant`.
30
+ Release versions are derived from Git tags. Builds from a source distribution
31
+ retain that version without needing Git.
32
+ Install this checkout for development:
33
+
34
+ ```sh
35
+ uv pip install .
36
+ ```
37
+
38
+ ```python
39
+ from ai_accountant import SessionReader, ModelPrice, calculate_cost
40
+
41
+ reader = SessionReader()
42
+ # sources = reader.discover("codex", home="/path/to/.codex")
43
+ # session = reader.parse(sources[0], client="codex")
44
+
45
+ price = ModelPrice("example", "model", {"input": "2", "output": "10"}, "example")
46
+ cost = calculate_cost({"input_tokens": 1000, "output_tokens": 100}, price)
47
+ print(cost.amount_usd) # Decimal('0.003')
48
+ ```
49
+
50
+ ## Library
51
+
52
+ ### Session reading
53
+
54
+ `SessionReader` discovers local Codex, Claude Code and Machina accounting sources.
55
+ The client is explicit; discovery and parsing only read files when called.
56
+
57
+ ```python
58
+ from ai_accountant import SessionReader
59
+
60
+ reader = SessionReader()
61
+ for path in reader.discover("codex"): # or "claude" / "machina"
62
+ session = reader.parse(path, client="codex")
63
+ print(session.session_id, session.models, session.warnings)
64
+ for event in session.events:
65
+ print(event["provider"], event["model"], event["tokens"])
66
+ ```
67
+
68
+ Pass `home="/path/to/store"` to `discover` to override the client's default
69
+ location. A parsed session contains metadata, per-model totals, normalized usage
70
+ observations, warnings and Codex tool statistics. It does not contain a full
71
+ conversation replay. SQLite indexing and reports belong to the CLI.
72
+
73
+ Normalized `input_tokens` includes fresh input, cache reads and cache writes;
74
+ `cached_input_tokens` and `cache_write_input_tokens` are subsets. Five-minute
75
+ and one-hour write counts are subsets of cache writes. Reasoning is included
76
+ in output. Use `usage_complete` and `reasoning_known` to interpret missing
77
+ evidence; an omitted category represented as zero is not proof of measured zero.
78
+ Source-specific parsing deduplicates usage snapshots and excludes inherited
79
+ Codex fork counters. Provider and model identities are preserved.
80
+
81
+ ### Provider pricing
82
+
83
+ `default_resolver()` supplies provider modules for OpenAI, Anthropic, DeepSeek,
84
+ Z.ai and OpenRouter. Refresh explicitly; import, construction and lookup perform
85
+ no networking. Most built-ins currently use the LiteLLM community catalogue;
86
+ OpenRouter uses its own public API. Provenance is retained.
87
+
88
+ ```python
89
+ import asyncio
90
+ from ai_accountant import default_resolver
91
+
92
+ prices = default_resolver()
93
+ results = asyncio.run(prices.refresh("anthropic"))
94
+ for result in results:
95
+ print(result.status, result.error)
96
+ for price in prices.search("*opus*", provider="anthropic"):
97
+ print(price.qualified_id, price.rates)
98
+ ```
99
+
100
+ Prices use Decimal USD per million tokens. Refresh failures retain the last good
101
+ snapshot. A host owns refresh scheduling, persistence and its event history.
102
+ `calculate_cost` accepts normalized counts and a `ModelPrice`; pass
103
+ `usage_complete=event["usage_complete"]` when pricing parsed observations.
104
+ Missing prices or consumed rates produce an unknown cost, never an assumed zero.
105
+ Context-tier pricing needs the per-request context size. These quotes estimate
106
+ API cost, not subscription quota usage or reconstructed historical invoices.
107
+
108
+ Custom provider and session-parser implementations can be registered explicitly.
109
+ Installed plugin discovery is opt-in. For API signatures and docstrings:
110
+
111
+ ```python
112
+ from ai_accountant import SessionReader, PricingResolver, calculate_cost
113
+
114
+ help(SessionReader)
115
+ help(PricingResolver)
116
+ help(calculate_cost)
117
+ ```
118
+
119
+ The source distribution includes detailed references at `docs/session-library.md`,
120
+ `docs/pricing-library.md` and `docs/campaign-classification.md`, with maintainer
121
+ release instructions in `docs/releasing.md`. They are archive paths, not hosted
122
+ documentation links.
123
+
124
+ ## CLI
125
+
126
+ A Python CLI run with uv. It reads local Codex rollouts, Claude Code transcripts,
127
+ and Machina turns, then reports token usage per model and estimated USD cost.
128
+ SQLite indexing makes combined history queryable. Codex remains the default
129
+ source so existing commands keep their behavior.
130
+
131
+ The installable `accountant_pricing` compatibility facade provides
132
+ provider plugins, live price acquisition, an in-memory resolver and pure Decimal
133
+ calculation for other programs. Consumers own their usage histories; SQLite
134
+ snapshot persistence belongs to this CLI.
135
+
136
+ ```powershell
137
+ uv run accountant.py --list
138
+ uv run accountant.py SESSION_UUID
139
+ uv run accountant.py SESSION_UUID --parent-only
140
+ uv run accountant.py --latest
141
+ uv run accountant.py 'C:\path\to\rollout.jsonl' --prices prices.json
142
+ uv run accountant.py --all --prices prices.json
143
+ uv run accountant.py --resets --prices prices.json
144
+ uv run accountant.py --reset latest --prices prices.json
145
+ uv run accountant.py --reset 1791580844 --window primary --json
146
+ uv run accountant.py --client claude --list
147
+ uv run accountant.py --client machina --all --json
148
+ uv run accountant.py --client all --all --json
149
+ ```
150
+
151
+ Use `--client codex|claude|machina|all` to choose a source. `--all` aggregates
152
+ the clients selected by `--client`. Codex history defaults to `$CODEX_HOME` or
153
+ `~/.codex`; Claude uses `CLAUDE_CONFIG_DIR` or `~/.claude`; Machina uses
154
+ `MACHINA_HOME` or `~/.machina`. Override those locations with `--codex-home`,
155
+ `--claude-home`, and `--machina-home`. Claude and Machina session IDs are
156
+ prefixed with `claude:` and `machina:` in combined reports and the database;
157
+ Codex UUIDs remain unchanged. `--latest` picks the most recently modified file
158
+ among selected clients, and `--list` includes each session's client.
159
+
160
+ `--reset` accepts `latest`, a Unix reset deadline, or an ISO timestamp with a
161
+ timezone. `--window secondary` selects the secondary quota. Window lengths come
162
+ from the logs; primary does not necessarily mean a five-hour window. `--latest`
163
+ selects the most recently modified file, including archived and subagent sessions.
164
+ Use a UUID or path when selecting a particular conversation. Unique UUID prefixes
165
+ work too. `--codex-home PATH` supports other installations or exported histories.
166
+ Session reports include recursively linked subagent rollouts by default, even
167
+ when archived. `--parent-only` excludes them. JSON includes a per-session
168
+ breakdown. Only explicit parent-thread links are followed; forks are separate
169
+ conversations. Agents with no parent link cannot be attributed to a conversation.
170
+
171
+ Codex session reports also tally tool requests by name and recorded completed tool
172
+ actions by type (commands, file changes, web extensions, and MCP calls). These
173
+ are two overlapping views: an `exec` request can run several underlying tools,
174
+ so do not add the two totals. Outputs and collaboration UI echoes are excluded;
175
+ repeated call/action IDs and copied pre-creation fork history are not counted
176
+ again. Counts include recorded failed actions and requests without outputs.
177
+ Shell commands can perform multiple file reads or searches; individual file
178
+ operations and hidden tool reviews cannot be reliably inferred from these counts.
179
+ Run `--sync` to populate tool statistics in an older index. An index without
180
+ these statistics reports them as unavailable rather than zero. Claude and Machina
181
+ report tool statistics as unavailable. Aggregate token and quota reports do not
182
+ include tool counts.
183
+
184
+ ## SQLite
185
+
186
+ ```powershell
187
+ uv run accountant.py --sync
188
+ uv run accountant.py SESSION_UUID --db accountant.sqlite3
189
+ uv run accountant.py --all --db accountant.sqlite3 --prices prices.json
190
+ uv run accountant.py --resets --db accountant.sqlite3 --prices prices.json
191
+ uv run accountant.py --reset latest --db accountant.sqlite3 --prices prices.json
192
+ uv run accountant.py --query 'SELECT * FROM model_usage ORDER BY total_tokens DESC'
193
+ uv run accountant.py --query 'SELECT * FROM reset_usage ORDER BY resets_at, model'
194
+ ```
195
+
196
+ Run `--sync` to migrate older indexes before using session or aggregate reports;
197
+ read-only SQL remains available on old schemas. Sync reparses available sources
198
+ when the parser version changes, and backfills missing Codex tool statistics.
199
+ Sources that are no longer available retain their indexed data; migrated legacy
200
+ events remain conservatively marked incomplete until reimported.
201
+
202
+ Select clients with `--client` when syncing. Sync again to import new or changed
203
+ logs. Unchanged files are skipped using size and modification time; changed files
204
+ are replaced transactionally. Archiving
205
+ a session updates its path without duplicating usage. Sessions whose logs are later
206
+ removed remain in the database. The database contains usage metadata, not prompts
207
+ or responses. `--db PATH` overrides the database location. Aggregate commands with
208
+ `--db` read the indexed snapshot; commands without it read the logs directly.
209
+ `--sync --rebuild` refreshes only the selected client index; use
210
+ `--client all --sync --rebuild` to rebuild every client.
211
+
212
+ Tables: `sessions` (including the source client), `usage_events` (usage increments,
213
+ provider, source-event identity, completeness flags, cache TTL counts, and raw
214
+ rate-limit JSON), and
215
+ `rate_limits` (primary/secondary observations), plus `session_links` (parent,
216
+ fork, and source metadata), and `tool_statistics` (Codex tool counts).
217
+ Views: `model_usage`, `reset_usage`, and
218
+ `session_roots`, which maps each session to itself and its recursively linked
219
+ descendants. The recursive view guards against cycles. `--query` opens SQLite
220
+ read-only and prints JSON. For example, aggregate a root session and all its
221
+ descendants by model:
222
+
223
+ ```sql
224
+ SELECT u.model, SUM(u.input_tokens + u.output_tokens) AS total_tokens
225
+ FROM session_roots r
226
+ JOIN usage_events u ON u.session_id = r.session_id
227
+ WHERE r.root_session_id = 'SESSION_UUID'
228
+ GROUP BY u.model
229
+ ORDER BY total_tokens DESC;
230
+ ```
231
+
232
+ Other queries:
233
+
234
+ ```sql
235
+ SELECT u.model, SUM(u.input_tokens + u.output_tokens) AS total_tokens
236
+ FROM usage_events u
237
+ WHERE julianday(u.timestamp) >= julianday('2026-10-01T00:00:00Z')
238
+ AND julianday(u.timestamp) < julianday('2026-10-08T00:00:00Z')
239
+ GROUP BY u.model;
240
+ ```
241
+
242
+ ## Prices
243
+
244
+ The script automatically loads `prices.json` beside `accountant.py`. This base
245
+ contains the user's supplied OpenAI rates and officially sourced DeepSeek,
246
+ Anthropic and Z.ai estimates,
247
+ in **USD per million tokens**. OpenAI rates were supplied on 2026-10-04 and were
248
+ not independently fetched during onboarding. Use `--prices FILE` to override the base. For example:
249
+
250
+ ```json
251
+ {
252
+ "gpt-6.1-sol": {"input": 2.0, "cached_input": 0.1, "output": 10.0},
253
+ "gpt-6-luna": {"input": 0.1, "cached_input": 0.01, "output": 0.5}
254
+ }
255
+ ```
256
+
257
+ The flat sheet is not fetched automatically. Provider price acquisition is opt-in:
258
+
259
+ ```powershell
260
+ uv run accountant.py --refresh-prices --db accountant.sqlite3
261
+ uv run accountant.py --refresh-prices --price-provider openai --force-prices
262
+ uv run accountant.py --price-history --price-provider openai
263
+ uv run accountant.py --all --db accountant.sqlite3 --price-provider openai --json
264
+ ```
265
+
266
+ The source archive's `docs/pricing-library.md` details source coverage,
267
+ plugin extension, TTL, failure retention and snapshot history. Snapshot reports
268
+ use standard base-context API reference rates and include provenance/assumptions.
269
+ These are current estimates rather than reconstructed invoices.
270
+
271
+ GPT-5.5 Pro's "no discount" is represented
272
+ by a cached-input rate equal to its input rate. The user supplied `gpt-5.6-luna`
273
+ rates matching `gpt-6-luna`, and `gpt-5.6-terra` input rates matching `gpt-6.1-sol`
274
+ with output at $12 per million tokens. The user supplied free pricing for
275
+ `codex-auto-review`; all its token rates are zero. No other aliases are inferred.
276
+ DeepSeek V4 Flash and Pro have explicit bare and `deepseek/`-prefixed entries.
277
+ Their current [official prices](https://api-docs.deepseek.com/quick_start/pricing/)
278
+ were retrieved via the [DeepSeek homepage](https://www.deepseek.com/) on
279
+ 2026-10-12. The flat configuration uses **peak** rates: input/cache/output
280
+ $0.30/$0.006/$1.20 for Flash and $1.32/$0.044/$3.96 for Pro, per million tokens.
281
+ Off-peak rates are half and retained as metadata; the loader does not apply them.
282
+ Peak hours are weekdays 01:00–04:00 and 06:00–10:00 UTC excluding Chinese public
283
+ holidays. The official page explicitly bills the retired V4 Flash alias at
284
+ current V4.1 Flash rates; Pro currently identifies V4-Pro-0813. These are current
285
+ direct-provider estimates applied to history, not reconstructed invoices. Codex
286
+ DeepSeek records report provider `cog`; its actual charges are not established.
287
+
288
+ The observed `claude-opus-4-8` model has [official Anthropic rates](https://platform.claude.com/docs/en/models/opus-4-8/overview)
289
+ retrieved on 2026-10-12: input/cache-read/output $5/$0.50/$25 per million,
290
+ with separate five-minute and one-hour cache-write rates $6.25/$10.
291
+ Unclassified cache writes have no assumed rate. The user reports using alternative
292
+ endpoints rather than Anthropic; for these logs the Opus rate is a hypothetical
293
+ comparison. The CLI still prices exact model labels and does not validate the
294
+ serving endpoint or actual underlying model.
295
+
296
+ The observed `glm-5.2` model has [official Z.ai rates](https://docs.z.ai/guides/overview/pricing)
297
+ retrieved on 2026-10-12: input/cache-read/output $1.40/$0.26/$4.40 per million.
298
+ Cache storage is temporarily free on that page; it is not mapped to a token
299
+ cache-write rate. These are standard API estimates, not Coding Plan subscription
300
+ charges. Incomplete GLM usage remains unpriced even with known model rates.
301
+
302
+ Optional `cache_write` supplies a generic cache-write rate. `cache_write_5m` and
303
+ `cache_write_1h` can price the two TTL buckets separately; each overrides the
304
+ generic rate for its bucket. Unclassified writes still need `cache_write`.
305
+ Unknown models or missing applicable rates yield an unknown total, with a priced
306
+ subtotal. Exact names avoid guessing prices for aliases. Estimates apply the supplied
307
+ flat rates to all history; they do not infer service-tier multipliers, historical
308
+ price changes, long-context rates, subscription charges, or tool fees.
309
+
310
+ Cost = `(uncached input × input rate + cached input × cached rate + cache-write
311
+ input × cache-write rate + output × output rate) / 1,000,000`.
312
+ Cached and cache-write tokens are treated as subsets of input. Cache-write TTL
313
+ counts are subsets of total cache writes. Reports show `cached_percent` as cached
314
+ input divided by inclusive input, and `reasoning_output_tokens: null` when a
315
+ client does not report reasoning counts. Incomplete usage is retained and warned
316
+ about; its cost is not treated as zero. A model with any incomplete usage is left
317
+ out of the priced subtotal as a whole. Provider names are stored only when a log
318
+ reports them. Rates apply only to explicit entries in the price sheet, including the observed
319
+ Anthropic and Z.ai model IDs. Claude Code client identity does not imply an
320
+ Anthropic model or endpoint. Older
321
+ Machina turn totals with ambiguous input/cache semantics stay visible as observed
322
+ values, carry an incomplete flag, and do not produce a cached percentage or cost.
323
+ Current canonical Machina request rounds preserve direct input, cache reads,
324
+ cache writes, and output separately.
325
+ Machina `session.yaml` metadata is read with PyYAML's safe loader when PyYAML is
326
+ installed; without it, the reader uses transcript-directory identity and still
327
+ accounts for current canonical request rounds. To enable YAML metadata for one
328
+ run, use `uv run --with pyyaml accountant.py --client machina --latest`.
329
+
330
+ CLI aggregates and prices group by exact model name across selected clients and
331
+ providers. SQL `model_usage` separates client/provider/model; `reset_usage` also
332
+ separates the quota identity. Supply rates only when the model-wide pricing
333
+ assumption is appropriate. Codex quota resets are recorded; other clients
334
+ currently have no quota observations and appear as unassigned usage.
335
+
336
+ ## Interpretation
337
+
338
+ - Codex metadata `source: "vscode"` does not establish VS Code use. Local
339
+ rollouts with that marker also record `originator: "codex-tui"` or
340
+ `"JetBrains.IntelliJ IDEA"`. Report source and originator as recorded metadata;
341
+ neither proves deliberate editor use or agent activity.
342
+
343
+ - Cumulative counter differences avoid counting duplicate notifications. Model
344
+ attribution follows the latest `turn_context`. Decreasing counters start a new
345
+ segment and generate a warning. Missing models stay `unknown`.
346
+ - Reset reports assign each increment to the reset deadline attached to its event.
347
+ Deadlines within 60 seconds are grouped for the same limit ID and window length
348
+ to absorb observed clock jitter. SQL `reset_usage` preserves exact deadlines.
349
+ - Percentages are account quota observations, not per-model token percentages.
350
+ Reports show first, last, and maximum observed percentages; percentages are never
351
+ added. Times are recorded event timestamps and reset deadlines in UTC, not elapsed
352
+ work time. Usage without a recorded reset is reported separately.
353
+ - Results cover available logs only. Missing history, usage imported by a fork,
354
+ inherited cumulative baselines, and consumption from other devices may affect
355
+ completeness. A large cumulative increment is attributed to its current event;
356
+ absent intermediate records cannot reconstruct its earlier timing or model.
357
+ For forks with creation metadata, copied events before creation establish the
358
+ counter baseline but do not contribute usage to the fork. A counter emitted at
359
+ creation before local turn/request activity also establishes the inherited
360
+ baseline. Real usage and tools at creation after activity starts remain counted.
361
+ Parser version changes trigger reimport on the next sync, including this fix.
362
+ The first session
363
+ metadata record identifies the rollout; copied parent metadata does not replace it.
364
+ - Active rollouts can have a partially written final line. Invalid lines are skipped
365
+ with warnings and can be recovered on the next sync when the file changes.
366
+
367
+ ## Verification
368
+
369
+ ```powershell
370
+ uv run python -m unittest discover -s tests -v
371
+ ```
372
+
373
+ If your environment restricts the default uv cache directory, add
374
+ `--cache-dir .uv-cache` immediately after `uv`.
375
+
376
+ The CLI needs no third-party dependencies for token accounting and can also run
377
+ directly with Python 3.11 or later:
378
+
379
+ ```sh
380
+ python3 accountant.py --help
381
+ python3 -m unittest discover -s tests -v
382
+ ```
383
+
384
+ GitHub Actions runs the tests on Python 3.11 and 3.13. Commit `uv.lock` with
385
+ changes to project dependencies. Local environments, SQLite indexes, session
386
+ logs, and generated historical usage reports are excluded from version control;
387
+ `prices.json` is the shared flat-sheet fallback; provider acquisition is owned by
388
+ the pricing library.
389
+
390
+ ## Development and local artifacts
391
+
392
+ The source distribution includes `docs/README.md` (architecture) and
393
+ `docs/source-map.md` for ownership, adapter contracts and index lifecycle.
394
+
395
+ The onboarding copied the seed index, its backup, historical-models.json,
396
+ claude-cache-report.json, claude-cache-by-session.csv and diagnose-network.ps1
397
+ into this workspace as ignored local files. They are snapshots and diagnostics,
398
+ not shared project inputs. The seed originals were preserved. Environments and
399
+ caches are regenerated locally rather than copied.