imbue-mngr-usage 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,346 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ **/__pycache__/
3
+ **/*.py[codz]
4
+ **/*$py.class
5
+
6
+ # macOS Finder metadata
7
+ **/.DS_Store
8
+
9
+ # C extensions
10
+ **/*.so
11
+
12
+ # Distribution / packaging
13
+ **/.Python
14
+ **/build/
15
+ **/develop-eggs/
16
+ **/dist/
17
+ **/downloads/
18
+ **/eggs/
19
+ **/.eggs/
20
+ **/lib/
21
+ **/lib64/
22
+ **/parts/
23
+ **/sdist/
24
+ **/var/
25
+ **/wheels/
26
+ **/share/python-wheels/
27
+ **/*.egg-info/
28
+ **/.installed.cfg
29
+ **/*.egg
30
+ **/MANIFEST
31
+
32
+ # PyInstaller
33
+ # Usually these files are written by a python script from a template
34
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
35
+ **/*.manifest
36
+ **/*.spec
37
+
38
+ # Installer logs
39
+ **/pip-log.txt
40
+ **/pip-delete-this-directory.txt
41
+
42
+ # Unit test / coverage reports
43
+ **/htmlcov/
44
+ **/.tox/
45
+ **/.nox/
46
+ **/.coverage
47
+ **/.coverage.*
48
+ **/.cache
49
+ **/nosetests.xml
50
+ **/coverage.xml
51
+ **/*.cover
52
+ **/*.py.cover
53
+ **/.hypothesis/
54
+ **/.pytest_cache/
55
+ **/cover/
56
+
57
+ # Translations
58
+ **/*.mo
59
+ **/*.pot
60
+
61
+ # Django stuff:
62
+ **/*.log
63
+ **/local_settings.py
64
+ **/db.sqlite3
65
+ **/db.sqlite3-journal
66
+
67
+ # Flask stuff:
68
+ **/instance/
69
+ **/.webassets-cache
70
+
71
+ # Scrapy stuff:
72
+ **/.scrapy
73
+
74
+ # Sphinx documentation
75
+ **/docs/_build/
76
+
77
+ # PyBuilder
78
+ **/.pybuilder/
79
+ **/target/
80
+
81
+ # Jupyter Notebook
82
+ **/.ipynb_checkpoints
83
+
84
+ # IPython
85
+ **/profile_default/
86
+ **/ipython_config.py
87
+
88
+ # pyenv
89
+ # For a library or package, you might want to ignore these files since the code is
90
+ # intended to run in multiple environments; otherwise, check them in:
91
+ # .python-version
92
+
93
+ # pipenv
94
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
95
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
96
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
97
+ # install all needed dependencies.
98
+ #Pipfile.lock
99
+
100
+ # UV
101
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
102
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
103
+ # commonly ignored for libraries.
104
+ #uv.lock
105
+
106
+ # poetry
107
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
108
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
109
+ # commonly ignored for libraries.
110
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
111
+ #poetry.lock
112
+ #poetry.toml
113
+
114
+ # pdm
115
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
116
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
117
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
118
+ #pdm.lock
119
+ #pdm.toml
120
+ **/.pdm-python
121
+ **/.pdm-build/
122
+
123
+ # pixi
124
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
125
+ #pixi.lock
126
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
127
+ # in the .venv directory. It is recommended not to include this directory in version control.
128
+ **/.pixi
129
+
130
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
131
+ **/__pypackages__/
132
+
133
+ # Celery stuff
134
+ **/celerybeat-schedule
135
+ **/celerybeat.pid
136
+
137
+ # SageMath parsed files
138
+ **/*.sage.py
139
+
140
+ # Environments
141
+ **/.env
142
+ **/.envrc
143
+ **/.venv
144
+ **/.venv-*
145
+ **/env/
146
+ **/venv/
147
+ **/ENV/
148
+ **/env.bak/
149
+ **/venv.bak/
150
+
151
+ # Spyder project settings
152
+ **/.spyderproject
153
+ **/.spyproject
154
+
155
+ # Rope project settings
156
+ **/.ropeproject
157
+
158
+ # mkdocs documentation
159
+ **/site
160
+
161
+ # mypy
162
+ **/.mypy_cache/
163
+ **/.dmypy.json
164
+ **/dmypy.json
165
+
166
+ # Pyre type checker
167
+ **/.pyre/
168
+
169
+ # pytype static type analyzer
170
+ **/.pytype/
171
+
172
+ # Cython debug symbols
173
+ **/cython_debug/
174
+
175
+ # PyCharm
176
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
177
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
178
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
179
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
180
+ **/.idea/
181
+
182
+ # Abstra
183
+ # Abstra is an AI-powered process automation framework.
184
+ # Ignore directories containing user credentials, local state, and settings.
185
+ # Learn more at https://abstra.io/docs
186
+ **/.abstra/
187
+
188
+ # Visual Studio Code
189
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
190
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
191
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
192
+ # you could uncomment the following to ignore the entire vscode folder
193
+ # .vscode/
194
+
195
+ # Ruff stuff:
196
+ **/.ruff_cache/
197
+
198
+ # PyPI configuration file
199
+ **/.pypirc
200
+
201
+ # Cursor
202
+ # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
203
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
204
+ # refer to https://docs.cursor.com/context/ignore-files
205
+ **/.cursorignore
206
+ **/.cursorindexingignore
207
+
208
+ # Marimo
209
+ **/marimo/_static/
210
+ **/marimo/_lsp/
211
+ **/__marimo__/
212
+
213
+ # task folders for local custom claude workflow
214
+ */*/_tasks/
215
+ **/.sesskey
216
+
217
+ # Node.js
218
+ **/node_modules/
219
+
220
+ # Frontend build artifacts
221
+ **/frontend-dist/
222
+
223
+ # Claude Code local settings (session-specific permissions)
224
+ **/.claude/settings.local.json
225
+
226
+ # Local Claude settings backup files
227
+ **/.claude/*.bak
228
+
229
+ # Claude worktress
230
+ **/.claude/worktrees
231
+
232
+ # Agent definitions deployed by mngr_subagent_proxy at session start
233
+ **/.claude/agents/
234
+
235
+ # Local mngr settings (not committed)
236
+ **/.mngr/settings.local.toml
237
+
238
+ # Local working / scratch markdown (not committed)
239
+ **/*.local.md
240
+
241
+ # Local working / scratch shell scripts (not committed)
242
+ # (covers user notification scripts like scripts/notify_user.local.sh, too)
243
+ **/*.local.sh
244
+
245
+ # Test output files (slow tests, coverage summaries)
246
+ **/.test_output/
247
+
248
+ # TMR output directories (reports + test outputs)
249
+ **/tmr_*/
250
+
251
+ # Active session marker file (used to detect interrupted sessions)
252
+ **/.claude/active
253
+
254
+ # we stick the image build artifacts here
255
+ **/.mngr/dev/build/
256
+ **/.mngr/dev/secrets/
257
+
258
+ # Offload caches and local files
259
+ **/.offload/**
260
+ **/test-results
261
+
262
+ # Minds deploy-time build artifacts.
263
+ # Ignore contents so we can un-ignore the committed `template/` subtree: git
264
+ # won't descend into a fully-ignored directory, so negations must live below
265
+ # the contents pattern rather than below the directory pattern.
266
+ **/.minds/*
267
+ # The template directory is committed: it defines the expected keys for each
268
+ # <service>.sh file, with empty values. `scripts/push_modal_secrets.py` uses
269
+ # it to check that per-env .sh files declare every template key.
270
+ !/.minds/template/
271
+ !/.minds/template/**
272
+ # Vault ACL policies for the minds tiers. The HCL is non-sensitive and is
273
+ # the source of truth for the policies pushed to Vault via
274
+ # `vault policy write <name> .minds/policies/<name>.hcl`.
275
+ !/.minds/policies/
276
+ !/.minds/policies/**
277
+
278
+ # Autofix session artifacts
279
+ **/.autofix/
280
+ **/.reviewer/logs/
281
+ **/.reviewer/outputs/
282
+ **/.reviewer/settings.local.json
283
+ **/.reviewer/.stop_hook_consecutive_blocks
284
+ **/.reviewer/conversation-issues.jsonl
285
+ **/.reviews/
286
+
287
+ # Stuck agent tracking (stop hook escape valve)
288
+ **/.claude/blocked_stop_commits
289
+
290
+ # Scheduled tasks lock file
291
+ **/.claude/scheduled_tasks.lock
292
+
293
+ # Demo recordings (asciinema .cast, .txt, and scripts)
294
+ **/.demos/
295
+
296
+ # for git worktrees from other repos
297
+ **/.external_worktrees/
298
+
299
+ # ignore coverage files generated within subprojects
300
+ **/cov.xml
301
+
302
+ # ignore demos
303
+ **/recordings
304
+
305
+ # Generated by the _generate-dockerignore justfile recipe before each
306
+ # test-offload* run. The recipe strips this self-reference so offload's
307
+ # copy_untracked_files ships the generated file into the build context.
308
+ /.dockerignore
309
+
310
+ # Transient offload image-cache marker written by test-offload runs; not
311
+ # checked in (unlike the committed .offload-*-cache-key files which pin CI
312
+ # image versions).
313
+ /.offload-image-cache
314
+
315
+ # Generated by _generate-release-dockerfile from Dockerfile + .release.extras.
316
+ /libs/mngr/imbue/mngr/resources/Dockerfile.release
317
+ **/.reviewer/autofix/issues/
318
+ .reviewer/logs/
319
+
320
+ # Tailwind Play CDN JS for the minds desktop client. Fetched once via
321
+ # `just minds-tailwind` (plain curl, no build) and shipped with the
322
+ # distributable. Not checked in because it's a ~300KB blob we can always
323
+ # re-fetch.
324
+ /apps/minds/imbue/minds/desktop_client/static/tailwind.js
325
+ **/.env.swp
326
+ # Vim swap files for any file (`.foo.swp`, `.foo.swo`, etc.) -- generated
327
+ # while a file is open in vim. Never checked in.
328
+ **/.*.sw[a-p]
329
+
330
+ # `minds env deploy` writes a recover-target file at the monorepo root
331
+ # on failed deploys; `minds env recover` deletes it after rolling back.
332
+ # Never checked in. Per-env naming
333
+ # (``.minds-deploy-recover-target-<env-name>.json``) means each in-flight
334
+ # deploy has its own file, so concurrent deploys against different envs
335
+ # don't trip over each other.
336
+ **/.minds-deploy-*.json
337
+ # Per-env deploy lock files (kernel-level ``flock`` targets). Created
338
+ # by ``hold_deploy_lock`` in ``apps/minds/imbue/minds/envs/recover.py``
339
+ # and held for the entire duration of ``minds env deploy`` /
340
+ # ``minds env recover`` so two concurrent invocations against the same
341
+ # env serialize. The file itself is empty and cheap to leave around.
342
+ **/.minds-deploy-lock-*.lock
343
+ **/.antigravitycli/
344
+
345
+ # Playwright MCP scratch artifacts (mock screenshots, page snapshots)
346
+ **/.playwright-mcp/
@@ -0,0 +1,26 @@
1
+ # Changelog - mngr_usage
2
+
3
+ A concise, human-friendly summary of changes for the `mngr_usage` library. Entries are categorized using the [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) categories: Added, Changed, Deprecated, Removed, Fixed, Security.
4
+
5
+ For the full, unedited changelog entries, see [UNABRIDGED_CHANGELOG.md](UNABRIDGED_CHANGELOG.md).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [v0.1.0] - 2026-06-05
10
+
11
+ ### Added
12
+
13
+ - Added: `mngr usage` per-session cost aggregation with separate `subscription_cost` / `api_cost` aggregates, `--since`, `--detail`, and CEL/format-template surfaces; cost tracking now works for direct `ANTHROPIC_API_KEY` users.
14
+ - Added: New `docs/cron_recipes.md` "cron automation recipes" doc (linked from the README) with worked examples of driving `mngr` from cron using check mode (`mngr usage --format json`) rather than the blocking `mngr usage wait`, plus a shared `spare-capacity.sh` helper (exit 0 when the 5h window still has budget and the week is under pace). Worked examples cover: using up an about-to-expire 5h window via a dedicated agent's full lifecycle, warming a fresh 5h window early so it resets partway through your work, and dispatching a queue of task files capped by a shared `queue=live` label.
15
+ - Added: The usage plugin contributes its cron-recipes documentation as a `mngr help usage_cron_recipes` topic via mngr's new `register_help_topics` hook. The topic body is the plugin's `cron_recipes.md`, shipped inside the wheel via `force-include`, and its `DocFile` carries a GitHub `source_url` so relative links (e.g. `[Waiting on a predicate](../README.md#waiting-on-a-predicate)`) are rewritten to clickable absolute GitHub URLs in an interactive terminal.
16
+
17
+ ### Changed
18
+
19
+ - Changed: Added to the release tooling's publish graph (`scripts/utils.py`); will be offered for first publication to PyPI on the next release. Stale `imbue-mngr==0.2.6` pin in `pyproject.toml` is realigned to the current `0.2.10`. No runtime change.
20
+
21
+ ## [v0.2.8] - 2026-05-13
22
+
23
+ ### Added
24
+
25
+ - Added: New `mngr usage` command reporting Claude Code's rolling 5h / 7d / overage quota usage with `human`/`json`/`jsonl` formats and the standard agent-filter flags.
26
+ - Added: New `mngr usage wait --until <CEL>` command that blocks until a usage snapshot matches a predicate, with exit codes mirroring `mngr wait`.
@@ -0,0 +1,140 @@
1
+ Metadata-Version: 2.4
2
+ Name: imbue-mngr-usage
3
+ Version: 0.1.0
4
+ Summary: mngr usage subcommand: agent-agnostic rolling-window quota / usage CLI (provider plugins implement the data side via the current_usage_snapshot hook)
5
+ Requires-Python: >=3.12
6
+ Requires-Dist: click-option-group>=0.5.6
7
+ Requires-Dist: click>=8.0
8
+ Requires-Dist: imbue-mngr==0.2.11
9
+ Requires-Dist: pydantic>=2.0
10
+ Description-Content-Type: text/markdown
11
+
12
+ # imbue-mngr-usage
13
+
14
+ `mngr usage` -- agent-agnostic CLI for rolling-window usage / quota data.
15
+
16
+ ## What it does
17
+
18
+ Provides a single `mngr usage` command that surfaces rolling-window usage data
19
+ in human / json / jsonl / format-template output. The command itself knows
20
+ nothing about any specific agent type or provider; it walks events files on
21
+ disk and renders whatever it finds.
22
+
23
+ ## Architecture
24
+
25
+ This package contains:
26
+
27
+ - The `mngr usage` CLI command and its rendering helpers.
28
+ - The `UsageSnapshot`, `WindowSnapshot`, and `CostSnapshot` data types.
29
+
30
+ Discovery is by path convention. The CLI walks
31
+ `<host_dir>/agents/*/events/<source>/usage/events.jsonl` (the same shape
32
+ `mngr transcript` uses for `events/<source>/common_transcript/...`), scans
33
+ every event line, and aggregates per `<source>`: rate-limit windows
34
+ collapse freshest-wins (the account-level counter), while cost is grouped
35
+ per `session_id` and filtered to a recency window (`--since`, default 24h).
36
+ The `<source>` segment is free-form -- whatever the writer plugin chose.
37
+
38
+ When multiple writers contribute, each renders as its own `[source]` section in
39
+ human output and as an entry in the JSON `sources` array.
40
+
41
+ ## Output formats
42
+
43
+ - `mngr usage` (human summary: per-mode cost line(s) + window lines)
44
+ - `mngr usage --detail` (human + per-session breakdown lines)
45
+ - `mngr usage --format json` (summary JSON: per-mode aggregates `subscription_cost` and `api_cost`, `session_count` plus per-mode counts, windows)
46
+ - `mngr usage --format json --detail` (JSON with `sessions[]` per source; each session carries a `cost_mode` tag)
47
+ - `mngr usage --format jsonl`
48
+ - `mngr usage --format '5h:{five_hour.used_percentage}%/{seven_day.used_percentage}%'`
49
+
50
+ The `--detail` flag is independent of `--verbose` (which controls log
51
+ level). It toggles only the per-session breakdown surfaces.
52
+
53
+ ## Waiting on a predicate
54
+
55
+ `mngr usage wait --until <CEL>` blocks until at least one source's CEL
56
+ context satisfies every `--until` expression, then exits 0. Composable with
57
+ shell:
58
+
59
+ ```
60
+ mngr usage wait --until 'five_hour.elapsed_percentage > 75 && five_hour.used_percentage < 50' \
61
+ && mngr message my-agent "ok, kick off the next batch"
62
+ ```
63
+
64
+ The CEL context per source mirrors one entry of `mngr usage --format json`'s
65
+ `sources` array. Each window exposes the writer-emitted fields
66
+ (`used_percentage`, `resets_at`, `window_seconds`, `label`, ...) plus the
67
+ reader-derived `seconds_until_reset`, `elapsed_seconds`, and
68
+ `elapsed_percentage` (the last two require `window_seconds` from the
69
+ writer; absent on variable-duration windows like Claude's overage).
70
+ Source-level fields are exposed too. Cost is split by auth mode and
71
+ **never lumped**: `subscription_cost.*` aggregates sessions whose Claude
72
+ Code process was on a Claude.ai Pro/Max subscription (cost is **imputed**
73
+ by Claude Code -- the user actually pays a flat subscription), and
74
+ `api_cost.*` aggregates sessions whose process was on a direct
75
+ `ANTHROPIC_API_KEY` (cost is **real** billable spend). Pick the mode you
76
+ care about, e.g. `api_cost.total_cost_usd > 20.0` means "I've spent more
77
+ than $20 of real API money across recent sessions". `session_count` is
78
+ the total across both modes; `subscription_session_count` and
79
+ `api_session_count` break that down per mode. `since_seconds` and
80
+ `sessions[]` (every session in the window, newest-first, each carrying
81
+ a `cost_mode` of `"SUBSCRIPTION"` or `"API_KEY"`) are available as well.
82
+ Use `--since` to tighten or widen the aggregation window from its
83
+ default (24h). For a per-session predicate, index `sessions[]` directly
84
+ (e.g. `sessions[0].cost.total_cost_usd > 5` for the most recent
85
+ session's own contribution).
86
+
87
+ Exit codes mirror `mngr wait`: 0 matched, 1 error, 2 timeout. Default poll
88
+ interval is 30s; use `--interval` for tighter cadence. To restrict matching
89
+ to a specific writer, use the top-level `source` field in CEL (e.g.
90
+ `--until 'source == "claude" && five_hour.used_percentage < 50'`).
91
+
92
+ ## Polling from cron (check mode)
93
+
94
+ For recurring automation, let `cron` own the cadence: poll the plain
95
+ `mngr usage --format json` snapshot on a schedule and branch in the shell. See
96
+ [cron automation recipes](docs/cron_recipes.md) for worked examples: using up an
97
+ about-to-expire 5h window (with a weekly pace check), warming a fresh window the
98
+ moment the last one elapses, and working through a queue of task files a couple
99
+ at a time.
100
+
101
+ ## Implementing a writer plugin
102
+
103
+ A writer plugin is responsible for producing `cost_snapshot` events at the
104
+ conventional path. The minimal contract is just the JSONL line shape:
105
+
106
+ ```jsonl
107
+ {"source":"<your-source>/usage","type":"cost_snapshot","event_id":"evt-<hex>","timestamp":"<ISO 8601>","session_id":"<uuid>","cost":{"total_cost_usd":<float>},"rate_limits":{"<window-key>":{"used_percentage":<float>,"resets_at":<unix-ts>}}}
108
+ ```
109
+
110
+ Every event must carry a `session_id` (non-empty string) plus at least one
111
+ of `rate_limits` or `cost`. Events missing `session_id` are dropped with a
112
+ WARNING log naming the source and event_id -- so if you're writing a usage
113
+ plugin and don't see your data in `mngr usage`, check the log first. Any
114
+ plausible statusline source has a session identifier handy, so this is
115
+ rarely a constraint; if a future tool doesn't, synthesize a stable
116
+ per-session id on the writer side. The `type` field is informational --
117
+ the reader doesn't validate it -- but `"cost_snapshot"` is the current
118
+ convention.
119
+
120
+ Append one line per refresh to:
121
+
122
+ ```
123
+ <agent_state_dir>/events/<your-source>/usage/events.jsonl
124
+ ```
125
+
126
+ `mngr usage` will pick it up automatically -- no plugin registration with this
127
+ package required.
128
+
129
+ The writer chooses both the window keys and (optionally) per-window
130
+ `label`s. Keys are used by format templates (`{<key>.used_percentage}`) and
131
+ should be identifier-safe if you want format-template support; the per-window
132
+ `label` controls human display (e.g. `"5h"` vs the literal key `"five_hour"`).
133
+ Render order is the writer's insertion order in the JSONL.
134
+
135
+ Writers may also include `"window_seconds": <int>` per window to declare a
136
+ fixed window duration. When present, `mngr usage` derives `elapsed_seconds`
137
+ and `elapsed_percentage`, which `mngr usage wait` CEL predicates can use to
138
+ express "75% of the window has elapsed" without callers hardcoding window
139
+ durations. Omit `window_seconds` for windows without a fixed length (e.g.
140
+ Claude's overage indicator).
@@ -0,0 +1,129 @@
1
+ # imbue-mngr-usage
2
+
3
+ `mngr usage` -- agent-agnostic CLI for rolling-window usage / quota data.
4
+
5
+ ## What it does
6
+
7
+ Provides a single `mngr usage` command that surfaces rolling-window usage data
8
+ in human / json / jsonl / format-template output. The command itself knows
9
+ nothing about any specific agent type or provider; it walks events files on
10
+ disk and renders whatever it finds.
11
+
12
+ ## Architecture
13
+
14
+ This package contains:
15
+
16
+ - The `mngr usage` CLI command and its rendering helpers.
17
+ - The `UsageSnapshot`, `WindowSnapshot`, and `CostSnapshot` data types.
18
+
19
+ Discovery is by path convention. The CLI walks
20
+ `<host_dir>/agents/*/events/<source>/usage/events.jsonl` (the same shape
21
+ `mngr transcript` uses for `events/<source>/common_transcript/...`), scans
22
+ every event line, and aggregates per `<source>`: rate-limit windows
23
+ collapse freshest-wins (the account-level counter), while cost is grouped
24
+ per `session_id` and filtered to a recency window (`--since`, default 24h).
25
+ The `<source>` segment is free-form -- whatever the writer plugin chose.
26
+
27
+ When multiple writers contribute, each renders as its own `[source]` section in
28
+ human output and as an entry in the JSON `sources` array.
29
+
30
+ ## Output formats
31
+
32
+ - `mngr usage` (human summary: per-mode cost line(s) + window lines)
33
+ - `mngr usage --detail` (human + per-session breakdown lines)
34
+ - `mngr usage --format json` (summary JSON: per-mode aggregates `subscription_cost` and `api_cost`, `session_count` plus per-mode counts, windows)
35
+ - `mngr usage --format json --detail` (JSON with `sessions[]` per source; each session carries a `cost_mode` tag)
36
+ - `mngr usage --format jsonl`
37
+ - `mngr usage --format '5h:{five_hour.used_percentage}%/{seven_day.used_percentage}%'`
38
+
39
+ The `--detail` flag is independent of `--verbose` (which controls log
40
+ level). It toggles only the per-session breakdown surfaces.
41
+
42
+ ## Waiting on a predicate
43
+
44
+ `mngr usage wait --until <CEL>` blocks until at least one source's CEL
45
+ context satisfies every `--until` expression, then exits 0. Composable with
46
+ shell:
47
+
48
+ ```
49
+ mngr usage wait --until 'five_hour.elapsed_percentage > 75 && five_hour.used_percentage < 50' \
50
+ && mngr message my-agent "ok, kick off the next batch"
51
+ ```
52
+
53
+ The CEL context per source mirrors one entry of `mngr usage --format json`'s
54
+ `sources` array. Each window exposes the writer-emitted fields
55
+ (`used_percentage`, `resets_at`, `window_seconds`, `label`, ...) plus the
56
+ reader-derived `seconds_until_reset`, `elapsed_seconds`, and
57
+ `elapsed_percentage` (the last two require `window_seconds` from the
58
+ writer; absent on variable-duration windows like Claude's overage).
59
+ Source-level fields are exposed too. Cost is split by auth mode and
60
+ **never lumped**: `subscription_cost.*` aggregates sessions whose Claude
61
+ Code process was on a Claude.ai Pro/Max subscription (cost is **imputed**
62
+ by Claude Code -- the user actually pays a flat subscription), and
63
+ `api_cost.*` aggregates sessions whose process was on a direct
64
+ `ANTHROPIC_API_KEY` (cost is **real** billable spend). Pick the mode you
65
+ care about, e.g. `api_cost.total_cost_usd > 20.0` means "I've spent more
66
+ than $20 of real API money across recent sessions". `session_count` is
67
+ the total across both modes; `subscription_session_count` and
68
+ `api_session_count` break that down per mode. `since_seconds` and
69
+ `sessions[]` (every session in the window, newest-first, each carrying
70
+ a `cost_mode` of `"SUBSCRIPTION"` or `"API_KEY"`) are available as well.
71
+ Use `--since` to tighten or widen the aggregation window from its
72
+ default (24h). For a per-session predicate, index `sessions[]` directly
73
+ (e.g. `sessions[0].cost.total_cost_usd > 5` for the most recent
74
+ session's own contribution).
75
+
76
+ Exit codes mirror `mngr wait`: 0 matched, 1 error, 2 timeout. Default poll
77
+ interval is 30s; use `--interval` for tighter cadence. To restrict matching
78
+ to a specific writer, use the top-level `source` field in CEL (e.g.
79
+ `--until 'source == "claude" && five_hour.used_percentage < 50'`).
80
+
81
+ ## Polling from cron (check mode)
82
+
83
+ For recurring automation, let `cron` own the cadence: poll the plain
84
+ `mngr usage --format json` snapshot on a schedule and branch in the shell. See
85
+ [cron automation recipes](docs/cron_recipes.md) for worked examples: using up an
86
+ about-to-expire 5h window (with a weekly pace check), warming a fresh window the
87
+ moment the last one elapses, and working through a queue of task files a couple
88
+ at a time.
89
+
90
+ ## Implementing a writer plugin
91
+
92
+ A writer plugin is responsible for producing `cost_snapshot` events at the
93
+ conventional path. The minimal contract is just the JSONL line shape:
94
+
95
+ ```jsonl
96
+ {"source":"<your-source>/usage","type":"cost_snapshot","event_id":"evt-<hex>","timestamp":"<ISO 8601>","session_id":"<uuid>","cost":{"total_cost_usd":<float>},"rate_limits":{"<window-key>":{"used_percentage":<float>,"resets_at":<unix-ts>}}}
97
+ ```
98
+
99
+ Every event must carry a `session_id` (non-empty string) plus at least one
100
+ of `rate_limits` or `cost`. Events missing `session_id` are dropped with a
101
+ WARNING log naming the source and event_id -- so if you're writing a usage
102
+ plugin and don't see your data in `mngr usage`, check the log first. Any
103
+ plausible statusline source has a session identifier handy, so this is
104
+ rarely a constraint; if a future tool doesn't, synthesize a stable
105
+ per-session id on the writer side. The `type` field is informational --
106
+ the reader doesn't validate it -- but `"cost_snapshot"` is the current
107
+ convention.
108
+
109
+ Append one line per refresh to:
110
+
111
+ ```
112
+ <agent_state_dir>/events/<your-source>/usage/events.jsonl
113
+ ```
114
+
115
+ `mngr usage` will pick it up automatically -- no plugin registration with this
116
+ package required.
117
+
118
+ The writer chooses both the window keys and (optionally) per-window
119
+ `label`s. Keys are used by format templates (`{<key>.used_percentage}`) and
120
+ should be identifier-safe if you want format-template support; the per-window
121
+ `label` controls human display (e.g. `"5h"` vs the literal key `"five_hour"`).
122
+ Render order is the writer's insertion order in the JSONL.
123
+
124
+ Writers may also include `"window_seconds": <int>` per window to declare a
125
+ fixed window duration. When present, `mngr usage` derives `elapsed_seconds`
126
+ and `elapsed_percentage`, which `mngr usage wait` CEL predicates can use to
127
+ express "75% of the window has elapsed" without callers hardcoding window
128
+ durations. Omit `window_seconds` for windows without a fixed length (e.g.
129
+ Claude's overage indicator).