usage-cli 0.31.0__tar.gz → 0.31.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {usage_cli-0.31.0/usage_cli.egg-info → usage_cli-0.31.2}/PKG-INFO +6 -4
- {usage_cli-0.31.0 → usage_cli-0.31.2}/README.md +5 -3
- {usage_cli-0.31.0 → usage_cli-0.31.2}/i18n.json +5 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/panels/__init__.py +14 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/panels/dynamic_height.py +11 -2
- {usage_cli-0.31.0 → usage_cli-0.31.2}/pyproject.toml +1 -1
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_analyzer_pipeline.py +1 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_dynamic_height.py +13 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_panels.py +3 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_wintray.py +86 -1
- {usage_cli-0.31.0 → usage_cli-0.31.2/usage_cli.egg-info}/PKG-INFO +6 -4
- {usage_cli-0.31.0 → usage_cli-0.31.2}/wintray/app.py +51 -14
- {usage_cli-0.31.0 → usage_cli-0.31.2}/LICENSE +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/adapters/__init__.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/adapters/agy.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/adapters/claude.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/adapters/codex.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/adapters/grok.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/adapters/muse.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/adapters/rate_limits.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/adapters/registry.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/adapters/types.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/analyzer/__init__.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/analyzer/aggregator.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/analyzer/blocks.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/analyzer/diagnoser.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/analyzer/dispatch_ledger.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/analyzer/insights.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/analyzer/persona_loader.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/analyzer/reporter.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/analyzer/subscription.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/analyzer/usage_snapshot.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/doctor.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/fsevents_watch.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/i18n.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/installer/__init__.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/installer/login_item.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/installer/session_hooks.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/installer/setup_hook.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/installer/statusline_settings.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/loaders/__init__.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/loaders/agy_disk_cache.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/loaders/agy_loader.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/loaders/agy_quota_probe.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/loaders/cache_quarantine.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/loaders/claude_paths.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/loaders/codex_disk_cache.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/loaders/codex_events.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/loaders/codex_fork_replay.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/loaders/codex_loader.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/loaders/codex_paths.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/loaders/disk_cache_common.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/loaders/disk_cache_lifecycle.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/loaders/grok_loader.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/loaders/grok_quota_probe.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/loaders/history_disk_cache.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/loaders/history_loader.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/loaders/jsonl_limits.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/loaders/jsonl_utils.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/loaders/muse_loader.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/main.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/menubar/__init__.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/menubar/actions.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/menubar/agy.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/menubar/app.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/menubar/chrome.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/menubar/grok.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/menubar/menu.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/menubar/notify.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/menubar/popover.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/menubar/prefs.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/menubar/refresh.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/menubar/state.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/menubar/title.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/menubar/update.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/panels/base.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/panels/panel_scale.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/panels/panel_window.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/panels/panel_window_state.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/panels/payload.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/panels/web_panel.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/panels/window_drag.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/prefs.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/pricing.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/project_resolver.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/quota/__init__.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/quota/agy_window_keeper.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/quota/burn_rate.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/quota/codex_window_keeper.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/quota/usage_rate.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/quota/window_keeper.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/service_status.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/setup.cfg +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/setup_app.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_adapters_claude.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_adapters_codex.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_adapters_rate_limits.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_aggregator.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_agy_adapter.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_agy_disk_cache.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_agy_loader.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_agy_quota_probe.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_agy_window_keeper.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_analyzer_blocks.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_burn_rate.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_cache_quarantine.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_claude_paths.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_codex_consistency.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_codex_loader.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_codex_window_keeper.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_diagnoser.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_disk_cache_shards.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_dispatch_ledger.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_doctor.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_fsevents_watch.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_grok_loader.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_grok_quota_probe.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_history_loader.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_hook_scripts_are_standalone.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_html_report_snapshot.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_i18n_format_fallback.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_i18n_key_parity.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_i18n_packaged_path.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_insights.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_jsonl_utils.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_login_item.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_main.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_menubar.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_menubar_agy.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_menubar_grok.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_menubar_prefs.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_menubar_state.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_muse_loader.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_packaged_resources.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_packaging.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_panel_footer_layout.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_panel_scale.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_panel_window_state.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_panels_lazy_import.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_persona_loader.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_pricing.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_project_resolver.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_report_daily_chart.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_report_filter_js.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_reporter_build_data.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_service_status.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_session_resume.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_setup_app.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_setup_hook.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_setup_hook_coexistence.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_setup_hook_resume.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_setup_hook_terse.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_subprocess_utils.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_subscription.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_time_utils.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_tui.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_tui_sprite.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_ui_tables.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_update_checker.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_update_gate.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_update_release_notes.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_usage_cli.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_usage_client.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_usage_diagnosis_snapshot.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_usage_dir_sweeper.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_usage_lang.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_usage_logging.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_usage_notifications.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_usage_rate.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_usage_snapshot.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_usage_snapshot_replay.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_usage_statusline.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_usage_statusline_agy.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_usage_statusline_forwarder.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_usage_statusline_grok.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_usage_terse_mode.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_usage_terse_reminder.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_web_panel_payload.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_wheel_packaging.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_win_login_item.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_window_keeper.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tests/test_windows_manifest.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tui/__init__.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tui/app.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/tui/sprite.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/ui/__init__.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/ui/html_report.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/ui/report_charts.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/ui/report_daily_chart.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/ui/report_filter.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/ui/report_ledger.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/ui/report_scripts.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/ui/report_styles.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/ui/tables.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/updates/__init__.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/updates/checker.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/updates/gate.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/updates/release_notes.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/usage_cli.egg-info/SOURCES.txt +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/usage_cli.egg-info/dependency_links.txt +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/usage_cli.egg-info/entry_points.txt +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/usage_cli.egg-info/requires.txt +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/usage_cli.egg-info/top_level.txt +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/usage_cli.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/usage_client.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/usage_common/__init__.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/usage_common/subprocess_utils.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/usage_common/time_utils.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/usage_common/usage_dir_sweeper.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/usage_common/usage_lang.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/usage_common/usage_logging.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/usage_diagnosis_snapshot.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/usage_notifications.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/usage_session_resume.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/usage_statusline.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/usage_statusline_agy.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/usage_statusline_forwarder.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/usage_statusline_grok.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/usage_terse_mode.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/usage_terse_reminder.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/wintray/__init__.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/wintray/login_item.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/wintray/menu.py +0 -0
- {usage_cli-0.31.0 → usage_cli-0.31.2}/wintray/watch.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: usage-cli
|
|
3
|
-
Version: 0.31.
|
|
3
|
+
Version: 0.31.2
|
|
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
|
|
@@ -87,7 +87,7 @@ It lands in your Applications folder automatically. Open it once; if macOS 15 or
|
|
|
87
87
|
|
|
88
88
|
### Experience & Customization
|
|
89
89
|
|
|
90
|
-
- **
|
|
90
|
+
- **16 Visual Themes:** Switch between panel styles including Default, Matrix, Windows 95, Vintage Newspaper, Cloud Observation, Midnight Aquarium, Prism Arcade, Black Hole, World Cup 2026, Lepidoptera, Migration, Stained Glass, Origami, Sketchbook, Heart Monitor, and Catppuccin (official palette, all four flavors).
|
|
91
91
|
- **Place the Panel Anywhere:** The panel is no longer pinned under the menu bar icon. Drag it from any empty spot to wherever you want it, and it reopens there next time. It stays put when another app takes focus — a second click on the menu bar icon, or Escape, closes it.
|
|
92
92
|
- **Drag to Reorder:** Grab any quota card and drag it up or down to swap the order — the arrangement is shared across every theme with quota cards (all except World Cup 2026) and survives restarts.
|
|
93
93
|
- **Automatic Localization:** UI text is available in Traditional Chinese, Simplified Chinese, English, Japanese, and Korean, automatically matching your system settings.
|
|
@@ -147,7 +147,7 @@ Once set up, the bottom of the Claude Code window will show a status line like t
|
|
|
147
147
|
|
|
148
148
|
Windows has the full core experience: the system-tray UI, Claude Code status-line hook, and Codex history parsing all work natively. Download `usage-windows.zip` from the [latest GitHub Release](https://github.com/aqua5230/usage/releases/latest), unzip it, then run `usage.exe`—no installer is needed. If SmartScreen shows **Windows protected your PC** on first launch, click **More info** → **Run anyway**. The tray UI requires Microsoft Edge WebView2 Runtime, which is normally included with Windows 10 and 11.
|
|
149
149
|
|
|
150
|
-
The system-tray icon updates with your Claude quota percentage; its tooltip summarizes the Claude and Codex windows. Left-click opens the same
|
|
150
|
+
The system-tray icon updates with your Claude quota percentage; its tooltip summarizes the Claude and Codex windows. Left-click opens the same 16 quota themes available on macOS (Default plus the other fifteen) in WebView2. Right-click provides Reset Panel Position and Quit; panel switching, refresh, launch at login, and update checks are in the panel menu.
|
|
151
151
|
|
|
152
152
|
Windows differences: the panel opens at the bottom-right of the working area rather than next to the tray icon; update prompts use a system Yes/No dialog.
|
|
153
153
|
|
|
@@ -164,7 +164,7 @@ Privacy policy: this program will not transfer any information to other networke
|
|
|
164
164
|
|
|
165
165
|
## Theme Gallery
|
|
166
166
|
|
|
167
|
-
Switch between **
|
|
167
|
+
Switch between **16 visual themes** directly from the UI:
|
|
168
168
|
|
|
169
169
|
<p align="center">
|
|
170
170
|
<img src="docs/classic.en.png" width="32%" alt="Classic theme" />
|
|
@@ -180,6 +180,8 @@ Switch between **15 visual themes** directly from the UI:
|
|
|
180
180
|
<img src="docs/lepidoptera.en.png" width="32%" alt="Lepidoptera theme" />
|
|
181
181
|
<img src="docs/migration.en.png" width="32%" alt="Migration theme" />
|
|
182
182
|
<img src="docs/catppuccin.en.png" width="32%" alt="Catppuccin theme" />
|
|
183
|
+
<img src="docs/sketchbook.en.png" width="32%" alt="Sketchbook theme" />
|
|
184
|
+
<img src="docs/heart_monitor.en.png" width="32%" alt="Heart Monitor theme" />
|
|
183
185
|
</p>
|
|
184
186
|
|
|
185
187
|
## Troubleshooting
|
|
@@ -67,7 +67,7 @@ It lands in your Applications folder automatically. Open it once; if macOS 15 or
|
|
|
67
67
|
|
|
68
68
|
### Experience & Customization
|
|
69
69
|
|
|
70
|
-
- **
|
|
70
|
+
- **16 Visual Themes:** Switch between panel styles including Default, Matrix, Windows 95, Vintage Newspaper, Cloud Observation, Midnight Aquarium, Prism Arcade, Black Hole, World Cup 2026, Lepidoptera, Migration, Stained Glass, Origami, Sketchbook, Heart Monitor, and Catppuccin (official palette, all four flavors).
|
|
71
71
|
- **Place the Panel Anywhere:** The panel is no longer pinned under the menu bar icon. Drag it from any empty spot to wherever you want it, and it reopens there next time. It stays put when another app takes focus — a second click on the menu bar icon, or Escape, closes it.
|
|
72
72
|
- **Drag to Reorder:** Grab any quota card and drag it up or down to swap the order — the arrangement is shared across every theme with quota cards (all except World Cup 2026) and survives restarts.
|
|
73
73
|
- **Automatic Localization:** UI text is available in Traditional Chinese, Simplified Chinese, English, Japanese, and Korean, automatically matching your system settings.
|
|
@@ -127,7 +127,7 @@ Once set up, the bottom of the Claude Code window will show a status line like t
|
|
|
127
127
|
|
|
128
128
|
Windows has the full core experience: the system-tray UI, Claude Code status-line hook, and Codex history parsing all work natively. Download `usage-windows.zip` from the [latest GitHub Release](https://github.com/aqua5230/usage/releases/latest), unzip it, then run `usage.exe`—no installer is needed. If SmartScreen shows **Windows protected your PC** on first launch, click **More info** → **Run anyway**. The tray UI requires Microsoft Edge WebView2 Runtime, which is normally included with Windows 10 and 11.
|
|
129
129
|
|
|
130
|
-
The system-tray icon updates with your Claude quota percentage; its tooltip summarizes the Claude and Codex windows. Left-click opens the same
|
|
130
|
+
The system-tray icon updates with your Claude quota percentage; its tooltip summarizes the Claude and Codex windows. Left-click opens the same 16 quota themes available on macOS (Default plus the other fifteen) in WebView2. Right-click provides Reset Panel Position and Quit; panel switching, refresh, launch at login, and update checks are in the panel menu.
|
|
131
131
|
|
|
132
132
|
Windows differences: the panel opens at the bottom-right of the working area rather than next to the tray icon; update prompts use a system Yes/No dialog.
|
|
133
133
|
|
|
@@ -144,7 +144,7 @@ Privacy policy: this program will not transfer any information to other networke
|
|
|
144
144
|
|
|
145
145
|
## Theme Gallery
|
|
146
146
|
|
|
147
|
-
Switch between **
|
|
147
|
+
Switch between **16 visual themes** directly from the UI:
|
|
148
148
|
|
|
149
149
|
<p align="center">
|
|
150
150
|
<img src="docs/classic.en.png" width="32%" alt="Classic theme" />
|
|
@@ -160,6 +160,8 @@ Switch between **15 visual themes** directly from the UI:
|
|
|
160
160
|
<img src="docs/lepidoptera.en.png" width="32%" alt="Lepidoptera theme" />
|
|
161
161
|
<img src="docs/migration.en.png" width="32%" alt="Migration theme" />
|
|
162
162
|
<img src="docs/catppuccin.en.png" width="32%" alt="Catppuccin theme" />
|
|
163
|
+
<img src="docs/sketchbook.en.png" width="32%" alt="Sketchbook theme" />
|
|
164
|
+
<img src="docs/heart_monitor.en.png" width="32%" alt="Heart Monitor theme" />
|
|
163
165
|
</p>
|
|
164
166
|
|
|
165
167
|
## Troubleshooting
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"panel_stained_glass": "彩繪玻璃",
|
|
12
12
|
"panel_migration": "候鳥遷徙",
|
|
13
13
|
"panel_sketchbook": "手繪筆記",
|
|
14
|
+
"panel_heart_monitor": "心電圖",
|
|
14
15
|
"panel_origami": "摺紙",
|
|
15
16
|
"panel_catppuccin": "Catppuccin",
|
|
16
17
|
"panel_black_hole": "黑洞視界",
|
|
@@ -437,6 +438,7 @@
|
|
|
437
438
|
"panel_stained_glass": "Stained Glass",
|
|
438
439
|
"panel_migration": "Migration",
|
|
439
440
|
"panel_sketchbook": "Sketchbook",
|
|
441
|
+
"panel_heart_monitor": "Heart Monitor",
|
|
440
442
|
"panel_origami": "Origami",
|
|
441
443
|
"panel_catppuccin": "Catppuccin",
|
|
442
444
|
"panel_black_hole": "Black Hole",
|
|
@@ -863,6 +865,7 @@
|
|
|
863
865
|
"panel_stained_glass": "彩绘玻璃",
|
|
864
866
|
"panel_migration": "候鸟迁徙",
|
|
865
867
|
"panel_sketchbook": "手绘笔记",
|
|
868
|
+
"panel_heart_monitor": "心电图",
|
|
866
869
|
"panel_origami": "折纸",
|
|
867
870
|
"panel_catppuccin": "Catppuccin",
|
|
868
871
|
"panel_black_hole": "黑洞视界",
|
|
@@ -1289,6 +1292,7 @@
|
|
|
1289
1292
|
"panel_stained_glass": "ステンドグラス",
|
|
1290
1293
|
"panel_migration": "渡り鳥",
|
|
1291
1294
|
"panel_sketchbook": "手描きノート",
|
|
1295
|
+
"panel_heart_monitor": "心電図モニター",
|
|
1292
1296
|
"panel_origami": "折り紙",
|
|
1293
1297
|
"panel_catppuccin": "Catppuccin",
|
|
1294
1298
|
"panel_black_hole": "ブラックホール",
|
|
@@ -1715,6 +1719,7 @@
|
|
|
1715
1719
|
"panel_stained_glass": "스테인드글라스",
|
|
1716
1720
|
"panel_migration": "철새 이동",
|
|
1717
1721
|
"panel_sketchbook": "손그림 노트",
|
|
1722
|
+
"panel_heart_monitor": "심전도 모니터",
|
|
1718
1723
|
"panel_origami": "종이접기",
|
|
1719
1724
|
"panel_catppuccin": "Catppuccin",
|
|
1720
1725
|
"panel_black_hole": "블랙홀",
|
|
@@ -144,6 +144,20 @@ def all_panels() -> tuple[Panel, ...]:
|
|
|
144
144
|
status_wrap_extra_height=30.0,
|
|
145
145
|
service_alert_height=32.0,
|
|
146
146
|
),
|
|
147
|
+
# Use classic's measured values as initial estimates for the same DOM;
|
|
148
|
+
# dynamic_height.py measures heart_monitor's actual rendered height.
|
|
149
|
+
HTMLPanel(
|
|
150
|
+
"heart_monitor",
|
|
151
|
+
"panel_heart_monitor",
|
|
152
|
+
"heart_monitor.html",
|
|
153
|
+
height=1132.0,
|
|
154
|
+
claude_card_height=192.0,
|
|
155
|
+
codex_card_height=192.0,
|
|
156
|
+
agy_card_height=192.0,
|
|
157
|
+
grok_card_height=128.0,
|
|
158
|
+
status_wrap_extra_height=30.0,
|
|
159
|
+
service_alert_height=32.0,
|
|
160
|
+
),
|
|
147
161
|
# Reuse classic's measured values because the DOM structure is identical;
|
|
148
162
|
# remeasure if a future render shows clipping.
|
|
149
163
|
HTMLPanel(
|
|
@@ -15,6 +15,7 @@ CONTENT_HEIGHT_SCRIPT = """
|
|
|
15
15
|
if (typeof applyState !== "function") return;
|
|
16
16
|
var scheduled = false;
|
|
17
17
|
var lastPostedHeight = null;
|
|
18
|
+
var lastPostedDpr = null;
|
|
18
19
|
var fittingHeight = 0;
|
|
19
20
|
var contentChanged = false;
|
|
20
21
|
var lastMeasuredHeight = null;
|
|
@@ -132,10 +133,17 @@ CONTENT_HEIGHT_SCRIPT = """
|
|
|
132
133
|
}
|
|
133
134
|
var bridge = window.webkit && window.webkit.messageHandlers
|
|
134
135
|
&& window.webkit.messageHandlers.usage;
|
|
135
|
-
|
|
136
|
+
// The native side sizes its window from this height, so it also needs the
|
|
137
|
+
// ratio the page really renders at. On Windows that is the display scale
|
|
138
|
+
// times the "Text size" accessibility setting (125% x 109% = 1.3625), which
|
|
139
|
+
// GetDpiForWindow does not include.
|
|
140
|
+
var dpr = window.devicePixelRatio;
|
|
141
|
+
if (Number.isFinite(height) && height > 0
|
|
142
|
+
&& (height !== lastPostedHeight || dpr !== lastPostedDpr) && bridge
|
|
136
143
|
&& typeof bridge.postMessage === "function") {
|
|
137
144
|
lastPostedHeight = height;
|
|
138
|
-
|
|
145
|
+
lastPostedDpr = dpr;
|
|
146
|
+
bridge.postMessage(JSON.stringify({ action: "content_height", height: height, dpr: dpr }));
|
|
139
147
|
}
|
|
140
148
|
}
|
|
141
149
|
function requestContentHeight() {
|
|
@@ -151,6 +159,7 @@ CONTENT_HEIGHT_SCRIPT = """
|
|
|
151
159
|
window.usageRequestContentHeight = requestContentHeight;
|
|
152
160
|
window.usageInvalidateContentHeight = function() {
|
|
153
161
|
lastPostedHeight = null;
|
|
162
|
+
lastPostedDpr = null;
|
|
154
163
|
fittingHeight = 0;
|
|
155
164
|
requestContentHeight();
|
|
156
165
|
};
|
|
@@ -59,6 +59,19 @@ def test_script_wraps_state_application_and_measures_without_height_constraints(
|
|
|
59
59
|
assert "body.style.height = bodyHeight" in CONTENT_HEIGHT_SCRIPT
|
|
60
60
|
|
|
61
61
|
|
|
62
|
+
def test_script_reports_the_rendered_device_pixel_ratio_with_the_height() -> None:
|
|
63
|
+
# The window is sized from the reported height, so the native side needs the
|
|
64
|
+
# ratio the page really renders at: on Windows it includes the "Text size"
|
|
65
|
+
# setting that GetDpiForWindow leaves out.
|
|
66
|
+
assert "var dpr = window.devicePixelRatio;" in CONTENT_HEIGHT_SCRIPT
|
|
67
|
+
assert 'action: "content_height", height: height, dpr: dpr' in CONTENT_HEIGHT_SCRIPT
|
|
68
|
+
# A ratio change alone is worth reporting again, and invalidating the
|
|
69
|
+
# height must not leave a stale ratio behind.
|
|
70
|
+
assert "dpr !== lastPostedDpr" in CONTENT_HEIGHT_SCRIPT
|
|
71
|
+
assert "lastPostedDpr = dpr;" in CONTENT_HEIGHT_SCRIPT
|
|
72
|
+
assert "lastPostedDpr = null;" in CONTENT_HEIGHT_SCRIPT
|
|
73
|
+
|
|
74
|
+
|
|
62
75
|
def test_world_cup_declares_its_pitch_height_floor() -> None:
|
|
63
76
|
html = (PANEL_ASSETS / "world_cup.html").read_text(encoding="utf-8")
|
|
64
77
|
|
|
@@ -33,6 +33,7 @@ CARD_PANEL_FILENAMES = (
|
|
|
33
33
|
"matrix.html",
|
|
34
34
|
"migration.html",
|
|
35
35
|
"sketchbook.html",
|
|
36
|
+
"heart_monitor.html",
|
|
36
37
|
"newspaper.html",
|
|
37
38
|
"origami.html",
|
|
38
39
|
"prism_arcade.html",
|
|
@@ -70,6 +71,7 @@ def test_registered_panel_ids_are_unique() -> None:
|
|
|
70
71
|
"stained_glass",
|
|
71
72
|
"migration",
|
|
72
73
|
"sketchbook",
|
|
74
|
+
"heart_monitor",
|
|
73
75
|
"origami",
|
|
74
76
|
"black_hole",
|
|
75
77
|
"lepidoptera",
|
|
@@ -93,6 +95,7 @@ def test_registered_panel_i18n_keys() -> None:
|
|
|
93
95
|
"panel_stained_glass",
|
|
94
96
|
"panel_migration",
|
|
95
97
|
"panel_sketchbook",
|
|
98
|
+
"panel_heart_monitor",
|
|
96
99
|
"panel_origami",
|
|
97
100
|
"panel_black_hole",
|
|
98
101
|
"panel_lepidoptera",
|
|
@@ -915,8 +915,92 @@ def test_short_high_dpi_panel_uses_native_work_area_and_keeps_footer_visible(
|
|
|
915
915
|
assert round((geometry["y"] + geometry["height"]) * 1.25) <= 1032
|
|
916
916
|
|
|
917
917
|
|
|
918
|
+
def test_content_height_message_records_the_page_render_scale(
|
|
919
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
920
|
+
) -> None:
|
|
921
|
+
controller = wintray._WindowsTrayController(mock=True, interval=60)
|
|
922
|
+
monkeypatch.setattr(controller, "_window_dpi_scale", lambda: 1.25)
|
|
923
|
+
monkeypatch.setattr(controller, "_working_area", lambda: (0, 0, 1000, 800))
|
|
924
|
+
|
|
925
|
+
assert controller._content_scale() == 1.25
|
|
926
|
+
|
|
927
|
+
controller.handle_panel_message(
|
|
928
|
+
json.dumps({"action": "content_height", "height": 700, "dpr": 1.3625})
|
|
929
|
+
)
|
|
930
|
+
assert controller._content_scale() == 1.3625
|
|
931
|
+
|
|
932
|
+
# A malformed or absurd ratio from the page never replaces a good one.
|
|
933
|
+
for invalid in (True, "1.5", 0, -1, 0.1, 99, None, float("nan")):
|
|
934
|
+
controller.handle_panel_message(
|
|
935
|
+
json.dumps({"action": "content_height", "height": 701, "dpr": invalid})
|
|
936
|
+
)
|
|
937
|
+
assert controller._content_scale() == 1.3625
|
|
938
|
+
|
|
939
|
+
|
|
940
|
+
def test_text_size_scaling_sizes_the_window_by_the_rendered_scale(
|
|
941
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
942
|
+
) -> None:
|
|
943
|
+
# Windows "Text size" at 109% on a 125% display: WebView2 renders 1.3625
|
|
944
|
+
# physical pixels per CSS pixel while GetDpiForWindow still reports 1.25.
|
|
945
|
+
# Sized with 1.25 alone, the window is ~8% shorter than its content and
|
|
946
|
+
# shows a scrollbar even though the screen has room to spare.
|
|
947
|
+
monkeypatch.setattr(prefs, "PREFERENCES_FILE", tmp_path / "usage-preferences.json")
|
|
948
|
+
geometry: dict[str, int] = {}
|
|
949
|
+
controller = wintray._WindowsTrayController(mock=True, interval=60)
|
|
950
|
+
controller.active_panel_id = "newspaper"
|
|
951
|
+
controller.window = SimpleNamespace(
|
|
952
|
+
x=0,
|
|
953
|
+
y=0,
|
|
954
|
+
evaluate_js=lambda _code: True,
|
|
955
|
+
resize=lambda width, height: geometry.update(width=width, height=height),
|
|
956
|
+
move=lambda x, y: geometry.update(x=x, y=y),
|
|
957
|
+
)
|
|
958
|
+
monkeypatch.setattr(controller, "_window_dpi_scale", lambda: 1.25)
|
|
959
|
+
# A 1920x1020 physical work area, expressed in CSS pixels at 1.3625.
|
|
960
|
+
monkeypatch.setattr(controller, "_native_work_area_for_point", lambda _point: (0, 0, 1409, 749))
|
|
961
|
+
controller.handle_panel_message(
|
|
962
|
+
json.dumps({"action": "content_height", "height": 600, "dpr": 1.3625})
|
|
963
|
+
)
|
|
964
|
+
|
|
965
|
+
controller._place_window()
|
|
966
|
+
|
|
967
|
+
# pywebview multiplies what it is given by the display scale on its own.
|
|
968
|
+
assert abs(geometry["height"] * 1.25 - 600 * 1.3625) <= 1
|
|
969
|
+
assert abs(geometry["width"] * 1.25 - wintray.PANEL_WIDTH * 1.3625) <= 1
|
|
970
|
+
|
|
971
|
+
|
|
972
|
+
def test_pywebview_geometry_is_unchanged_without_a_page_render_scale(
|
|
973
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
974
|
+
) -> None:
|
|
975
|
+
monkeypatch.setattr(prefs, "PREFERENCES_FILE", tmp_path / "usage-preferences.json")
|
|
976
|
+
geometry: dict[str, int] = {}
|
|
977
|
+
controller = wintray._WindowsTrayController(mock=True, interval=60)
|
|
978
|
+
controller.active_panel_id = "newspaper"
|
|
979
|
+
controller.window = SimpleNamespace(
|
|
980
|
+
x=0,
|
|
981
|
+
y=0,
|
|
982
|
+
evaluate_js=lambda _code: True,
|
|
983
|
+
resize=lambda width, height: geometry.update(width=width, height=height),
|
|
984
|
+
move=lambda x, y: geometry.update(x=x, y=y),
|
|
985
|
+
)
|
|
986
|
+
monkeypatch.setattr(controller, "_window_dpi_scale", lambda: 1.25)
|
|
987
|
+
monkeypatch.setattr(controller, "_native_work_area_for_point", lambda _point: (0, 0, 1536, 816))
|
|
988
|
+
controller.handle_panel_message(json.dumps({"action": "content_height", "height": 600}))
|
|
989
|
+
|
|
990
|
+
controller._place_window()
|
|
991
|
+
|
|
992
|
+
assert geometry["height"] == 600
|
|
993
|
+
assert geometry["width"] == wintray.PANEL_WIDTH
|
|
994
|
+
|
|
995
|
+
|
|
996
|
+
@pytest.mark.parametrize(
|
|
997
|
+
("page_scale", "expected"),
|
|
998
|
+
[(None, (0, 0, 1536, 826)), (1.3625, (0, 0, 1409, 757))],
|
|
999
|
+
)
|
|
918
1000
|
def test_native_work_area_converts_physical_pixels_to_logical(
|
|
919
1001
|
monkeypatch: pytest.MonkeyPatch,
|
|
1002
|
+
page_scale: float | None,
|
|
1003
|
+
expected: tuple[int, int, int, int],
|
|
920
1004
|
) -> None:
|
|
921
1005
|
class FakeFunction:
|
|
922
1006
|
def __init__(self, result: object) -> None:
|
|
@@ -945,8 +1029,9 @@ def test_native_work_area_converts_physical_pixels_to_logical(
|
|
|
945
1029
|
controller = wintray._WindowsTrayController(mock=True, interval=60)
|
|
946
1030
|
controller.window = SimpleNamespace(native=SimpleNamespace(Handle=123))
|
|
947
1031
|
monkeypatch.setattr(controller, "_window_dpi_scale", lambda: 1.25)
|
|
1032
|
+
controller._apply_page_scale(page_scale)
|
|
948
1033
|
|
|
949
|
-
assert _REAL_NATIVE_WORK_AREA(controller, (1169, 2)) ==
|
|
1034
|
+
assert _REAL_NATIVE_WORK_AREA(controller, (1169, 2)) == expected
|
|
950
1035
|
|
|
951
1036
|
|
|
952
1037
|
def test_panel_rechecks_native_work_area_before_final_move(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: usage-cli
|
|
3
|
-
Version: 0.31.
|
|
3
|
+
Version: 0.31.2
|
|
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
|
|
@@ -87,7 +87,7 @@ It lands in your Applications folder automatically. Open it once; if macOS 15 or
|
|
|
87
87
|
|
|
88
88
|
### Experience & Customization
|
|
89
89
|
|
|
90
|
-
- **
|
|
90
|
+
- **16 Visual Themes:** Switch between panel styles including Default, Matrix, Windows 95, Vintage Newspaper, Cloud Observation, Midnight Aquarium, Prism Arcade, Black Hole, World Cup 2026, Lepidoptera, Migration, Stained Glass, Origami, Sketchbook, Heart Monitor, and Catppuccin (official palette, all four flavors).
|
|
91
91
|
- **Place the Panel Anywhere:** The panel is no longer pinned under the menu bar icon. Drag it from any empty spot to wherever you want it, and it reopens there next time. It stays put when another app takes focus — a second click on the menu bar icon, or Escape, closes it.
|
|
92
92
|
- **Drag to Reorder:** Grab any quota card and drag it up or down to swap the order — the arrangement is shared across every theme with quota cards (all except World Cup 2026) and survives restarts.
|
|
93
93
|
- **Automatic Localization:** UI text is available in Traditional Chinese, Simplified Chinese, English, Japanese, and Korean, automatically matching your system settings.
|
|
@@ -147,7 +147,7 @@ Once set up, the bottom of the Claude Code window will show a status line like t
|
|
|
147
147
|
|
|
148
148
|
Windows has the full core experience: the system-tray UI, Claude Code status-line hook, and Codex history parsing all work natively. Download `usage-windows.zip` from the [latest GitHub Release](https://github.com/aqua5230/usage/releases/latest), unzip it, then run `usage.exe`—no installer is needed. If SmartScreen shows **Windows protected your PC** on first launch, click **More info** → **Run anyway**. The tray UI requires Microsoft Edge WebView2 Runtime, which is normally included with Windows 10 and 11.
|
|
149
149
|
|
|
150
|
-
The system-tray icon updates with your Claude quota percentage; its tooltip summarizes the Claude and Codex windows. Left-click opens the same
|
|
150
|
+
The system-tray icon updates with your Claude quota percentage; its tooltip summarizes the Claude and Codex windows. Left-click opens the same 16 quota themes available on macOS (Default plus the other fifteen) in WebView2. Right-click provides Reset Panel Position and Quit; panel switching, refresh, launch at login, and update checks are in the panel menu.
|
|
151
151
|
|
|
152
152
|
Windows differences: the panel opens at the bottom-right of the working area rather than next to the tray icon; update prompts use a system Yes/No dialog.
|
|
153
153
|
|
|
@@ -164,7 +164,7 @@ Privacy policy: this program will not transfer any information to other networke
|
|
|
164
164
|
|
|
165
165
|
## Theme Gallery
|
|
166
166
|
|
|
167
|
-
Switch between **
|
|
167
|
+
Switch between **16 visual themes** directly from the UI:
|
|
168
168
|
|
|
169
169
|
<p align="center">
|
|
170
170
|
<img src="docs/classic.en.png" width="32%" alt="Classic theme" />
|
|
@@ -180,6 +180,8 @@ Switch between **15 visual themes** directly from the UI:
|
|
|
180
180
|
<img src="docs/lepidoptera.en.png" width="32%" alt="Lepidoptera theme" />
|
|
181
181
|
<img src="docs/migration.en.png" width="32%" alt="Migration theme" />
|
|
182
182
|
<img src="docs/catppuccin.en.png" width="32%" alt="Catppuccin theme" />
|
|
183
|
+
<img src="docs/sketchbook.en.png" width="32%" alt="Sketchbook theme" />
|
|
184
|
+
<img src="docs/heart_monitor.en.png" width="32%" alt="Heart Monitor theme" />
|
|
183
185
|
</p>
|
|
184
186
|
|
|
185
187
|
## Troubleshooting
|
|
@@ -8,6 +8,7 @@ import ctypes
|
|
|
8
8
|
import importlib
|
|
9
9
|
import json
|
|
10
10
|
import logging
|
|
11
|
+
import math
|
|
11
12
|
import os
|
|
12
13
|
import threading
|
|
13
14
|
import time
|
|
@@ -97,6 +98,7 @@ WINDOWS_PANELS = (
|
|
|
97
98
|
("stained_glass", "panel_stained_glass", "stained_glass.html"),
|
|
98
99
|
("migration", "panel_migration", "migration.html"),
|
|
99
100
|
("sketchbook", "panel_sketchbook", "sketchbook.html"),
|
|
101
|
+
("heart_monitor", "panel_heart_monitor", "heart_monitor.html"),
|
|
100
102
|
("origami", "panel_origami", "origami.html"),
|
|
101
103
|
("catppuccin", "panel_catppuccin", "catppuccin.html"),
|
|
102
104
|
)
|
|
@@ -119,6 +121,7 @@ PANEL_HEIGHTS = {
|
|
|
119
121
|
"stained_glass": 1132,
|
|
120
122
|
"migration": 1132,
|
|
121
123
|
"sketchbook": 1132,
|
|
124
|
+
"heart_monitor": 1132,
|
|
122
125
|
"origami": 1132,
|
|
123
126
|
"catppuccin": 1166,
|
|
124
127
|
}
|
|
@@ -755,6 +758,7 @@ class _WindowsTrayController:
|
|
|
755
758
|
self._history_scan: menubar_state.HistorySourceScan | None = None
|
|
756
759
|
self._history_scan_at: float | None = None
|
|
757
760
|
self._content_height: int | None = None
|
|
761
|
+
self._page_scale: float | None = None
|
|
758
762
|
self._window_mutations: deque[Callable[[], None]] = deque()
|
|
759
763
|
self._window_mutation_lock = threading.Lock()
|
|
760
764
|
self._window_mutation_scheduled = False
|
|
@@ -930,7 +934,7 @@ class _WindowsTrayController:
|
|
|
930
934
|
),
|
|
931
935
|
result[0] if result else None,
|
|
932
936
|
)
|
|
933
|
-
window_scale = self.
|
|
937
|
+
window_scale = self._content_scale()
|
|
934
938
|
if (
|
|
935
939
|
primary is not None
|
|
936
940
|
and primary[2] == 1.0
|
|
@@ -976,10 +980,37 @@ class _WindowsTrayController:
|
|
|
976
980
|
return None
|
|
977
981
|
return dpi / 96.0 if dpi > 0 else None
|
|
978
982
|
|
|
983
|
+
def _content_scale(self) -> float | None:
|
|
984
|
+
"""Physical pixels per CSS pixel that the panel page is really rendered at.
|
|
985
|
+
|
|
986
|
+
Windows' "Text size" accessibility setting multiplies WebView2's device
|
|
987
|
+
pixel ratio on top of the display scale (125% x 109% = 1.3625), and
|
|
988
|
+
GetDpiForWindow does not include it. A window sized with the display
|
|
989
|
+
scale alone ends up shorter than its content and shows a scrollbar, so
|
|
990
|
+
every CSS <-> physical conversion uses the ratio the page reports and
|
|
991
|
+
falls back to the display scale until it has reported one.
|
|
992
|
+
"""
|
|
993
|
+
return self._page_scale or self._window_dpi_scale()
|
|
994
|
+
|
|
995
|
+
def _apply_page_scale(self, value: object) -> None:
|
|
996
|
+
if isinstance(value, bool) or not isinstance(value, int | float):
|
|
997
|
+
return
|
|
998
|
+
scale = float(value)
|
|
999
|
+
if math.isfinite(scale) and 0.5 <= scale <= 8.0:
|
|
1000
|
+
self._page_scale = scale
|
|
1001
|
+
|
|
1002
|
+
def _pywebview_scale_ratio(self) -> float:
|
|
1003
|
+
"""Correction for pywebview's resize()/move(), which apply the display scale."""
|
|
1004
|
+
content = self._content_scale()
|
|
1005
|
+
display = self._window_dpi_scale()
|
|
1006
|
+
if content is None or display is None or display <= 0:
|
|
1007
|
+
return 1.0
|
|
1008
|
+
return content / display
|
|
1009
|
+
|
|
979
1010
|
def _apply_geometry_without_showing(self, width: int, height: int, x: int, y: int) -> bool:
|
|
980
1011
|
if os.name != "nt" or self.window is None:
|
|
981
1012
|
return False
|
|
982
|
-
scale = self.
|
|
1013
|
+
scale = self._content_scale()
|
|
983
1014
|
if scale is None:
|
|
984
1015
|
return False
|
|
985
1016
|
try:
|
|
@@ -1018,7 +1049,7 @@ class _WindowsTrayController:
|
|
|
1018
1049
|
"""Read the target monitor's physical work area independently of pywebview."""
|
|
1019
1050
|
if os.name != "nt" or self.window is None:
|
|
1020
1051
|
return None
|
|
1021
|
-
scale = self.
|
|
1052
|
+
scale = self._content_scale()
|
|
1022
1053
|
if scale is None:
|
|
1023
1054
|
return None
|
|
1024
1055
|
|
|
@@ -1104,7 +1135,7 @@ class _WindowsTrayController:
|
|
|
1104
1135
|
return None
|
|
1105
1136
|
if not isinstance(x, (int, float)) or not isinstance(y, (int, float)):
|
|
1106
1137
|
return None
|
|
1107
|
-
scale = self.
|
|
1138
|
+
scale = self._content_scale() or 1.0
|
|
1108
1139
|
return (int(round(x / scale)), int(round(y / scale)))
|
|
1109
1140
|
|
|
1110
1141
|
def _current_window_position(self) -> tuple[int, int] | None:
|
|
@@ -1138,7 +1169,7 @@ class _WindowsTrayController:
|
|
|
1138
1169
|
position = self._current_window_position()
|
|
1139
1170
|
if position is None:
|
|
1140
1171
|
return None
|
|
1141
|
-
scale = self.
|
|
1172
|
+
scale = self._content_scale() or 1.0
|
|
1142
1173
|
return (int(round(position[0] * scale)), int(round(position[1] * scale)))
|
|
1143
1174
|
|
|
1144
1175
|
@staticmethod
|
|
@@ -1158,9 +1189,9 @@ class _WindowsTrayController:
|
|
|
1158
1189
|
left, top, right, bottom = work_area
|
|
1159
1190
|
return (max(left + 12, right - width - 12), max(top + 12, bottom - height - 12))
|
|
1160
1191
|
|
|
1161
|
-
# The legibility floor uses physical pixels: CSS zoom times
|
|
1192
|
+
# The legibility floor uses physical pixels: CSS zoom times the rendered scale.
|
|
1162
1193
|
def _panel_minimum_scale(self) -> float:
|
|
1163
|
-
return MIN_PANEL_SCALE / (self.
|
|
1194
|
+
return MIN_PANEL_SCALE / (self._content_scale() or 1.0)
|
|
1164
1195
|
|
|
1165
1196
|
def _placement_target(
|
|
1166
1197
|
self, force_default: bool
|
|
@@ -1239,9 +1270,10 @@ class _WindowsTrayController:
|
|
|
1239
1270
|
)
|
|
1240
1271
|
x, y = self._clamp_window_position(position, work_area, width, height)
|
|
1241
1272
|
logger.debug(
|
|
1242
|
-
"Panel placement work_area=%s
|
|
1243
|
-
"x=%s y=%s width=%s height=%s",
|
|
1273
|
+
"Panel placement work_area=%s content_scale=%s display_scale=%s "
|
|
1274
|
+
"natural_height=%s fit_scale=%s x=%s y=%s width=%s height=%s",
|
|
1244
1275
|
work_area,
|
|
1276
|
+
self._content_scale(),
|
|
1245
1277
|
self._window_dpi_scale(),
|
|
1246
1278
|
natural_height,
|
|
1247
1279
|
scale,
|
|
@@ -1259,10 +1291,10 @@ class _WindowsTrayController:
|
|
|
1259
1291
|
not isinstance(value, bool) and isinstance(value, int | float)
|
|
1260
1292
|
for value in current_geometry
|
|
1261
1293
|
):
|
|
1262
|
-
|
|
1263
|
-
if
|
|
1294
|
+
content_scale = self._content_scale()
|
|
1295
|
+
if content_scale is not None:
|
|
1264
1296
|
target_geometry = tuple(
|
|
1265
|
-
int(round(value *
|
|
1297
|
+
int(round(value * content_scale)) for value in (x, y, width, height)
|
|
1266
1298
|
)
|
|
1267
1299
|
if tuple(int(round(value)) for value in current_geometry) == target_geometry:
|
|
1268
1300
|
self._positioned_this_show = True
|
|
@@ -1271,8 +1303,11 @@ class _WindowsTrayController:
|
|
|
1271
1303
|
self._apply_geometry_without_showing(width, height, x, y)
|
|
1272
1304
|
)
|
|
1273
1305
|
if not geometry_applied:
|
|
1274
|
-
|
|
1275
|
-
|
|
1306
|
+
# pywebview multiplies by the display scale itself; pass values that
|
|
1307
|
+
# land on the same physical pixels the page is rendered at.
|
|
1308
|
+
ratio = self._pywebview_scale_ratio()
|
|
1309
|
+
self.window.resize(int(round(width * ratio)), int(round(height * ratio)))
|
|
1310
|
+
self.window.move(int(round(x * ratio)), int(round(y * ratio)))
|
|
1276
1311
|
self._positioned_this_show = True
|
|
1277
1312
|
|
|
1278
1313
|
def _dispatch_window_mutation(self, mutation: Callable[[], None]) -> None:
|
|
@@ -2023,6 +2058,8 @@ class _WindowsTrayController:
|
|
|
2023
2058
|
if action == "open_menu":
|
|
2024
2059
|
return self._panel_menu_data()
|
|
2025
2060
|
if action == "content_height":
|
|
2061
|
+
# The scale first: applying the height re-places the window.
|
|
2062
|
+
self._apply_page_scale(payload.get("dpr"))
|
|
2026
2063
|
self._apply_content_height(payload.get("height"))
|
|
2027
2064
|
return None
|
|
2028
2065
|
if action == "set_card_order":
|
|
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
|