cc-session-tools 3.10.2__tar.gz → 3.10.3__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.
- {cc_session_tools-3.10.2/src/cc_session_tools.egg-info → cc_session_tools-3.10.3}/PKG-INFO +1 -1
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/pyproject.toml +1 -1
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/pm-pdata-do-init/SKILL.md +3 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3/src/cc_session_tools.egg-info}/PKG-INFO +1 -1
- cc_session_tools-3.10.3/src/claude_code_usage/data/pricing.json +181 -0
- cc_session_tools-3.10.3/src/claude_code_usage/pricing.py +134 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/hooks/context_window_warning.py +21 -15
- cc_session_tools-3.10.3/src/hooks/model_info.py +78 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_context_window_warning.py +63 -16
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_model_info.py +17 -15
- cc_session_tools-3.10.3/tests/test_pricing.py +189 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_query.py +4 -4
- cc_session_tools-3.10.2/src/claude_code_usage/data/pricing.json +0 -102
- cc_session_tools-3.10.2/src/claude_code_usage/pricing.py +0 -97
- cc_session_tools-3.10.2/src/hooks/model_info.py +0 -79
- cc_session_tools-3.10.2/tests/test_pricing.py +0 -70
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/LICENSE +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/README.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/setup.cfg +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/__init__.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/cli/__init__.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/cli/ccd.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/cli/ccmsg.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/cli/ccr.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/cli/ccs.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/cli/ccsched.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/cli/ccst.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/cli/migrate_ccmsg.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/cli/migrate_ccsched.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/cli/migrate_sessions_db.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/cli/migrate_telemetry.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/config/hooks-bundle.json +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/config/session-tag-hook.json +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/hooks_install.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/__init__.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/bun_tools.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/claude_flags.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/claude_md_install.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/context_overrides.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/db.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/debug.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/doctor.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/doctor_mutes.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/hook_registry.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/install_sync.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/levenshtein.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/machine_identity.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/messaging/__init__.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/messaging/addressing.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/messaging/cursor.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/messaging/lock.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/messaging/message.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/messaging/move_safety.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/messaging/repository.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/messaging/retention.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/messaging/service.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/messaging/store.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/occupancy.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/paths.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/__init__.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/backup.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/classify.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/cutover.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/dump.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/formatting.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/importers.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/init_paths.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/init_service.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/manifest.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/naming.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/readiness.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/rehydrate.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/rename_group.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/reorganize.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/repository.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/resolve.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/service.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/session_output.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/store.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/sync_check.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/sync_lock.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/sync_notify.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/vector_clock.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/vector_clock_store.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/verify.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/pdata/write_log.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/picker.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/proc_lock.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/prompts.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/roots.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/rules.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/scheduler/__init__.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/scheduler/bundled_jobs.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/scheduler/cadence.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/scheduler/cursor.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/scheduler/digest.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/scheduler/due.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/scheduler/duration.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/scheduler/jobspec.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/scheduler/ledger.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/scheduler/lock.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/scheduler/notify.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/scheduler/reconcile.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/scheduler/registry.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/scheduler/runner.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/scheduler/state.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/scheduler/store.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/scheduler/surface.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/scheduler/throttle.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/scheduler/worker.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/session_gc.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/sessions.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/sessions_db.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/sessions_repair.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/shell_install.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/tasklist.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/telemetry_store.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/lib/text_wrap.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/analyse-cc-usage/SKILL.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/clean-hook-sessions/SKILL.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/clean-hook-sessions/scripts/clean-hook-sessions.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/compact-smart/SKILL.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/context-override/SKILL.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/delete-sessions/SKILL.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/delete-sessions/scripts/delete_sessions.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/do-executor-critic-assessor-loop/SKILL.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/extract-pptx/SKILL.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/extract-pptx/scripts/extract_pptx.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/extract-to-text/SKILL.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/extract-to-text/scripts/extract_to_text.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/find-claude-code-session/SKILL.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/generate-8digit-code/SKILL.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/generate-8digit-code/scripts/generate_8digit_code.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/list-empty-sessions/SKILL.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/list-empty-sessions/scripts/list_empty_sessions.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/manage-recurring-cc-jobs-using-ccsched/SKILL.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/move-session/SKILL.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/move-session/hooks/sessionstart-pending-rename.sh +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/move-session/scripts/cc_session_rules.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/move-session/scripts/move_session.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/pm-pdata-design-schema/SKILL.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/pm-pdata-do-audit-and-prepare-to-migrate/SKILL.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/pm-pdata-do-manual-migration/SKILL.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/pm-pdata-do-update-consuming-skills/SKILL.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/pm-pdata-do-update-project-docs/SKILL.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/pm-pdata-resolve-conflicts/SKILL.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/pm-project-layout-reference/SKILL.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/pm-update-central-files/SKILL.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/reduce-persistent-context/CALIBRATION.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/reduce-persistent-context/SKILL.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/reduce-persistent-context/scripts/analyze_context.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/reduce-persistent-context/scripts/duplicates.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/reduce-persistent-context/scripts/measure.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/reduce-persistent-context/scripts/report.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/reduce-persistent-context/scripts/tokens.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/reduce-persistent-context/scripts/usage.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/select-agent-model/SKILL.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/send-session-message/SKILL.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/update-command-cache/SKILL.md +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools/skills/update-command-cache/scripts/update_command_cache.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools.egg-info/SOURCES.txt +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools.egg-info/dependency_links.txt +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools.egg-info/entry_points.txt +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools.egg-info/requires.txt +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/cc_session_tools.egg-info/top_level.txt +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/claude_code_usage/__init__.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/claude_code_usage/attribution.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/claude_code_usage/cache.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/claude_code_usage/ccusage_wrapper.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/claude_code_usage/cli.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/claude_code_usage/parent_inference.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/claude_code_usage/parser.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/claude_code_usage/query.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/claude_code_usage/report.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/claude_code_usage/schema.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/claude_code_usage/session_names.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/hooks/__init__.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/hooks/after_response.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/hooks/bash_hard_deny.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/hooks/bash_security_review.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/hooks/cache.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/hooks/catchup.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/hooks/confirm_8digit.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/hooks/last_screenshot.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/hooks/marker_allow.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/hooks/markers.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/hooks/messaging_deliver.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/hooks/normalise.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/hooks/pdata_sync.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/hooks/pending_migration.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/hooks/pending_rename.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/hooks/script_allowlist.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/hooks/session_tag.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/hooks/stats.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/hooks/telemetry.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/hooks/telemetry_query.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/hooks/telemetry_trim.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/hooks/transcript.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/src/hooks/worklog_guard.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_after_response.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_attribution.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_bash_hard_deny.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_bash_security_review.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_bash_security_review_allowlist.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_bun_tools.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_cache.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_cache_sqlite.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccr_orphans.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccs_emptiness.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccs_eta.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccs_list_mode.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccs_scope_flags.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccs_sentinel_sort.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccs_session_counts.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_bundle_discovery.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_ccsched_jobs_cli.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_cli.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_context_override_cli.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_doctor.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_gc_prune.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_gc_report.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_hook_dispatcher.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_hooks_allowlist.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_hooks_install_prune.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_hooks_install_zero_arg.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_hooks_uninstall.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_install_everything.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_machine_identity_cli.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_main_gate.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_pdata_cli.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_pdata_init_cli.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_pdata_readiness_cli.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_pdata_reconcile_cli.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_pdata_rename_group_cli.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_pdata_reorganize_cli.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_pdata_sync_cli.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_pdata_verify_cli.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_repair_sessions_cli.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_sessions_cli.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_shell_install.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_skills_install.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_skills_uninstall.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_telemetry_query.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccst_telemetry_trim.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_ccusage_wrapper.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_claude_flags.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_claude_md_install.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_cli.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_cli_ccd.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_cli_ccd_dry_run.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_cli_ccr.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_cli_ccs.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_cli_version.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_confirm_8digit.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_context_overrides.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_db.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_debug.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_doctor_mutes.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_empty_session.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_hook_cache.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_hooks_install.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_hooks_install_records.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_install_sync.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_last_screenshot.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_levenshtein.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_machine_identity.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_marker_allow.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_migrate_ccmsg.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_migrate_ccsched.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_migrate_sessions_db.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_migrate_telemetry.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_normalise.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_occupancy.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_parent_inference.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_parser.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_paths.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_pdata_audit_skill.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_pdata_post_migration_skills.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_pending_migration_hook.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_picker.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_proc_lock.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_prompts.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_report.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_roots.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_rules.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_scheduler_bundled_jobs.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_schema.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_script_allowlist.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_session_names.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_session_tag.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_sessions.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_sessions_db.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_sessions_db_migration_chain.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_sessions_repair.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_tasklist.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_telemetry.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_telemetry_store.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_text_wrap.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_transcript.py +0 -0
- {cc_session_tools-3.10.2 → cc_session_tools-3.10.3}/tests/test_worklog_guard.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cc-session-tools"
|
|
7
|
-
version = "3.10.
|
|
7
|
+
version = "3.10.3"
|
|
8
8
|
description = "Claude Code session management CLI tools (ccd, ccr, ccs, claude-code-usage, ccst) and hook library (hooks package)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -141,6 +141,9 @@ relative to a subfolder rather than the project root (e.g. an index-of-a-subfold
|
|
|
141
141
|
root) - `--write`'s file_path-resolution verification will fail with a bare "does not resolve
|
|
142
142
|
under `<project_root>`" and no further guidance. Instead, map that column to a plain named field
|
|
143
143
|
(e.g. `source_file_path`) and note in the field's `description` what path it's relative to.
|
|
144
|
+
Likewise, a column documented to use multiple or shorthand path notations (e.g. brace expansion
|
|
145
|
+
alongside plain paths) is unsafe to map to `file_path_column` at all - map it to a plain named TEXT
|
|
146
|
+
field instead, exactly as for the subfolder-relative case.
|
|
144
147
|
|
|
145
148
|
**`content_column: null` default caveat:** an entry with `content_column` left unset falls back to
|
|
146
149
|
a raw `json.dumps(row)` dump as every record's content - rarely what you want for readable `ccst
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_source": "Anthropic published first-party API pricing (https://platform.claude.com/docs/en/about-claude/pricing), hand-curated. Per-token USD. Rows for retired models not on that page keep their historical list price.",
|
|
3
|
+
"_refreshed_at": "2026-09-29",
|
|
4
|
+
"models": {
|
|
5
|
+
"<synthetic>": {
|
|
6
|
+
"input": 0.0,
|
|
7
|
+
"output": 0.0,
|
|
8
|
+
"cache_creation_5m": 0.0,
|
|
9
|
+
"cache_creation_1h": 0.0,
|
|
10
|
+
"cache_read": 0.0
|
|
11
|
+
},
|
|
12
|
+
"claude-fable-5-1": {
|
|
13
|
+
"input": 0.00001,
|
|
14
|
+
"output": 0.00005,
|
|
15
|
+
"cache_creation_5m": 0.0000125,
|
|
16
|
+
"cache_creation_1h": 0.00002,
|
|
17
|
+
"cache_read": 0.00000025
|
|
18
|
+
},
|
|
19
|
+
"claude-mythos-5-1": {
|
|
20
|
+
"input": 0.00001,
|
|
21
|
+
"output": 0.00005,
|
|
22
|
+
"cache_creation_5m": 0.0000125,
|
|
23
|
+
"cache_creation_1h": 0.00002,
|
|
24
|
+
"cache_read": 0.00000025
|
|
25
|
+
},
|
|
26
|
+
"claude-fable-5": {
|
|
27
|
+
"input": 0.00001,
|
|
28
|
+
"output": 0.00005,
|
|
29
|
+
"cache_creation_5m": 0.0000125,
|
|
30
|
+
"cache_creation_1h": 0.00002,
|
|
31
|
+
"cache_read": 0.000001
|
|
32
|
+
},
|
|
33
|
+
"claude-mythos-5": {
|
|
34
|
+
"input": 0.00001,
|
|
35
|
+
"output": 0.00005,
|
|
36
|
+
"cache_creation_5m": 0.0000125,
|
|
37
|
+
"cache_creation_1h": 0.00002,
|
|
38
|
+
"cache_read": 0.000001
|
|
39
|
+
},
|
|
40
|
+
"claude-opus-5-5": {
|
|
41
|
+
"input": 0.000004,
|
|
42
|
+
"output": 0.00002,
|
|
43
|
+
"cache_creation_5m": 0.000005,
|
|
44
|
+
"cache_creation_1h": 0.000008,
|
|
45
|
+
"cache_read": 0.0000002
|
|
46
|
+
},
|
|
47
|
+
"claude-opus-5": {
|
|
48
|
+
"input": 0.000005,
|
|
49
|
+
"output": 0.000025,
|
|
50
|
+
"cache_creation_5m": 0.00000625,
|
|
51
|
+
"cache_creation_1h": 0.00001,
|
|
52
|
+
"cache_read": 0.0000005
|
|
53
|
+
},
|
|
54
|
+
"claude-opus-4-8": {
|
|
55
|
+
"input": 0.000005,
|
|
56
|
+
"output": 0.000025,
|
|
57
|
+
"cache_creation_5m": 0.00000625,
|
|
58
|
+
"cache_creation_1h": 0.00001,
|
|
59
|
+
"cache_read": 0.0000005
|
|
60
|
+
},
|
|
61
|
+
"claude-opus-4-7": {
|
|
62
|
+
"input": 0.000005,
|
|
63
|
+
"output": 0.000025,
|
|
64
|
+
"cache_creation_5m": 0.00000625,
|
|
65
|
+
"cache_creation_1h": 0.00001,
|
|
66
|
+
"cache_read": 0.0000005
|
|
67
|
+
},
|
|
68
|
+
"claude-opus-4-6": {
|
|
69
|
+
"input": 0.000005,
|
|
70
|
+
"output": 0.000025,
|
|
71
|
+
"cache_creation_5m": 0.00000625,
|
|
72
|
+
"cache_creation_1h": 0.00001,
|
|
73
|
+
"cache_read": 0.0000005
|
|
74
|
+
},
|
|
75
|
+
"claude-opus-4-5": {
|
|
76
|
+
"input": 0.000005,
|
|
77
|
+
"output": 0.000025,
|
|
78
|
+
"cache_creation_5m": 0.00000625,
|
|
79
|
+
"cache_creation_1h": 0.00001,
|
|
80
|
+
"cache_read": 0.0000005
|
|
81
|
+
},
|
|
82
|
+
"claude-opus-4-1": {
|
|
83
|
+
"input": 0.000015,
|
|
84
|
+
"output": 0.000075,
|
|
85
|
+
"cache_creation_5m": 0.00001875,
|
|
86
|
+
"cache_creation_1h": 0.00003,
|
|
87
|
+
"cache_read": 0.0000015
|
|
88
|
+
},
|
|
89
|
+
"claude-opus-4": {
|
|
90
|
+
"input": 0.000015,
|
|
91
|
+
"output": 0.000075,
|
|
92
|
+
"cache_creation_5m": 0.00001875,
|
|
93
|
+
"cache_creation_1h": 0.00003,
|
|
94
|
+
"cache_read": 0.0000015
|
|
95
|
+
},
|
|
96
|
+
"claude-sonnet-5-5": {
|
|
97
|
+
"input": 0.000002,
|
|
98
|
+
"output": 0.00001,
|
|
99
|
+
"cache_creation_5m": 0.0000025,
|
|
100
|
+
"cache_creation_1h": 0.000004,
|
|
101
|
+
"cache_read": 0.0000002
|
|
102
|
+
},
|
|
103
|
+
"claude-sonnet-5": {
|
|
104
|
+
"input": 0.000002,
|
|
105
|
+
"output": 0.00001,
|
|
106
|
+
"cache_creation_5m": 0.0000025,
|
|
107
|
+
"cache_creation_1h": 0.000004,
|
|
108
|
+
"cache_read": 0.0000002
|
|
109
|
+
},
|
|
110
|
+
"claude-sonnet-4-6": {
|
|
111
|
+
"input": 0.000003,
|
|
112
|
+
"output": 0.000015,
|
|
113
|
+
"cache_creation_5m": 0.00000375,
|
|
114
|
+
"cache_creation_1h": 0.000006,
|
|
115
|
+
"cache_read": 0.0000003
|
|
116
|
+
},
|
|
117
|
+
"claude-sonnet-4-5": {
|
|
118
|
+
"input": 0.000003,
|
|
119
|
+
"output": 0.000015,
|
|
120
|
+
"cache_creation_5m": 0.00000375,
|
|
121
|
+
"cache_creation_1h": 0.000006,
|
|
122
|
+
"cache_read": 0.0000003
|
|
123
|
+
},
|
|
124
|
+
"claude-sonnet-4": {
|
|
125
|
+
"input": 0.000003,
|
|
126
|
+
"output": 0.000015,
|
|
127
|
+
"cache_creation_5m": 0.00000375,
|
|
128
|
+
"cache_creation_1h": 0.000006,
|
|
129
|
+
"cache_read": 0.0000003
|
|
130
|
+
},
|
|
131
|
+
"claude-haiku-4-5": {
|
|
132
|
+
"input": 0.000001,
|
|
133
|
+
"output": 0.000005,
|
|
134
|
+
"cache_creation_5m": 0.00000125,
|
|
135
|
+
"cache_creation_1h": 0.000002,
|
|
136
|
+
"cache_read": 0.0000001
|
|
137
|
+
},
|
|
138
|
+
"claude-haiku-4": {
|
|
139
|
+
"input": 0.0000008,
|
|
140
|
+
"output": 0.000004,
|
|
141
|
+
"cache_creation_5m": 0.000001,
|
|
142
|
+
"cache_creation_1h": 0.0000016,
|
|
143
|
+
"cache_read": 0.00000008
|
|
144
|
+
},
|
|
145
|
+
"claude-3-7-sonnet-20250219": {
|
|
146
|
+
"input": 0.000003,
|
|
147
|
+
"output": 0.000015,
|
|
148
|
+
"cache_creation_5m": 0.00000375,
|
|
149
|
+
"cache_creation_1h": 0.000006,
|
|
150
|
+
"cache_read": 0.0000003
|
|
151
|
+
},
|
|
152
|
+
"claude-3-5-sonnet-20241022": {
|
|
153
|
+
"input": 0.000003,
|
|
154
|
+
"output": 0.000015,
|
|
155
|
+
"cache_creation_5m": 0.00000375,
|
|
156
|
+
"cache_creation_1h": 0.000006,
|
|
157
|
+
"cache_read": 0.0000003
|
|
158
|
+
},
|
|
159
|
+
"claude-3-5-haiku-20241022": {
|
|
160
|
+
"input": 0.0000008,
|
|
161
|
+
"output": 0.000004,
|
|
162
|
+
"cache_creation_5m": 0.000001,
|
|
163
|
+
"cache_creation_1h": 0.0000016,
|
|
164
|
+
"cache_read": 0.00000008
|
|
165
|
+
},
|
|
166
|
+
"claude-3-opus-20240229": {
|
|
167
|
+
"input": 0.000015,
|
|
168
|
+
"output": 0.000075,
|
|
169
|
+
"cache_creation_5m": 0.00001875,
|
|
170
|
+
"cache_creation_1h": 0.00003,
|
|
171
|
+
"cache_read": 0.0000015
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
"family_fallbacks": {
|
|
175
|
+
"fable": "claude-fable-5-1",
|
|
176
|
+
"mythos": "claude-mythos-5-1",
|
|
177
|
+
"opus": "claude-opus-5-5",
|
|
178
|
+
"sonnet": "claude-sonnet-5-5",
|
|
179
|
+
"haiku": "claude-haiku-4-5"
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Per-token pricing data and dollar calculations.
|
|
2
|
+
|
|
3
|
+
`data/pricing.json` is a hand-curated copy of Anthropic's published
|
|
4
|
+
first-party API rates (its `_source` / `_refreshed_at` fields say where
|
|
5
|
+
from and when); re-check it whenever a new model ships. Resolution order
|
|
6
|
+
for a model id:
|
|
7
|
+
|
|
8
|
+
1. an exact row;
|
|
9
|
+
2. a dated snapshot (`<id>-YYYYMMDD`, exactly eight digits, stripped
|
|
10
|
+
once) resolves to the undated row;
|
|
11
|
+
3. an id containing `claude-<family>-` for a family in `family_fallbacks`
|
|
12
|
+
is priced as that family's newest model - an estimate, which
|
|
13
|
+
`add_cost_column()` reports in a warning. Claude 3 ids (`claude-3-...`)
|
|
14
|
+
and non-Claude ids never match, so an unlisted old model is reported as
|
|
15
|
+
unpriced rather than given a newer model's price;
|
|
16
|
+
4. otherwise `lookup()` raises `KeyError`.
|
|
17
|
+
|
|
18
|
+
`<synthetic>` (Claude Code's zero-usage stub entries) has an explicit
|
|
19
|
+
all-zero row.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import logging
|
|
26
|
+
import re
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
import pandas as pd
|
|
31
|
+
|
|
32
|
+
_THIS_DIR = Path(__file__).resolve().parent
|
|
33
|
+
_PRICING_PATH = _THIS_DIR / "data" / "pricing.json"
|
|
34
|
+
_DATED_SNAPSHOT = re.compile(r"(.+)-\d{8}")
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _load() -> dict[str, Any]:
|
|
40
|
+
return json.loads(_PRICING_PATH.read_text())
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def resolve(model: str) -> tuple[str, bool]:
|
|
44
|
+
"""Return (pricing row key, True if the row came from a family fallback).
|
|
45
|
+
Raises KeyError if `model` has no row and no family match."""
|
|
46
|
+
data = _load()
|
|
47
|
+
models = data["models"]
|
|
48
|
+
if model in models:
|
|
49
|
+
return model, False
|
|
50
|
+
dated = _DATED_SNAPSHOT.fullmatch(model)
|
|
51
|
+
if dated and dated.group(1) in models:
|
|
52
|
+
return dated.group(1), False
|
|
53
|
+
for family, fallback_model in data["family_fallbacks"].items():
|
|
54
|
+
if f"claude-{family}-" in model:
|
|
55
|
+
return fallback_model, True
|
|
56
|
+
raise KeyError(model)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def lookup(model: str) -> dict[str, float]:
|
|
60
|
+
"""Return per-token rates for `model`. Raises KeyError if unknown."""
|
|
61
|
+
row, _ = resolve(model)
|
|
62
|
+
rates: dict[str, float] = _load()["models"][row]
|
|
63
|
+
return rates
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def cost_for_usage(
|
|
67
|
+
model: str,
|
|
68
|
+
input_tokens: int,
|
|
69
|
+
output_tokens: int,
|
|
70
|
+
cache_creation_5m: int = 0,
|
|
71
|
+
cache_creation_1h: int = 0,
|
|
72
|
+
cache_read: int = 0,
|
|
73
|
+
) -> float:
|
|
74
|
+
"""Return the dollar cost of one assistant message's token mix."""
|
|
75
|
+
rates = lookup(model)
|
|
76
|
+
return (
|
|
77
|
+
input_tokens * rates["input"]
|
|
78
|
+
+ output_tokens * rates["output"]
|
|
79
|
+
+ cache_creation_5m * rates["cache_creation_5m"]
|
|
80
|
+
+ cache_creation_1h * rates["cache_creation_1h"]
|
|
81
|
+
+ cache_read * rates["cache_read"]
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def add_cost_column(df: pd.DataFrame) -> pd.DataFrame:
|
|
86
|
+
"""Return `df` with a `cost_usd` column added.
|
|
87
|
+
|
|
88
|
+
Vectorised over rows: builds a per-row rates frame from the model
|
|
89
|
+
column then dot-products against the token columns. Models that
|
|
90
|
+
can't be resolved (after family fallback) get cost = 0 with a
|
|
91
|
+
warning - we don't want a single unknown model to break a report.
|
|
92
|
+
Models priced by family fallback are named in a separate warning, so
|
|
93
|
+
an estimated price is never silent.
|
|
94
|
+
"""
|
|
95
|
+
out = df.copy()
|
|
96
|
+
if out.empty:
|
|
97
|
+
out["cost_usd"] = pd.Series(dtype=float)
|
|
98
|
+
return out
|
|
99
|
+
models = _load()["models"]
|
|
100
|
+
zero = {"input": 0.0, "output": 0.0, "cache_creation_5m": 0.0,
|
|
101
|
+
"cache_creation_1h": 0.0, "cache_read": 0.0}
|
|
102
|
+
rates_by_model: dict[str, dict[str, float]] = {}
|
|
103
|
+
unknown: set[str] = set()
|
|
104
|
+
fallback: dict[str, str] = {}
|
|
105
|
+
for model in out["model"].unique():
|
|
106
|
+
try:
|
|
107
|
+
row, is_fallback = resolve(model)
|
|
108
|
+
except KeyError:
|
|
109
|
+
unknown.add(model)
|
|
110
|
+
rates_by_model[model] = zero
|
|
111
|
+
continue
|
|
112
|
+
rates_by_model[model] = models[row]
|
|
113
|
+
if is_fallback:
|
|
114
|
+
fallback[model] = row
|
|
115
|
+
rates_records = [rates_by_model[model] for model in out["model"]]
|
|
116
|
+
rates_df = pd.DataFrame(rates_records, index=out.index)
|
|
117
|
+
out["cost_usd"] = (
|
|
118
|
+
out["input_tokens"] * rates_df["input"]
|
|
119
|
+
+ out["output_tokens"] * rates_df["output"]
|
|
120
|
+
+ out["cache_creation_5m"] * rates_df["cache_creation_5m"]
|
|
121
|
+
+ out["cache_creation_1h"] * rates_df["cache_creation_1h"]
|
|
122
|
+
+ out["cache_read"] * rates_df["cache_read"]
|
|
123
|
+
)
|
|
124
|
+
if unknown:
|
|
125
|
+
logger.warning(
|
|
126
|
+
"no pricing for models: %s (cost set to 0 for those rows)",
|
|
127
|
+
sorted(unknown),
|
|
128
|
+
)
|
|
129
|
+
if fallback:
|
|
130
|
+
logger.warning(
|
|
131
|
+
"no exact pricing for models, priced by family fallback: %s",
|
|
132
|
+
", ".join(f"{model} as {row}" for model, row in sorted(fallback.items())),
|
|
133
|
+
)
|
|
134
|
+
return out
|
|
@@ -41,10 +41,16 @@ class TranscriptError(Exception):
|
|
|
41
41
|
not a single parseable JSON line."""
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
SYNTHETIC_MODEL = "<synthetic>"
|
|
45
|
+
|
|
46
|
+
|
|
44
47
|
def _current_context_tokens(transcript_path: Path) -> tuple[int, str]:
|
|
45
48
|
"""(token count, model id) from the last non-sidechain assistant message's
|
|
46
49
|
usage. (0, "") for a fresh session with no assistant usage yet - that is a
|
|
47
|
-
legitimate state, not an error.
|
|
50
|
+
legitimate state, not an error. Entries whose model is `<synthetic>` are
|
|
51
|
+
skipped: Claude Code writes them (with all-zero usage) for things that are
|
|
52
|
+
not API responses, such as a usage-limit notice, so they say nothing about
|
|
53
|
+
the context size."""
|
|
48
54
|
if not transcript_path.is_file():
|
|
49
55
|
raise TranscriptError(f"transcript not found: {transcript_path}")
|
|
50
56
|
|
|
@@ -68,9 +74,10 @@ def _current_context_tokens(transcript_path: Path) -> tuple[int, str]:
|
|
|
68
74
|
continue
|
|
69
75
|
message = obj.get("message") or {}
|
|
70
76
|
usage = message.get("usage")
|
|
71
|
-
|
|
77
|
+
model = message.get("model") or ""
|
|
78
|
+
if isinstance(usage, dict) and model != SYNTHETIC_MODEL:
|
|
72
79
|
last_usage = usage
|
|
73
|
-
last_model =
|
|
80
|
+
last_model = model
|
|
74
81
|
except OSError as exc:
|
|
75
82
|
raise TranscriptError(f"cannot read transcript: {exc}") from exc
|
|
76
83
|
|
|
@@ -102,19 +109,18 @@ def _window_label(window: int) -> str:
|
|
|
102
109
|
return f"{(window + 500) // 1000}k"
|
|
103
110
|
|
|
104
111
|
|
|
105
|
-
def _format_cost(*, tokens: int,
|
|
106
|
-
"""
|
|
107
|
-
(not rounded) to 2dp - matches `bc`'s `scale=2`
|
|
108
|
-
original. Uses Decimal rather than native float
|
|
109
|
-
math.floor(raw * 100) / 100 on a float hits
|
|
110
|
-
ordinary inputs (e.g. tokens=
|
|
111
|
-
0.
|
|
112
|
-
|
|
113
|
-
hypothetical. Decimal(str(price_per_mtok)) avoids reintroducing the same
|
|
112
|
+
def _format_cost(*, tokens: int, cache_read_price_per_mtok: float) -> str:
|
|
113
|
+
"""Cost of reading `tokens` from the prompt cache at the model's cache-read
|
|
114
|
+
price, truncated (not rounded) to 2dp - matches `bc`'s `scale=2`
|
|
115
|
+
semantics from the bash original. Uses Decimal rather than native float
|
|
116
|
+
arithmetic: a naive math.floor(raw * 100) / 100 on a float hits
|
|
117
|
+
representation error for ordinary inputs (e.g. tokens=350_000,
|
|
118
|
+
price=0.20 evaluates to 0.06999999999999999 in float, one cent short of
|
|
119
|
+
the correct 0.07). Decimal(str(price)) avoids reintroducing the same
|
|
114
120
|
float-representation problem by round-tripping the price through its
|
|
115
121
|
float form first.
|
|
116
122
|
"""
|
|
117
|
-
raw = Decimal(tokens) / Decimal(1_000_000) * Decimal(str(
|
|
123
|
+
raw = Decimal(tokens) / Decimal(1_000_000) * Decimal(str(cache_read_price_per_mtok))
|
|
118
124
|
return str(raw.quantize(Decimal("0.01"), rounding=ROUND_DOWN))
|
|
119
125
|
|
|
120
126
|
|
|
@@ -134,7 +140,7 @@ def _reason(*, tokens: int, window: int, price: float, name: str, now: str, red:
|
|
|
134
140
|
k = _k_tokens(tokens)
|
|
135
141
|
pct = _pct_of_window(tokens, window)
|
|
136
142
|
label = _window_label(window)
|
|
137
|
-
cost = _format_cost(tokens=tokens,
|
|
143
|
+
cost = _format_cost(tokens=tokens, cache_read_price_per_mtok=price)
|
|
138
144
|
emoji = "🔴" if red else "🟠"
|
|
139
145
|
when = "when you get a natural break" if red else "whenever convenient"
|
|
140
146
|
return (
|
|
@@ -176,7 +182,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
176
182
|
return 0
|
|
177
183
|
|
|
178
184
|
window = model_info.context_window(model_id)
|
|
179
|
-
price = model_info.
|
|
185
|
+
price = model_info.cache_read_price_per_mtok(model_id)
|
|
180
186
|
name = model_info.display_name(model_id)
|
|
181
187
|
now = datetime.datetime.now().strftime("%H:%M")
|
|
182
188
|
red = tokens >= THRESHOLD_RED
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Model id -> context window size, cache-read price, display name.
|
|
2
|
+
|
|
3
|
+
Consumed by context_window_warning to phrase its nudge in terms of "% of the
|
|
4
|
+
model's actual window" and to estimate a per-turn cache-read cost.
|
|
5
|
+
|
|
6
|
+
Context windows as of 2026-09-29: Fable 5 / Mythos 5 / Opus 5 / Opus 4.6-4.8 /
|
|
7
|
+
Sonnet 5 / Sonnet 5.5 / Opus 5.5 / Fable 5.1 / Mythos 5.1 / Sonnet 4.6 all 1,000,000 tokens
|
|
8
|
+
(the default, not a beta long-context mode); Haiku 4.5 stays at 200,000.
|
|
9
|
+
Prices are each model's published first-party cache-read ("cache hits and
|
|
10
|
+
refreshes") rate per million tokens, from
|
|
11
|
+
https://platform.claude.com/docs/en/about-claude/pricing (checked 2026-09-29).
|
|
12
|
+
They are not a fixed fraction of the input price: Fable 5.1 and Mythos 5.1 read at 0.025x
|
|
13
|
+
and Opus 5.5 at 0.05x, every other model here at 0.1x.
|
|
14
|
+
|
|
15
|
+
Re-check this table whenever a new model ships. A model not matched by any
|
|
16
|
+
case falls through to the default row (200000 tokens / $0.50 per MTok / "an
|
|
17
|
+
unrecognized model") - deliberately the same window as every pre-Claude-5
|
|
18
|
+
model, so an unrecognized model degrades to conservative,
|
|
19
|
+
already-correct-for-most-history behaviour rather than going quiet or firing
|
|
20
|
+
too early.
|
|
21
|
+
|
|
22
|
+
Deliberately not `claude_code_usage.pricing`: that module imports pandas, too
|
|
23
|
+
heavy for a hook that runs on every Stop event. tests/test_pricing.py asserts
|
|
24
|
+
that every cache-read price here matches that module's table.
|
|
25
|
+
"""
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import re
|
|
29
|
+
|
|
30
|
+
_WINDOW_1M = {
|
|
31
|
+
"claude-fable-5", "claude-mythos-5",
|
|
32
|
+
"claude-opus-5", "claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6",
|
|
33
|
+
"claude-sonnet-5", "claude-sonnet-4-6",
|
|
34
|
+
"claude-sonnet-5-5", "claude-opus-5-5", "claude-fable-5-1", "claude-mythos-5-1",
|
|
35
|
+
}
|
|
36
|
+
_CACHE_READ_PRICE = {
|
|
37
|
+
"claude-fable-5-1": 0.25, "claude-mythos-5-1": 0.25, "claude-fable-5": 1.00, "claude-mythos-5": 1.00,
|
|
38
|
+
"claude-opus-5-5": 0.20,
|
|
39
|
+
"claude-opus-5": 0.50, "claude-opus-4-8": 0.50, "claude-opus-4-7": 0.50, "claude-opus-4-6": 0.50,
|
|
40
|
+
"claude-sonnet-5-5": 0.20, "claude-sonnet-5": 0.20, "claude-sonnet-4-6": 0.30,
|
|
41
|
+
"claude-haiku-4-5": 0.10,
|
|
42
|
+
}
|
|
43
|
+
_NAME = {
|
|
44
|
+
"claude-fable-5": "Fable 5", "claude-mythos-5": "Mythos 5",
|
|
45
|
+
"claude-opus-5": "Opus 5", "claude-opus-4-8": "Opus 4.8",
|
|
46
|
+
"claude-opus-4-7": "Opus 4.7", "claude-opus-4-6": "Opus 4.6",
|
|
47
|
+
"claude-sonnet-5": "Sonnet 5", "claude-sonnet-4-6": "Sonnet 4.6",
|
|
48
|
+
"claude-sonnet-5-5": "Sonnet 5.5", "claude-opus-5-5": "Opus 5.5",
|
|
49
|
+
"claude-fable-5-1": "Fable 5.1", "claude-mythos-5-1": "Mythos 5.1",
|
|
50
|
+
"claude-haiku-4-5": "Haiku 4.5",
|
|
51
|
+
}
|
|
52
|
+
_DEFAULT_WINDOW = 200_000
|
|
53
|
+
_DEFAULT_CACHE_READ_PRICE = 0.50
|
|
54
|
+
_DEFAULT_NAME = "an unrecognized model"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
_DATED_SNAPSHOT = re.compile(r"(.+)-\d{8}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _canonical(model_id: str) -> str:
|
|
61
|
+
"""A dated snapshot id (`<id>-YYYYMMDD`) resolves to its undated row. Applied once, and
|
|
62
|
+
only for an exactly-eight-digit suffix: any other suffix (or a snapshot of an unknown id)
|
|
63
|
+
stays unrecognised. `[1m]`/`-latest` are not handled - transcripts' `message.model` never
|
|
64
|
+
carries them."""
|
|
65
|
+
match = _DATED_SNAPSHOT.fullmatch(model_id)
|
|
66
|
+
return match.group(1) if match else model_id
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def context_window(model_id: str) -> int:
|
|
70
|
+
return 1_000_000 if _canonical(model_id) in _WINDOW_1M else _DEFAULT_WINDOW
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def cache_read_price_per_mtok(model_id: str) -> float:
|
|
74
|
+
return _CACHE_READ_PRICE.get(_canonical(model_id), _DEFAULT_CACHE_READ_PRICE)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def display_name(model_id: str) -> str:
|
|
78
|
+
return _NAME.get(_canonical(model_id), _DEFAULT_NAME)
|
|
@@ -56,27 +56,33 @@ def test_corrupt_transcript_raises(tmp_path: Path):
|
|
|
56
56
|
cww._current_context_tokens(p)
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
def
|
|
60
|
-
"""tokens
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
def test_cost_is_tokens_times_cache_read_price():
|
|
60
|
+
"""155k tokens at a $0.30/MTok cache-read price is $0.0465, shown as 0.04."""
|
|
61
|
+
assert cww._format_cost(tokens=155_000, cache_read_price_per_mtok=0.30) == "0.04"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@pytest.mark.parametrize(
|
|
65
|
+
("model_id", "tokens", "cost"),
|
|
66
|
+
[("claude-fable-5-1", 400_000, "0.10"), ("claude-sonnet-5-5", 500_000, "0.10")],
|
|
67
|
+
)
|
|
68
|
+
def test_cost_uses_the_models_published_cache_read_price(model_id, tokens, cost):
|
|
69
|
+
price = cww.model_info.cache_read_price_per_mtok(model_id)
|
|
70
|
+
assert cww._format_cost(tokens=tokens, cache_read_price_per_mtok=price) == cost
|
|
64
71
|
|
|
65
72
|
|
|
66
73
|
def test_cost_truncates_not_rounds():
|
|
67
|
-
"""tokens=199_000, price
|
|
74
|
+
"""tokens=199_000, cache-read price 0.30 -> 0.199*0.30 = 0.0597 -> bc truncates
|
|
68
75
|
to "0.05"; a naive f'{x:.2f}' would round to "0.06". Chosen specifically
|
|
69
76
|
because truncation and rounding clearly disagree on this input."""
|
|
70
|
-
assert cww._format_cost(tokens=199_000,
|
|
77
|
+
assert cww._format_cost(tokens=199_000, cache_read_price_per_mtok=0.30) == "0.05"
|
|
71
78
|
|
|
72
79
|
|
|
73
80
|
def test_cost_does_not_lose_a_cent_to_float_representation_error():
|
|
74
81
|
"""math.floor(raw * 100) / 100 on a native float loses a cent here:
|
|
75
|
-
|
|
76
|
-
exactly 0.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
assert cww._format_cost(tokens=700_000, price_per_mtok=3.00) == "0.21"
|
|
82
|
+
350000/1e6 * 0.20 evaluates to 0.06999999999999999 in float, not
|
|
83
|
+
exactly 0.07, so a naive floor gives "0.06". 350k tokens at the Sonnet 5.x
|
|
84
|
+
/ Opus 5.5 cache-read price is an entirely ordinary state for this hook."""
|
|
85
|
+
assert cww._format_cost(tokens=350_000, cache_read_price_per_mtok=0.20) == "0.07"
|
|
80
86
|
|
|
81
87
|
|
|
82
88
|
def test_k_and_pct_integer_arithmetic():
|
|
@@ -134,7 +140,7 @@ def test_orange_band_emits_block_decision(tmp_path, monkeypatch, capsys):
|
|
|
134
140
|
assert payload["decision"] == "block"
|
|
135
141
|
assert "🟠 CONTEXT" in payload["reason"]
|
|
136
142
|
assert "~155k tokens" in payload["reason"]
|
|
137
|
-
assert "~$0.
|
|
143
|
+
assert "~$0.03/turn" in payload["reason"] # Sonnet 5 cache reads $0.20/MTok
|
|
138
144
|
|
|
139
145
|
|
|
140
146
|
def test_red_band_emits_red_decision(tmp_path, monkeypatch, capsys):
|
|
@@ -185,9 +191,10 @@ def test_override_end_to_end_via_real_store(tmp_path, monkeypatch, capsys):
|
|
|
185
191
|
@pytest.mark.parametrize(
|
|
186
192
|
("model_id", "name", "cost"),
|
|
187
193
|
[
|
|
188
|
-
|
|
189
|
-
("claude-
|
|
190
|
-
("claude-
|
|
194
|
+
# 266k tokens x published cache-read price ($0.20, $0.20, $0.25 per MTok)
|
|
195
|
+
("claude-sonnet-5-5", "Sonnet 5.5", "0.05"),
|
|
196
|
+
("claude-opus-5-5", "Opus 5.5", "0.05"),
|
|
197
|
+
("claude-fable-5-1", "Fable 5.1", "0.06"),
|
|
191
198
|
],
|
|
192
199
|
)
|
|
193
200
|
def test_new_5x_models_are_measured_against_their_one_million_token_window(
|
|
@@ -203,3 +210,43 @@ def test_new_5x_models_are_measured_against_their_one_million_token_window(
|
|
|
203
210
|
assert name in reason
|
|
204
211
|
assert f"~${cost}/turn" in reason
|
|
205
212
|
assert "unrecognized" not in reason
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _synthetic_stub() -> dict:
|
|
216
|
+
"""The shape Claude Code writes for a non-API assistant entry (e.g. a usage-limit notice)."""
|
|
217
|
+
return {"type": "assistant", "isApiErrorMessage": True, "message": {"model": "<synthetic>",
|
|
218
|
+
"stop_reason": "stop_sequence",
|
|
219
|
+
"usage": {"input_tokens": 0, "output_tokens": 0, "cache_creation_input_tokens": 0,
|
|
220
|
+
"cache_read_input_tokens": 0}}}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def test_trailing_synthetic_entry_does_not_reset_the_count(tmp_path: Path):
|
|
224
|
+
p = tmp_path / "t.jsonl"
|
|
225
|
+
_write_transcript(p, [
|
|
226
|
+
{"type": "assistant", "message": {"model": "claude-sonnet-5-5",
|
|
227
|
+
"usage": {"input_tokens": 500, "cache_creation_input_tokens": 1500, "cache_read_input_tokens": 178000}}},
|
|
228
|
+
_synthetic_stub(),
|
|
229
|
+
])
|
|
230
|
+
assert cww._current_context_tokens(p) == (180000, "claude-sonnet-5-5")
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def test_only_synthetic_entries_behave_like_a_fresh_session(tmp_path: Path):
|
|
234
|
+
p = tmp_path / "t.jsonl"
|
|
235
|
+
_write_transcript(p, [{"type": "user", "message": {"content": "hi"}}, _synthetic_stub()])
|
|
236
|
+
assert cww._current_context_tokens(p) == (0, "")
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def test_warning_still_fires_after_a_trailing_synthetic_entry(tmp_path, monkeypatch, capsys):
|
|
240
|
+
p = tmp_path / "t.jsonl"
|
|
241
|
+
_write_transcript(p, [
|
|
242
|
+
{"type": "assistant", "message": {"model": "claude-sonnet-5-5",
|
|
243
|
+
"usage": {"input_tokens": 180000, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}}},
|
|
244
|
+
_synthetic_stub(),
|
|
245
|
+
])
|
|
246
|
+
monkeypatch.setattr(cww, "_is_overridden", lambda session_id: False)
|
|
247
|
+
rc, out = _run_main(monkeypatch, capsys, {"transcript_path": str(p), "session_id": "s1"})
|
|
248
|
+
assert rc == 0
|
|
249
|
+
reason = json.loads(out.out)["reason"]
|
|
250
|
+
assert "~180k tokens" in reason
|
|
251
|
+
assert "Sonnet 5.5" in reason
|
|
252
|
+
assert "unrecognized" not in reason
|