usage-cli 0.33.3__tar.gz → 0.33.4__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.
- {usage_cli-0.33.3/usage_cli.egg-info → usage_cli-0.33.4}/PKG-INFO +2 -2
- {usage_cli-0.33.3 → usage_cli-0.33.4}/README.md +1 -1
- {usage_cli-0.33.3 → usage_cli-0.33.4}/installer/session_hooks.py +1 -1
- {usage_cli-0.33.3 → usage_cli-0.33.4}/pyproject.toml +1 -1
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_pricing.py +1 -1
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_project_resolver.py +4 -1
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_session_resume.py +170 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_wintray.py +4 -2
- {usage_cli-0.33.3 → usage_cli-0.33.4/usage_cli.egg-info}/PKG-INFO +2 -2
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_common/pricing.py +2 -2
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_hooks/usage_session_resume.py +79 -1
- {usage_cli-0.33.3 → usage_cli-0.33.4}/wintray/app.py +8 -1
- {usage_cli-0.33.3 → usage_cli-0.33.4}/LICENSE +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/THIRD_PARTY_NOTICES.md +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/adapters/__init__.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/adapters/agy.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/adapters/claude.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/adapters/codex.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/adapters/grok.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/adapters/muse.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/adapters/rate_limits.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/adapters/registry.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/adapters/types.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/analyzer/__init__.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/analyzer/aggregator.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/analyzer/blocks.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/analyzer/diagnoser.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/analyzer/diagnosis_snapshot.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/analyzer/dispatch_ledger.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/analyzer/insights.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/analyzer/persona_loader.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/analyzer/reporter.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/analyzer/subscription.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/analyzer/usage_snapshot.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/i18n.json +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/installer/__init__.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/installer/claude_pane.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/installer/codex_hook_trust.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/installer/login_item.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/installer/setup_hook.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/installer/statusline_settings.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/__init__.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/_shared_cache_file.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/agy_disk_cache.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/agy_loader.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/agy_quota_probe.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/cache_quarantine.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/chromium_cache.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/claude_desktop.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/claude_paths.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/claude_usage.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/codex_disk_cache.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/codex_events.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/codex_fork_replay.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/codex_loader.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/codex_paths.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/disk_cache_common.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/disk_cache_lifecycle.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/grok_loader.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/grok_quota_probe.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/history_disk_cache.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/history_loader.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/jsonl_limits.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/jsonl_utils.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/muse_loader.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/main.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/__init__.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/actions.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/agy.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/app.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/chrome.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/fsevents_watch.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/grok.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/manual_refresh.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/menu.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/new_badge.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/notify.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/popover.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/prefs.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/refresh.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/state.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/title.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/update.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/whats_new.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/panels/__init__.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/panels/base.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/panels/catalog.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/panels/dynamic_height.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/panels/panel_scale.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/panels/panel_window.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/panels/panel_window_state.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/panels/payload.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/panels/web_panel.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/panels/window_drag.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/quota/__init__.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/quota/agy_window_keeper.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/quota/burn_rate.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/quota/codex_window_keeper.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/quota/keeper_outcome.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/quota/notifications.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/quota/quota_snapshot.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/quota/status_payload.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/quota/usage_rate.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/quota/window_keeper.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/setup.cfg +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/setup_app.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_adapters_claude.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_adapters_codex.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_adapters_rate_limits.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_aggregator.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_agy_adapter.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_agy_disk_cache.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_agy_loader.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_agy_quota_probe.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_agy_window_keeper.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_analyzer_blocks.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_analyzer_pipeline.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_app_version.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_burn_rate.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_cache_quarantine.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_check_upstream.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_chromium_cache.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_claude_desktop.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_claude_pane.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_claude_paths.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_codex_consistency.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_codex_hook_trust.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_codex_loader.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_codex_window_keeper.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_desktop_fresh_cache.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_desktop_quota_display.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_desktop_quota_review.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_diagnoser.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_disk_cache_shards.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_dispatch_ledger.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_doctor.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_dynamic_height.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_fsevents_watch.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_grok_loader.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_grok_quota_probe.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_history_loader.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_hook_scripts_are_standalone.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_html_report_snapshot.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_i18n_format_fallback.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_i18n_key_parity.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_i18n_packaged_path.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_insights.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_jsonl_utils.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_keeper_menu.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_keeper_outcome.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_login_item.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_main.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_manual_refresh.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_menubar.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_menubar_agy.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_menubar_grok.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_menubar_notify.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_menubar_prefs.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_menubar_state.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_menubar_update.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_muse_loader.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_new_badge.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_packaged_resources.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_packaging.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_panel_footer_layout.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_panel_scale.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_panel_window_state.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_panels.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_panels_lazy_import.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_persona_loader.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_quota_aware.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_quota_label.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_report_daily_chart.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_report_filter_js.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_reporter_build_data.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_service_status.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_setup_app.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_setup_hook.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_setup_hook_coexistence.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_setup_hook_resume.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_setup_hook_terse.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_subprocess_utils.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_subscription.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_taskbar_menu.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_time_utils.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_tui.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_tui_sprite.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_ui_tables.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_update_checker.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_update_gate.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_update_release_notes.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_cli.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_client.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_diagnosis_snapshot.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_dir_sweeper.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_lang.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_logging.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_notifications.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_rate.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_snapshot.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_snapshot_replay.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_statusline.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_statusline_agy.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_statusline_forwarder.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_statusline_grok.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_terse_mode.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_terse_reminder.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_web_panel_payload.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_whats_new.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_wheel_packaging.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_win_login_item.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_window_keeper.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_windows_manifest.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tui/__init__.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tui/app.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/tui/sprite.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/ui/__init__.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/ui/html_report.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/ui/report_charts.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/ui/report_daily_chart.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/ui/report_filter.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/ui/report_ledger.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/ui/report_scripts.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/ui/report_styles.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/ui/tables.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/updates/__init__.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/updates/checker.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/updates/gate.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/updates/release_notes.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/updates/whats_new.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_app/__init__.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_app/cli.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_app/doctor.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_cli.egg-info/SOURCES.txt +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_cli.egg-info/dependency_links.txt +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_cli.egg-info/entry_points.txt +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_cli.egg-info/requires.txt +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_cli.egg-info/top_level.txt +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_common/__init__.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_common/app_version.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_common/i18n.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_common/prefs.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_common/project_resolver.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_common/service_status.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_common/subprocess_utils.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_common/time_utils.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_common/usage_dir_sweeper.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_common/usage_lang.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_common/usage_logging.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_hooks/__init__.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_hooks/usage_quota_aware.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_hooks/usage_statusline.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_hooks/usage_statusline_agy.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_hooks/usage_statusline_forwarder.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_hooks/usage_statusline_grok.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_hooks/usage_terse_mode.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_hooks/usage_terse_reminder.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/wintray/__init__.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/wintray/login_item.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/wintray/menu.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/wintray/prefs.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/wintray/quota_label.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/wintray/taskbar_menu.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/wintray/taskbar_obstacles.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/wintray/taskbar_overlay.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/wintray/watch.py +0 -0
- {usage_cli-0.33.3 → usage_cli-0.33.4}/wintray/whats_new.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: usage-cli
|
|
3
|
-
Version: 0.33.
|
|
3
|
+
Version: 0.33.4
|
|
4
4
|
Summary: usage — 在 macOS menu bar 與 Windows 系統匣顯示 Claude Code、Codex、Antigravity 用量的小工具(也提供終端機 TUI)
|
|
5
5
|
License-Expression: AGPL-3.0-only
|
|
6
6
|
Requires-Python: >=3.13
|
|
@@ -138,7 +138,7 @@ Once set up, the bottom of the Claude Code window will show a status line like t
|
|
|
138
138
|
|
|
139
139
|
See your quota, other conversations, and background jobs without leaving Claude Code. Available on macOS and Windows.
|
|
140
140
|
|
|
141
|
-
<p align="center"><img src="docs/side-pane.png" alt="Claude Code side pane showing quotas, conversations, and background jobs" width="637"></p>
|
|
141
|
+
<p align="center"><img src="docs/side-pane.en.png" alt="Claude Code side pane showing quotas, conversations, and background jobs" width="637"></p>
|
|
142
142
|
|
|
143
143
|
**What you’ll see**
|
|
144
144
|
|
|
@@ -116,7 +116,7 @@ Once set up, the bottom of the Claude Code window will show a status line like t
|
|
|
116
116
|
|
|
117
117
|
See your quota, other conversations, and background jobs without leaving Claude Code. Available on macOS and Windows.
|
|
118
118
|
|
|
119
|
-
<p align="center"><img src="docs/side-pane.png" alt="Claude Code side pane showing quotas, conversations, and background jobs" width="637"></p>
|
|
119
|
+
<p align="center"><img src="docs/side-pane.en.png" alt="Claude Code side pane showing quotas, conversations, and background jobs" width="637"></p>
|
|
120
120
|
|
|
121
121
|
**What you’ll see**
|
|
122
122
|
|
|
@@ -59,7 +59,7 @@ CODEX_CONFIG = setup_hook.CODEX_CONFIG
|
|
|
59
59
|
# session. Off by default: enabled only via the menu toggle, never by self_heal.
|
|
60
60
|
RESUME_HOOK_TARGET = Path(os.path.expanduser("~/.claude/usage-session-resume.py"))
|
|
61
61
|
RESUME_PROMPT_SIDECAR = Path(os.path.expanduser("~/.claude/usage-resume-prompt.json"))
|
|
62
|
-
RESUME_HOOK_VERSION = "1.
|
|
62
|
+
RESUME_HOOK_VERSION = "1.11"
|
|
63
63
|
RESUME_MATCHER = "startup|clear|resume"
|
|
64
64
|
RESUME_LANGS = ("zh-TW", "zh-CN", "en", "ja", "ko")
|
|
65
65
|
_RESUME_MARKER = "usage-session-resume"
|
|
@@ -383,7 +383,7 @@ def test_fallback_pricing_contains_expected_models() -> None:
|
|
|
383
383
|
"input_cost_per_token": 2e-6,
|
|
384
384
|
"output_cost_per_token": 10e-6,
|
|
385
385
|
"cache_creation_input_token_cost": 2.5e-6,
|
|
386
|
-
"cache_read_input_token_cost": 0.
|
|
386
|
+
"cache_read_input_token_cost": 0.1e-6,
|
|
387
387
|
}
|
|
388
388
|
assert pricing._resolve_model_key("claude-sonnet-5", fallback) == "claude-sonnet-5"
|
|
389
389
|
assert fallback["claude-haiku-5-5"] == {
|
|
@@ -214,7 +214,10 @@ def test_project_from_encoded_path_matches_names_claude_flattens(tmp_path: Path,
|
|
|
214
214
|
def test_project_from_encoded_path_matches_truncated_long_path(tmp_path: Path) -> None:
|
|
215
215
|
# Claude Code keeps the first 200 encoded characters and appends "-<hash>".
|
|
216
216
|
projects_dir = tmp_path / "projects"
|
|
217
|
-
|
|
217
|
+
# Size the path below Windows' MAX_PATH limit while keeping it over 200.
|
|
218
|
+
prefix = "long-project-"
|
|
219
|
+
padding = 220 - len(_claude_encode(tmp_path / "work")) - len(prefix) - 1
|
|
220
|
+
name = prefix + "x" * padding
|
|
218
221
|
real_project = tmp_path / "work" / name
|
|
219
222
|
real_project.mkdir(parents=True)
|
|
220
223
|
encoded = _claude_encode(real_project)
|
|
@@ -1398,3 +1398,173 @@ def test_build_prompt_skips_diagnosis_reminder_when_snapshot_is_stale(
|
|
|
1398
1398
|
)
|
|
1399
1399
|
|
|
1400
1400
|
assert "Health check:" not in prompt
|
|
1401
|
+
|
|
1402
|
+
|
|
1403
|
+
def _register_session(tmp_path: Path, session_id: str, pid: int) -> None:
|
|
1404
|
+
sessions = tmp_path / "sessions"
|
|
1405
|
+
sessions.mkdir(exist_ok=True)
|
|
1406
|
+
(sessions / f"{pid}.json").write_text(
|
|
1407
|
+
json.dumps({"pid": pid, "sessionId": session_id, "kind": "interactive"}),
|
|
1408
|
+
encoding="utf-8",
|
|
1409
|
+
)
|
|
1410
|
+
|
|
1411
|
+
|
|
1412
|
+
def _two_sessions(tmp_path: Path, *, neighbour_age: timedelta) -> tuple[Path, Path]:
|
|
1413
|
+
"""An older finished session plus a neighbour whose log was written ``neighbour_age`` ago."""
|
|
1414
|
+
project = _project_dir(tmp_path)
|
|
1415
|
+
now = datetime.now().astimezone()
|
|
1416
|
+
finished = project / "finished.jsonl"
|
|
1417
|
+
_write_session(finished, when=now - timedelta(hours=1), request="finish the release notes")
|
|
1418
|
+
stamp = now.timestamp() - 3600
|
|
1419
|
+
os.utime(finished, (stamp, stamp))
|
|
1420
|
+
neighbour = project / "neighbour.jsonl"
|
|
1421
|
+
_write_session(neighbour, when=now - neighbour_age, request="plan the kyoto trip app")
|
|
1422
|
+
stamp = now.timestamp() - neighbour_age.total_seconds()
|
|
1423
|
+
os.utime(neighbour, (stamp, stamp))
|
|
1424
|
+
current = project / "current.jsonl"
|
|
1425
|
+
current.write_text("", encoding="utf-8")
|
|
1426
|
+
return neighbour, current
|
|
1427
|
+
|
|
1428
|
+
|
|
1429
|
+
def _resume_prompt(current: Path) -> str:
|
|
1430
|
+
return mod._build_prompt({"transcript_path": str(current), "cwd": "/Users/me/Developer/myproj"})
|
|
1431
|
+
|
|
1432
|
+
|
|
1433
|
+
def test_build_prompt_skips_session_another_window_is_still_writing(
|
|
1434
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
1435
|
+
) -> None:
|
|
1436
|
+
monkeypatch.setenv("USAGE_LANG", "en")
|
|
1437
|
+
_sidecar(tmp_path, monkeypatch)
|
|
1438
|
+
monkeypatch.delenv("CLAUDE_PID", raising=False)
|
|
1439
|
+
neighbour, current = _two_sessions(tmp_path, neighbour_age=timedelta(minutes=1))
|
|
1440
|
+
_register_session(tmp_path, neighbour.stem, os.getpid())
|
|
1441
|
+
|
|
1442
|
+
prompt = _resume_prompt(current)
|
|
1443
|
+
|
|
1444
|
+
assert "finish the release notes" in prompt
|
|
1445
|
+
assert "kyoto" not in prompt
|
|
1446
|
+
|
|
1447
|
+
|
|
1448
|
+
def test_build_prompt_keeps_session_closed_a_moment_ago(
|
|
1449
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
1450
|
+
) -> None:
|
|
1451
|
+
# Closing a window and opening a new one is the main resume case: the closed session
|
|
1452
|
+
# is no longer registered, so its fresh log must still be handed over.
|
|
1453
|
+
monkeypatch.setenv("USAGE_LANG", "en")
|
|
1454
|
+
_sidecar(tmp_path, monkeypatch)
|
|
1455
|
+
_neighbour, current = _two_sessions(tmp_path, neighbour_age=timedelta(minutes=1))
|
|
1456
|
+
_register_session(tmp_path, "some-other-session", os.getpid())
|
|
1457
|
+
|
|
1458
|
+
assert "plan the kyoto trip app" in _resume_prompt(current)
|
|
1459
|
+
|
|
1460
|
+
|
|
1461
|
+
@pytest.mark.parametrize("via", ["env", "parent"])
|
|
1462
|
+
def test_build_prompt_keeps_session_this_window_just_cleared(
|
|
1463
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, via: str
|
|
1464
|
+
) -> None:
|
|
1465
|
+
# After /clear the registry may still list this window's pre-clear session.
|
|
1466
|
+
monkeypatch.setenv("USAGE_LANG", "en")
|
|
1467
|
+
_sidecar(tmp_path, monkeypatch)
|
|
1468
|
+
cleared, current = _two_sessions(tmp_path, neighbour_age=timedelta(minutes=1))
|
|
1469
|
+
_register_session(tmp_path, cleared.stem, 4242)
|
|
1470
|
+
monkeypatch.setattr(mod, "_pid_running", lambda pid: True)
|
|
1471
|
+
if via == "env":
|
|
1472
|
+
monkeypatch.setenv("CLAUDE_PID", "4242")
|
|
1473
|
+
else:
|
|
1474
|
+
monkeypatch.delenv("CLAUDE_PID", raising=False)
|
|
1475
|
+
monkeypatch.setattr(os, "getppid", lambda: 4242)
|
|
1476
|
+
|
|
1477
|
+
assert "plan the kyoto trip app" in _resume_prompt(current)
|
|
1478
|
+
|
|
1479
|
+
|
|
1480
|
+
def test_build_prompt_keeps_open_window_that_went_quiet(
|
|
1481
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
1482
|
+
) -> None:
|
|
1483
|
+
monkeypatch.setenv("USAGE_LANG", "en")
|
|
1484
|
+
_sidecar(tmp_path, monkeypatch)
|
|
1485
|
+
neighbour, current = _two_sessions(tmp_path, neighbour_age=timedelta(minutes=30))
|
|
1486
|
+
_register_session(tmp_path, neighbour.stem, os.getpid())
|
|
1487
|
+
|
|
1488
|
+
assert "plan the kyoto trip app" in _resume_prompt(current)
|
|
1489
|
+
|
|
1490
|
+
|
|
1491
|
+
def test_build_prompt_keeps_session_whose_window_crashed(
|
|
1492
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
1493
|
+
) -> None:
|
|
1494
|
+
monkeypatch.setenv("USAGE_LANG", "en")
|
|
1495
|
+
_sidecar(tmp_path, monkeypatch)
|
|
1496
|
+
neighbour, current = _two_sessions(tmp_path, neighbour_age=timedelta(minutes=1))
|
|
1497
|
+
_register_session(tmp_path, neighbour.stem, 4242)
|
|
1498
|
+
monkeypatch.setattr(mod, "_pid_running", lambda pid: False)
|
|
1499
|
+
|
|
1500
|
+
assert "plan the kyoto trip app" in _resume_prompt(current)
|
|
1501
|
+
|
|
1502
|
+
|
|
1503
|
+
def test_build_prompt_ignores_unreadable_session_registry(
|
|
1504
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
1505
|
+
) -> None:
|
|
1506
|
+
monkeypatch.setenv("USAGE_LANG", "en")
|
|
1507
|
+
_sidecar(tmp_path, monkeypatch)
|
|
1508
|
+
_neighbour, current = _two_sessions(tmp_path, neighbour_age=timedelta(minutes=1))
|
|
1509
|
+
sessions = tmp_path / "sessions"
|
|
1510
|
+
sessions.mkdir()
|
|
1511
|
+
(sessions / "1.json").write_text("{not json", encoding="utf-8")
|
|
1512
|
+
(sessions / "2.json").write_text("[]", encoding="utf-8")
|
|
1513
|
+
(sessions / "3.json").write_text(json.dumps({"pid": "x", "sessionId": 7}), encoding="utf-8")
|
|
1514
|
+
|
|
1515
|
+
assert "plan the kyoto trip app" in _resume_prompt(current)
|
|
1516
|
+
|
|
1517
|
+
|
|
1518
|
+
def test_pid_running_detects_exited_process() -> None:
|
|
1519
|
+
child = subprocess.Popen([sys.executable, "-c", ""])
|
|
1520
|
+
child.wait()
|
|
1521
|
+
|
|
1522
|
+
assert mod._pid_running(os.getpid()) is True
|
|
1523
|
+
if os.name != "nt":
|
|
1524
|
+
assert mod._pid_running(child.pid) is False
|
|
1525
|
+
|
|
1526
|
+
|
|
1527
|
+
@pytest.mark.parametrize("pid", [None, "123", True, 0, -1])
|
|
1528
|
+
def test_pid_running_rejects_invalid_pid(pid: object) -> None:
|
|
1529
|
+
assert mod._pid_running(pid) is False
|
|
1530
|
+
|
|
1531
|
+
|
|
1532
|
+
def test_pid_running_trusts_registry_on_windows(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
1533
|
+
def fail_kill(pid: int, sig: int) -> None:
|
|
1534
|
+
raise AssertionError("os.kill must not run on Windows")
|
|
1535
|
+
|
|
1536
|
+
monkeypatch.setattr(mod, "os", SimpleNamespace(name="nt", kill=fail_kill))
|
|
1537
|
+
|
|
1538
|
+
assert mod._pid_running(4242) is True
|
|
1539
|
+
|
|
1540
|
+
|
|
1541
|
+
@pytest.mark.parametrize(
|
|
1542
|
+
"value",
|
|
1543
|
+
[
|
|
1544
|
+
"<task-notification>\n<task-id>b1</task-id>\n<status>completed</status>"
|
|
1545
|
+
"\n<summary>Background agent finished the research</summary>\n</task-notification>",
|
|
1546
|
+
"<bash-input>rm -rf /tmp/frames</bash-input>",
|
|
1547
|
+
"<bash-stdout>(Bash completed with no output)</bash-stdout><bash-stderr></bash-stderr>",
|
|
1548
|
+
"<local-command-stdout>Set effort level to high for new sessions</local-command-stdout>",
|
|
1549
|
+
"<command-name>/clear</command-name>\n <command-message>clear</command-message>"
|
|
1550
|
+
"\n <command-args></command-args>",
|
|
1551
|
+
"<command-message>effort</command-message>\n<command-name>/effort</command-name>",
|
|
1552
|
+
],
|
|
1553
|
+
)
|
|
1554
|
+
def test_clean_request_drops_turns_claude_code_writes(value: str) -> None:
|
|
1555
|
+
assert mod._clean_request(value) == ""
|
|
1556
|
+
|
|
1557
|
+
|
|
1558
|
+
def test_clean_request_keeps_slash_command_arguments() -> None:
|
|
1559
|
+
value = (
|
|
1560
|
+
"<command-name>/loop</command-name>\n <command-message>loop</command-message>"
|
|
1561
|
+
"\n <command-args>5m check the deploy</command-args>"
|
|
1562
|
+
)
|
|
1563
|
+
|
|
1564
|
+
assert mod._clean_request(value) == "/loop 5m check the deploy"
|
|
1565
|
+
|
|
1566
|
+
|
|
1567
|
+
def test_clean_request_keeps_user_tagged_prompt() -> None:
|
|
1568
|
+
value = "<Task>split the settings panel</Task> <Constraints>keep the old API</Constraints>"
|
|
1569
|
+
|
|
1570
|
+
assert mod._clean_request(value) == value
|
|
@@ -1814,7 +1814,7 @@ def test_run_app_wires_pystray_and_pywebview(
|
|
|
1814
1814
|
fake_pystray = SimpleNamespace(Icon=FakeIcon, Menu=FakeMenu, MenuItem=FakeMenuItem)
|
|
1815
1815
|
fake_webview = SimpleNamespace(
|
|
1816
1816
|
create_window=create_window,
|
|
1817
|
-
start=lambda **kwargs: events.append(("start", kwargs["gui"])),
|
|
1817
|
+
start=lambda **kwargs: events.append(("start", kwargs["gui"], kwargs["storage_path"])),
|
|
1818
1818
|
)
|
|
1819
1819
|
monkeypatch.setitem(sys.modules, "pystray", fake_pystray)
|
|
1820
1820
|
monkeypatch.setitem(sys.modules, "webview", fake_webview)
|
|
@@ -1826,12 +1826,14 @@ def test_run_app_wires_pystray_and_pywebview(
|
|
|
1826
1826
|
|
|
1827
1827
|
wintray.run_app(mock=True, interval=60)
|
|
1828
1828
|
|
|
1829
|
+
assert wintray._WEBVIEW_STORAGE_DIR.name == "webview"
|
|
1830
|
+
assert wintray._WEBVIEW_STORAGE_DIR.parent == Path.home() / ".usage"
|
|
1829
1831
|
assert events == [
|
|
1830
1832
|
("window", "usage", True, "#eef2f7"),
|
|
1831
1833
|
"loaded_handler",
|
|
1832
1834
|
("icon", "usage"),
|
|
1833
1835
|
"run_detached",
|
|
1834
|
-
("start", "edgechromium"),
|
|
1836
|
+
("start", "edgechromium", str(wintray._WEBVIEW_STORAGE_DIR)),
|
|
1835
1837
|
]
|
|
1836
1838
|
|
|
1837
1839
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: usage-cli
|
|
3
|
-
Version: 0.33.
|
|
3
|
+
Version: 0.33.4
|
|
4
4
|
Summary: usage — 在 macOS menu bar 與 Windows 系統匣顯示 Claude Code、Codex、Antigravity 用量的小工具(也提供終端機 TUI)
|
|
5
5
|
License-Expression: AGPL-3.0-only
|
|
6
6
|
Requires-Python: >=3.13
|
|
@@ -138,7 +138,7 @@ Once set up, the bottom of the Claude Code window will show a status line like t
|
|
|
138
138
|
|
|
139
139
|
See your quota, other conversations, and background jobs without leaving Claude Code. Available on macOS and Windows.
|
|
140
140
|
|
|
141
|
-
<p align="center"><img src="docs/side-pane.png" alt="Claude Code side pane showing quotas, conversations, and background jobs" width="637"></p>
|
|
141
|
+
<p align="center"><img src="docs/side-pane.en.png" alt="Claude Code side pane showing quotas, conversations, and background jobs" width="637"></p>
|
|
142
142
|
|
|
143
143
|
**What you’ll see**
|
|
144
144
|
|
|
@@ -33,7 +33,7 @@ FALLBACK_RETRY_SECONDS = 600
|
|
|
33
33
|
MISSING_MODEL_REFRESH_SECONDS = FALLBACK_RETRY_SECONDS
|
|
34
34
|
USER_AGENT = "usage/0.9"
|
|
35
35
|
# The date when the hard-coded fallback table was last manually checked upstream.
|
|
36
|
-
FALLBACK_PRICING_AS_OF: str = "2026-
|
|
36
|
+
FALLBACK_PRICING_AS_OF: str = "2026-10-12"
|
|
37
37
|
# Anthropic's official cache write/read prices relative to input pricing. These
|
|
38
38
|
# are only fallbacks for missing upstream fields and may be wrong for other providers.
|
|
39
39
|
CACHE_WRITE_COST_MULTIPLIER = 1.25
|
|
@@ -484,7 +484,7 @@ def _fallback_pricing() -> PricingTable:
|
|
|
484
484
|
"input_cost_per_token": 2e-6,
|
|
485
485
|
"output_cost_per_token": 10e-6,
|
|
486
486
|
"cache_creation_input_token_cost": 2.5e-6,
|
|
487
|
-
"cache_read_input_token_cost": 0.
|
|
487
|
+
"cache_read_input_token_cost": 0.1e-6,
|
|
488
488
|
},
|
|
489
489
|
"claude-fable-5": {
|
|
490
490
|
"input_cost_per_token": 10e-6,
|
|
@@ -50,7 +50,7 @@ from datetime import datetime, timedelta, timezone
|
|
|
50
50
|
from pathlib import Path
|
|
51
51
|
from typing import Any, TypedDict, cast
|
|
52
52
|
|
|
53
|
-
__version__ = "1.
|
|
53
|
+
__version__ = "1.11"
|
|
54
54
|
|
|
55
55
|
|
|
56
56
|
class _HiddenConsoleKwargs(TypedDict, total=False):
|
|
@@ -100,6 +100,20 @@ _MAX_REQUESTS = 3
|
|
|
100
100
|
_MAX_REQUEST_CHARS = 280
|
|
101
101
|
_MIN_SUBSTANTIVE_CHARS = 7
|
|
102
102
|
_IMAGE_MARKER = re.compile(r"\[Image(?:\s+#[0-9]+)?\]", re.I)
|
|
103
|
+
# User turns Claude Code writes on the user's behalf: background-task notices, `!` shell
|
|
104
|
+
# runs, and local command output. Matched exactly so a user's own `<Task>` prompt survives.
|
|
105
|
+
_SYSTEM_TURN_PREFIXES = (
|
|
106
|
+
"<task-notification>",
|
|
107
|
+
"<bash-input>",
|
|
108
|
+
"<bash-stdout>",
|
|
109
|
+
"<local-command-stdout>",
|
|
110
|
+
)
|
|
111
|
+
_SLASH_COMMAND_PREFIXES = ("<command-name>", "<command-message>")
|
|
112
|
+
_SLASH_COMMAND_NAME = re.compile(r"<command-name>(.*?)</command-name>")
|
|
113
|
+
_SLASH_COMMAND_ARGS = re.compile(r"<command-args>(.*?)</command-args>")
|
|
114
|
+
# A session another window still has open and wrote to this recently is that window's
|
|
115
|
+
# work in progress, not this window's previous session.
|
|
116
|
+
_LIVE_SESSION_WINDOW = timedelta(minutes=10)
|
|
103
117
|
_DIAGNOSIS_MAX_AGE = timedelta(hours=48)
|
|
104
118
|
_DIAGNOSIS_REMINDER_COOLDOWN = timedelta(days=7)
|
|
105
119
|
_DIAGNOSIS_CAUSE_KEYS = (
|
|
@@ -520,7 +534,12 @@ def _other_jsonls_by_mtime(project_dir: Path, exclude: str) -> list[Path]:
|
|
|
520
534
|
|
|
521
535
|
Newest-first so the caller can skip a freshly-written but empty/corrupt log and
|
|
522
536
|
still fall back to the previous session that actually has progress to report.
|
|
537
|
+
Sessions another window still has open and is still writing are left out: with
|
|
538
|
+
several windows in one project, the newest log is usually a neighbour's live work.
|
|
539
|
+
A session closed a moment ago is no longer registered, so it stays a candidate.
|
|
523
540
|
"""
|
|
541
|
+
live_ids = _live_session_ids(project_dir.parent.parent / "sessions")
|
|
542
|
+
busy_since = (datetime.now() - _LIVE_SESSION_WINDOW).timestamp()
|
|
524
543
|
candidates: list[tuple[float, Path]] = []
|
|
525
544
|
for jsonl in project_dir.glob("*.jsonl"):
|
|
526
545
|
if jsonl.name == exclude:
|
|
@@ -529,11 +548,60 @@ def _other_jsonls_by_mtime(project_dir: Path, exclude: str) -> list[Path]:
|
|
|
529
548
|
mtime = jsonl.stat().st_mtime
|
|
530
549
|
except OSError:
|
|
531
550
|
continue
|
|
551
|
+
if mtime >= busy_since and jsonl.stem in live_ids:
|
|
552
|
+
continue
|
|
532
553
|
candidates.append((mtime, jsonl))
|
|
533
554
|
candidates.sort(key=lambda item: item[0], reverse=True)
|
|
534
555
|
return [path for _, path in candidates]
|
|
535
556
|
|
|
536
557
|
|
|
558
|
+
def _live_session_ids(sessions_dir: Path) -> set[str]:
|
|
559
|
+
"""Session IDs that open Claude Code windows list in ``~/.claude/sessions/<pid>.json``."""
|
|
560
|
+
ids: set[str] = set()
|
|
561
|
+
try:
|
|
562
|
+
entries = list(sessions_dir.glob("*.json"))
|
|
563
|
+
except OSError:
|
|
564
|
+
return ids
|
|
565
|
+
own_pids = _own_window_pids()
|
|
566
|
+
for entry in entries:
|
|
567
|
+
data = _read_json_file(entry)
|
|
568
|
+
if data is None:
|
|
569
|
+
continue
|
|
570
|
+
session_id = data.get("sessionId")
|
|
571
|
+
pid = data.get("pid")
|
|
572
|
+
if pid in own_pids:
|
|
573
|
+
continue
|
|
574
|
+
if isinstance(session_id, str) and session_id and _pid_running(pid):
|
|
575
|
+
ids.add(session_id)
|
|
576
|
+
return ids
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def _own_window_pids() -> set[int]:
|
|
580
|
+
"""This window's Claude Code process. After /clear its registry entry may still name
|
|
581
|
+
the session just cleared, which is the one to hand over."""
|
|
582
|
+
pids = {os.getppid()}
|
|
583
|
+
with contextlib.suppress(ValueError):
|
|
584
|
+
pids.add(int(os.environ.get("CLAUDE_PID", "")))
|
|
585
|
+
return pids
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def _pid_running(pid: object) -> bool:
|
|
589
|
+
"""False for a registry entry a crashed window left behind."""
|
|
590
|
+
if not isinstance(pid, int) or isinstance(pid, bool) or pid <= 0:
|
|
591
|
+
return False
|
|
592
|
+
# On Windows os.kill(pid, 0) sends CTRL_C_EVENT instead of probing, so the entry
|
|
593
|
+
# is trusted as is.
|
|
594
|
+
if os.name == "nt":
|
|
595
|
+
return True
|
|
596
|
+
try:
|
|
597
|
+
os.kill(pid, 0)
|
|
598
|
+
except PermissionError:
|
|
599
|
+
return True
|
|
600
|
+
except OSError:
|
|
601
|
+
return False
|
|
602
|
+
return True
|
|
603
|
+
|
|
604
|
+
|
|
537
605
|
def _parse_session(path: Path) -> tuple[datetime, str, list[str], list[str], list[str]] | None:
|
|
538
606
|
"""Return (last_active, recent_requests, commits, todos, edited_files) for the previous session.
|
|
539
607
|
|
|
@@ -625,6 +693,16 @@ def _clean_request(value: object) -> str:
|
|
|
625
693
|
if not isinstance(value, str):
|
|
626
694
|
return ""
|
|
627
695
|
text = " ".join(value.split())
|
|
696
|
+
if text.startswith(_SYSTEM_TURN_PREFIXES):
|
|
697
|
+
return ""
|
|
698
|
+
if text.startswith(_SLASH_COMMAND_PREFIXES):
|
|
699
|
+
# Keep a slash command only when it carries arguments: `/loop 5m check CI` is a
|
|
700
|
+
# request, a bare `/clear` is not.
|
|
701
|
+
name = _SLASH_COMMAND_NAME.search(text)
|
|
702
|
+
args = _SLASH_COMMAND_ARGS.search(text)
|
|
703
|
+
if name is None or args is None or not args.group(1).strip():
|
|
704
|
+
return ""
|
|
705
|
+
text = f"{name.group(1).strip()} {args.group(1).strip()}"
|
|
628
706
|
# Skip the interruption marker Claude Code writes as a user turn — it is noise,
|
|
629
707
|
# not a request.
|
|
630
708
|
if not text or text.startswith("[Request interrupted"):
|
|
@@ -18,6 +18,7 @@ from collections.abc import Callable
|
|
|
18
18
|
from dataclasses import dataclass
|
|
19
19
|
from datetime import date, datetime, timedelta
|
|
20
20
|
from enum import IntEnum
|
|
21
|
+
from pathlib import Path
|
|
21
22
|
from typing import TYPE_CHECKING, Any
|
|
22
23
|
from uuid import UUID
|
|
23
24
|
|
|
@@ -84,6 +85,8 @@ UPDATE_ALERT_BODY_LIMIT = 2000
|
|
|
84
85
|
# szTip is 128 WCHARs including the terminator; pystray raises ValueError past that.
|
|
85
86
|
TOOLTIP_MAX_LENGTH = 127
|
|
86
87
|
PANEL_WIDTH = 380
|
|
88
|
+
# Abnormal exits reuse one dir; pywebview private mode deletes on clean exit: subdir, never ~/.usage
|
|
89
|
+
_WEBVIEW_STORAGE_DIR = Path.home() / ".usage" / "webview"
|
|
87
90
|
_TOAST_AUMID = "com.lollapalooza.usage"
|
|
88
91
|
_TOAST_OPEN_PANEL_ACTION = "open_panel"
|
|
89
92
|
WINDOWS_PANELS = tuple((spec.id, spec.i18n_key, spec.html_filename) for spec in PANEL_SPECS)
|
|
@@ -2639,7 +2642,11 @@ def run_app(
|
|
|
2639
2642
|
|
|
2640
2643
|
start(_current_version(), snapshot, controller.language)
|
|
2641
2644
|
try:
|
|
2642
|
-
webview.start(
|
|
2645
|
+
webview.start(
|
|
2646
|
+
gui="edgechromium",
|
|
2647
|
+
debug=os.environ.get("USAGE_DEBUG") == "1",
|
|
2648
|
+
storage_path=str(_WEBVIEW_STORAGE_DIR),
|
|
2649
|
+
)
|
|
2643
2650
|
finally:
|
|
2644
2651
|
controller.stopping.set()
|
|
2645
2652
|
_release_single_instance_lock()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|