imbue-mngr-codex-usage 0.1.1__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,364 @@
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 (at any depth, incl. the
214
+ # root-level `dev` project's `dev/_tasks/`, which `*/*/_tasks/` missed)
215
+ **/_tasks/
216
+ **/.sesskey
217
+
218
+ # Node.js
219
+ **/node_modules/
220
+
221
+ # Frontend build artifacts
222
+ **/frontend-dist/
223
+
224
+ # Claude Code local settings (session-specific permissions)
225
+ **/.claude/settings.local.json
226
+
227
+ # Local Claude settings backup files
228
+ **/.claude/*.bak
229
+
230
+ # Claude worktress
231
+ **/.claude/worktrees
232
+
233
+ # Agent definitions deployed by mngr_subagent_proxy at session start
234
+ **/.claude/agents/
235
+
236
+ # Local mngr settings (not committed)
237
+ **/.mngr/settings.local.toml
238
+
239
+ # Local working / scratch markdown (not committed)
240
+ **/*.local.md
241
+
242
+ # Local working / scratch shell scripts (not committed)
243
+ # (covers user notification scripts like scripts/notify_user.local.sh, too)
244
+ **/*.local.sh
245
+
246
+ # Local-only throwaway scripts (e.g. one-off test harnesses).
247
+ **/scripts/*.local.sh
248
+
249
+ # Local working / scratch Python + text (not committed; e.g. one-off validation
250
+ # harnesses or probe scripts). Mirrors the *.local.md / *.local.sh convention.
251
+ **/*.local.py
252
+ **/*.local.txt
253
+
254
+ # Test output files (slow tests, coverage summaries)
255
+ **/.test_output/
256
+
257
+ # TMR output directories (reports + test outputs)
258
+ **/tmr_*/
259
+ **/tmr-report/
260
+
261
+ # Active session marker file (used to detect interrupted sessions)
262
+ **/.claude/active
263
+
264
+ # we stick the image build artifacts here
265
+ **/.mngr/dev/build/
266
+ **/.mngr/dev/secrets/
267
+
268
+ # Offload caches and local files
269
+ **/.offload/**
270
+ **/test-results
271
+
272
+ # Minds deploy-time build artifacts.
273
+ # Ignore contents so we can un-ignore the committed `template/` subtree: git
274
+ # won't descend into a fully-ignored directory, so negations must live below
275
+ # the contents pattern rather than below the directory pattern.
276
+ **/.minds/*
277
+ # The template directory is committed: it defines the expected keys for each
278
+ # <service>.sh file, with empty values. `scripts/push_modal_secrets.py` uses
279
+ # it to check that per-env .sh files declare every template key.
280
+ !/.minds/template/
281
+ !/.minds/template/**
282
+ # Vault ACL policies for the minds tiers. The HCL is non-sensitive and is
283
+ # the source of truth for the policies pushed to Vault via
284
+ # `vault policy write <name> .minds/policies/<name>.hcl`.
285
+ !/.minds/policies/
286
+ !/.minds/policies/**
287
+
288
+ # Autofix session artifacts
289
+ **/.autofix/
290
+ **/.reviewer/logs/
291
+ **/.reviewer/outputs/
292
+ **/.reviewer/settings.local.json
293
+ **/.reviewer/.stop_hook_consecutive_blocks
294
+ **/.reviewer/conversation-issues.jsonl
295
+ **/.reviews/
296
+
297
+ # Stuck agent tracking (stop hook escape valve)
298
+ **/.claude/blocked_stop_commits
299
+
300
+ # Scheduled tasks lock file
301
+ **/.claude/scheduled_tasks.lock
302
+
303
+ # Demo recordings (asciinema .cast, .txt, and scripts)
304
+ **/.demos/
305
+
306
+ # for git worktrees from other repos
307
+ **/.external_worktrees/
308
+
309
+ # ignore coverage files generated within subprojects
310
+ **/cov.xml
311
+
312
+ # ignore demos
313
+ **/recordings
314
+ apps/minds_workspace_server/package-lock.json
315
+ **/*.local.md
316
+
317
+ # Generated by the _generate-dockerignore justfile recipe before each
318
+ # test-offload* run. The recipe strips this self-reference so offload's
319
+ # copy_untracked_files ships the generated file into the build context.
320
+ /.dockerignore
321
+
322
+ # Transient offload image-cache marker written by test-offload runs; not
323
+ # checked in (unlike the committed .offload-*-cache-key files which pin CI
324
+ # image versions).
325
+ /.offload-image-cache
326
+
327
+ # Generated by _generate-release-dockerfile from Dockerfile + .release.extras.
328
+ /libs/mngr/imbue/mngr/resources/Dockerfile.release
329
+ **/.reviewer/autofix/issues/
330
+ .reviewer/logs/
331
+
332
+ # Tailwind Play CDN JS for the minds desktop client. Fetched once via
333
+ # `just minds-tailwind` (plain curl, no build) and shipped with the
334
+ # distributable. Not checked in because it's a ~300KB blob we can always
335
+ # re-fetch.
336
+ /apps/minds/imbue/minds/desktop_client/static/tailwind.js
337
+
338
+ # macOS Finder metadata
339
+ **/.DS_Store
340
+ **/.env.swp
341
+ # Vim swap files for any file (`.foo.swp`, `.foo.swo`, etc.) -- generated
342
+ # while a file is open in vim. Never checked in.
343
+ **/.*.sw[a-p]
344
+
345
+ # `minds env deploy` writes a recover-target file at the monorepo root
346
+ # on failed deploys; `minds env recover` deletes it after rolling back.
347
+ # Never checked in. Per-env naming
348
+ # (``.minds-deploy-recover-target-<env-name>.json``) means each in-flight
349
+ # deploy has its own file, so concurrent deploys against different envs
350
+ # don't trip over each other.
351
+ **/.minds-deploy-*.json
352
+ # Per-env deploy lock files (kernel-level ``flock`` targets). Created
353
+ # by ``hold_deploy_lock`` in ``apps/minds/imbue/minds/envs/recover.py``
354
+ # and held for the entire duration of ``minds env deploy`` /
355
+ # ``minds env recover`` so two concurrent invocations against the same
356
+ # env serialize. The file itself is empty and cheap to leave around.
357
+ **/.minds-deploy-lock-*.lock
358
+ **/.antigravitycli/
359
+
360
+ # Playwright MCP scratch artifacts (mock screenshots, page snapshots)
361
+ **/.playwright-mcp/
362
+
363
+ # tmr (task-runner) orchestration scratch report
364
+ **/tmr-report/
@@ -0,0 +1,13 @@
1
+ # Changelog - mngr_codex_usage
2
+
3
+ A concise, human-friendly summary of changes for the `mngr_codex_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.1] - 2026-06-16
10
+
11
+ ### Added
12
+
13
+ - Added: New package `imbue-mngr-codex-usage` providing cost/usage tracking for Codex agents in `mngr usage`. Codex reports cumulative token usage (no dollar cost), so cost is estimated from the pricing table and aggregated session-cumulatively. The writer (`codex_usage.sh`) reads codex's rollout stream and emits one `cost_snapshot` per `token_count` item; `mngr_codex`'s background-tasks supervisor launches it when present. Codex's 5h/7d rate-limit windows (subscription mode) are mapped so Codex subscription agents get Claude-style windows. The writer tracks a byte-offset cursor (persisted under `plugin/codex/.usage_cursor`) so each poll processes only the new tail of the rollout.
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.4
2
+ Name: imbue-mngr-codex-usage
3
+ Version: 0.1.1
4
+ Summary: Codex usage data provider for `mngr usage` (rollout token_count -> cost_snapshot writer + reader)
5
+ Requires-Python: >=3.12
6
+ Requires-Dist: imbue-mngr-codex==0.1.3
7
+ Requires-Dist: imbue-mngr-usage==0.1.5
8
+ Requires-Dist: imbue-mngr==0.2.16
9
+ Description-Content-Type: text/markdown
10
+
11
+ # imbue-mngr-codex-usage
12
+
13
+ Codex data provider for `mngr usage`. Codex exposes no statusline or in-process
14
+ plugin, but mngr already tails its rollout JSONL into a raw transcript via
15
+ mngr_codex's background-tasks supervisor. This package adds:
16
+
17
+ - **The writer** (`codex_usage.sh`): installed into the agent's `commands/` dir,
18
+ where mngr_codex's `codex_background_tasks.sh` launches it **iff present**. It
19
+ reads the raw rollout stream and emits one `cost_snapshot` per `token_count`
20
+ item to `events/codex/usage/events.jsonl`.
21
+ - **The reader**: an `aggregate_usage_source` hookimpl claiming the `codex`
22
+ source, aggregated **session-cumulatively** (each `token_count` carries the
23
+ session's cumulative total, so the freshest per session wins).
24
+
25
+ ## What gets captured
26
+
27
+ Codex reports cumulative token usage (`info.total_token_usage`) but **no dollar
28
+ cost**, so cost is left null and the reader **estimates** it from tokens via the
29
+ pricing table (provenance `ESTIMATED`). `input_tokens` includes cached, so the
30
+ writer emits `input = input_tokens - cached_input_tokens` and
31
+ `cache_read = cached_input_tokens` (OpenAI has no cache-write surcharge). The
32
+ model is `openai/<model>` from the rollout's `turn_context`.
33
+
34
+ In ChatGPT-plan (subscription) mode, `token_count` also carries 5h/7d rate-limit
35
+ windows, which the writer maps onto the window schema -- so Codex subscription
36
+ agents get Claude-style windows, and the session is classified `SUBSCRIPTION`
37
+ (imputed). Without rate limits it's `API_KEY` (real spend).
@@ -0,0 +1,27 @@
1
+ # imbue-mngr-codex-usage
2
+
3
+ Codex data provider for `mngr usage`. Codex exposes no statusline or in-process
4
+ plugin, but mngr already tails its rollout JSONL into a raw transcript via
5
+ mngr_codex's background-tasks supervisor. This package adds:
6
+
7
+ - **The writer** (`codex_usage.sh`): installed into the agent's `commands/` dir,
8
+ where mngr_codex's `codex_background_tasks.sh` launches it **iff present**. It
9
+ reads the raw rollout stream and emits one `cost_snapshot` per `token_count`
10
+ item to `events/codex/usage/events.jsonl`.
11
+ - **The reader**: an `aggregate_usage_source` hookimpl claiming the `codex`
12
+ source, aggregated **session-cumulatively** (each `token_count` carries the
13
+ session's cumulative total, so the freshest per session wins).
14
+
15
+ ## What gets captured
16
+
17
+ Codex reports cumulative token usage (`info.total_token_usage`) but **no dollar
18
+ cost**, so cost is left null and the reader **estimates** it from tokens via the
19
+ pricing table (provenance `ESTIMATED`). `input_tokens` includes cached, so the
20
+ writer emits `input = input_tokens - cached_input_tokens` and
21
+ `cache_read = cached_input_tokens` (OpenAI has no cache-write surcharge). The
22
+ model is `openai/<model>` from the rollout's `turn_context`.
23
+
24
+ In ChatGPT-plan (subscription) mode, `token_count` also carries 5h/7d rate-limit
25
+ windows, which the writer maps onto the window schema -- so Codex subscription
26
+ agents get Claude-style windows, and the session is classified `SUBSCRIPTION`
27
+ (imputed). Without rate limits it's `API_KEY` (real spend).
@@ -0,0 +1,13 @@
1
+ # Unabridged Changelog - mngr_codex_usage
2
+
3
+ Full, unedited changelog entries consolidated nightly from individual files in `libs/mngr_codex_usage/changelog/`.
4
+
5
+ For a concise summary, see [CHANGELOG.md](CHANGELOG.md).
6
+
7
+ ## 2026-06-16
8
+
9
+ New package `imbue-mngr-codex-usage`: cost/usage tracking for Codex agents in `mngr usage`. Codex reports cumulative token usage (no dollar cost), so cost is estimated from the pricing table and aggregated session-cumulatively. The writer (`codex_usage.sh`) reads codex's rollout stream and emits one `cost_snapshot` per `token_count` item; mngr_codex's background-tasks supervisor launches it when present. It also maps codex's 5h/7d rate-limit windows (subscription mode), so Codex subscription agents get Claude-style windows.
10
+
11
+ The writer tracks a byte-offset cursor (persisted under `plugin/codex/.usage_cursor`) so each poll processes only the new tail of the rollout (O(new bytes) per tick), important for long Codex sessions. Re-emission after a crash is harmless because the session-cumulative reader keeps the freshest reading per session.
12
+
13
+ The writer delegates event emission to a standalone `codex_usage_emit.py` (installed alongside `codex_usage.sh` and invoked by it), keeping that logic type-checked, linted, and unit-tested directly. Malformed rollout lines and unreadable cursor state are logged at warning level.
File without changes
@@ -0,0 +1,7 @@
1
+ """Project-level conftest for mngr_codex_usage."""
2
+
3
+ from imbue.imbue_common.conftest_hooks import register_conftest_hooks
4
+ from imbue.mngr.utils.logging import suppress_warnings
5
+
6
+ suppress_warnings()
7
+ register_conftest_hooks(globals())
@@ -0,0 +1,114 @@
1
+ """Unit tests for the Codex ``aggregate_usage_source`` reader hookimpl."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ import pytest
8
+
9
+ from imbue.mngr_codex_usage.plugin import aggregate_usage_source
10
+ from imbue.mngr_usage.api import parse_usage_events
11
+ from imbue.mngr_usage.data_types import CostMode
12
+ from imbue.mngr_usage.data_types import CostProvenance
13
+
14
+ _NOW = 2_800_000_000
15
+ _SINCE = 10**12
16
+
17
+
18
+ def _codex_event(
19
+ session_id: str, event_id: str, second: int, tokens: dict[str, Any], rate_limits: dict[str, Any] | None = None
20
+ ) -> dict[str, Any]:
21
+ event: dict[str, Any] = {
22
+ "timestamp": f"2056-01-01T00:00:{second:02d}.000000000Z",
23
+ "session_id": session_id,
24
+ "event_id": event_id,
25
+ "cost": None,
26
+ "tokens": tokens,
27
+ "model": "openai/gpt-5.2-codex",
28
+ "cost_mode": "SUBSCRIPTION" if rate_limits is not None else "API_KEY",
29
+ }
30
+ if rate_limits is not None:
31
+ event["rate_limits"] = rate_limits
32
+ return event
33
+
34
+
35
+ def test_codex_hookimpl_takes_freshest_cumulative_and_estimates_cost() -> None:
36
+ # token_count is cumulative; the freshest reading is the session total, and
37
+ # cost is estimated from tokens (Codex reports no dollar cost).
38
+ snapshot = aggregate_usage_source(
39
+ source_name="codex",
40
+ agents_events={
41
+ "agent-1": parse_usage_events(
42
+ [
43
+ _codex_event(
44
+ "s1", "line-1-usage", 1, {"input": 100, "output": 0, "cache_read": 0, "cache_creation": None}
45
+ ),
46
+ _codex_event(
47
+ "s1",
48
+ "line-2-usage",
49
+ 2,
50
+ {"input": 1_000_000, "output": 0, "cache_read": 0, "cache_creation": None},
51
+ ),
52
+ ],
53
+ "codex",
54
+ )
55
+ },
56
+ since_seconds=_SINCE,
57
+ now=_NOW,
58
+ )
59
+ assert snapshot is not None
60
+ record = snapshot.sessions[0]
61
+ # Freshest = 1,000,000 input * gpt-5.2-codex input price 1.75e-6 = 1.75.
62
+ assert record.cost.total_cost_usd == pytest.approx(1.75)
63
+ assert record.cost_provenance == CostProvenance.ESTIMATED
64
+ assert record.cost_mode == CostMode.API_KEY
65
+
66
+
67
+ def test_codex_hookimpl_surfaces_windows_and_subscription_mode() -> None:
68
+ snapshot = aggregate_usage_source(
69
+ source_name="codex",
70
+ agents_events={
71
+ "agent-1": parse_usage_events(
72
+ [
73
+ _codex_event(
74
+ "s1",
75
+ "line-1-usage",
76
+ 1,
77
+ {"input": 100, "output": 50, "cache_read": 200, "cache_creation": None},
78
+ rate_limits={
79
+ "five_hour": {
80
+ "used_percentage": 23.0,
81
+ "resets_at": 9_999_999_999,
82
+ "window_seconds": 18000,
83
+ },
84
+ "seven_day": {
85
+ "used_percentage": 27.0,
86
+ "resets_at": 9_999_999_999,
87
+ "window_seconds": 604800,
88
+ },
89
+ },
90
+ )
91
+ ],
92
+ "codex",
93
+ )
94
+ },
95
+ since_seconds=_SINCE,
96
+ now=_NOW,
97
+ )
98
+ assert snapshot is not None
99
+ assert snapshot.sessions[0].cost_mode == CostMode.SUBSCRIPTION
100
+ assert "five_hour" in snapshot.windows and "seven_day" in snapshot.windows
101
+
102
+
103
+ def test_codex_hookimpl_declines_non_codex_sources() -> None:
104
+ snapshot = aggregate_usage_source(
105
+ source_name="opencode",
106
+ agents_events={
107
+ "agent-1": parse_usage_events(
108
+ [_codex_event("s1", "line-1-usage", 1, {"input": 1, "output": 1})], "opencode"
109
+ )
110
+ },
111
+ since_seconds=_SINCE,
112
+ now=_NOW,
113
+ )
114
+ assert snapshot is None
@@ -0,0 +1,3 @@
1
+ from imbue.mngr.utils.plugin_testing import register_plugin_test_fixtures
2
+
3
+ register_plugin_test_fixtures(globals())
@@ -0,0 +1,37 @@
1
+ """Guard the writer-script <-> reader and supervisor seams.
2
+
3
+ The Codex usage writer (`codex_usage.sh`, this package) emits under a source the
4
+ reader here claims, and mngr_codex's background-tasks supervisor launches it by
5
+ filename. Both agreements are hand-synced string literals (across the shell <->
6
+ Python boundary, and across packages), so a rename silently disables usage
7
+ tracking. Pin both invariants.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import importlib.resources
13
+
14
+ from imbue.mngr_codex.codex_config import BACKGROUND_TASKS_SCRIPT_NAME
15
+ from imbue.mngr_codex.plugin import _load_codex_resource_script
16
+ from imbue.mngr_codex_usage import resources as _resources
17
+ from imbue.mngr_codex_usage.plugin import _CODEX_USAGE_SOURCE_NAME
18
+ from imbue.mngr_codex_usage.plugin import _USAGE_EMIT_SCRIPT
19
+ from imbue.mngr_codex_usage.plugin import _USAGE_WRITER_SCRIPT
20
+
21
+
22
+ def test_writer_script_emits_the_source_the_reader_claims() -> None:
23
+ # The emitted source literal lives in the python emitter the writer invokes.
24
+ emit_py = importlib.resources.files(_resources).joinpath(_USAGE_EMIT_SCRIPT).read_text()
25
+ assert f'"{_CODEX_USAGE_SOURCE_NAME}/usage"' in emit_py
26
+
27
+
28
+ def test_writer_script_invokes_the_emitter_it_ships_with() -> None:
29
+ writer_sh = importlib.resources.files(_resources).joinpath(_USAGE_WRITER_SCRIPT).read_text()
30
+ assert _USAGE_EMIT_SCRIPT in writer_sh
31
+
32
+
33
+ def test_supervisor_launches_the_writer_by_the_name_this_package_installs() -> None:
34
+ supervisor_sh = _load_codex_resource_script(BACKGROUND_TASKS_SCRIPT_NAME)
35
+ # mngr_codex's supervisor must reference the exact script filename this
36
+ # package installs into commands/, or the writer never launches.
37
+ assert _USAGE_WRITER_SCRIPT in supervisor_sh