usage-cli 0.33.10__tar.gz → 0.34.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {usage_cli-0.33.10/usage_cli.egg-info → usage_cli-0.34.0}/PKG-INFO +16 -15
- {usage_cli-0.33.10 → usage_cli-0.34.0}/README.md +15 -14
- {usage_cli-0.33.10 → usage_cli-0.34.0}/analyzer/dispatch_ledger.py +6 -1
- {usage_cli-0.33.10 → usage_cli-0.34.0}/i18n.json +5 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/installer/session_hooks.py +1 -1
- usage_cli-0.34.0/loaders/muse_quota_probe.py +276 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/menubar/agy.py +2 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/menubar/app.py +5 -19
- {usage_cli-0.33.10 → usage_cli-0.34.0}/menubar/chrome.py +15 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/menubar/menu.py +10 -0
- usage_cli-0.34.0/menubar/muse.py +73 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/menubar/notify.py +32 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/menubar/prefs.py +6 -1
- {usage_cli-0.33.10 → usage_cli-0.34.0}/menubar/refresh.py +21 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/menubar/state.py +63 -2
- {usage_cli-0.33.10 → usage_cli-0.34.0}/menubar/title.py +35 -51
- {usage_cli-0.33.10 → usage_cli-0.34.0}/panels/__init__.py +1 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/panels/base.py +1 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/panels/catalog.py +32 -16
- {usage_cli-0.33.10 → usage_cli-0.34.0}/panels/payload.py +15 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/panels/web_panel.py +3 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/pyproject.toml +1 -1
- {usage_cli-0.33.10 → usage_cli-0.34.0}/quota/codex_window_keeper.py +14 -1
- {usage_cli-0.33.10 → usage_cli-0.34.0}/quota/notifications.py +2 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/quota/status_payload.py +46 -2
- {usage_cli-0.33.10 → usage_cli-0.34.0}/quota/window_keeper.py +16 -1
- {usage_cli-0.33.10 → usage_cli-0.34.0}/setup_app.py +1 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_analyzer_pipeline.py +4 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_check_upstream.py +23 -1
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_codex_window_keeper.py +4 -0
- usage_cli-0.34.0/tests/test_compact_cards.py +262 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_dispatch_ledger.py +33 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_menubar.py +154 -10
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_menubar_agy.py +2 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_menubar_grok.py +1 -1
- usage_cli-0.34.0/tests/test_menubar_muse.py +88 -0
- usage_cli-0.34.0/tests/test_menubar_notify.py +191 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_menubar_prefs.py +17 -2
- usage_cli-0.34.0/tests/test_muse_coverage.py +94 -0
- usage_cli-0.34.0/tests/test_muse_quota_probe.py +263 -0
- usage_cli-0.34.0/tests/test_muse_review_regressions.py +203 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_panels.py +21 -8
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_quota_aware.py +35 -1
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_usage_notifications.py +20 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_web_panel_payload.py +19 -3
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_window_keeper.py +32 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_wintray.py +44 -5
- {usage_cli-0.33.10 → usage_cli-0.34.0/usage_cli.egg-info}/PKG-INFO +16 -15
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_cli.egg-info/SOURCES.txt +7 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_hooks/usage_quota_aware.py +5 -2
- {usage_cli-0.33.10 → usage_cli-0.34.0}/wintray/app.py +37 -13
- {usage_cli-0.33.10 → usage_cli-0.34.0}/wintray/menu.py +8 -0
- usage_cli-0.33.10/tests/test_menubar_notify.py +0 -91
- {usage_cli-0.33.10 → usage_cli-0.34.0}/LICENSE +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/THIRD_PARTY_NOTICES.md +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/adapters/__init__.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/adapters/agy.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/adapters/claude.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/adapters/codex.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/adapters/grok.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/adapters/muse.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/adapters/rate_limits.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/adapters/registry.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/adapters/types.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/analyzer/__init__.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/analyzer/aggregator.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/analyzer/blocks.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/analyzer/diagnoser.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/analyzer/diagnosis_snapshot.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/analyzer/history_persist.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/analyzer/insights.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/analyzer/ledger_backfill.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/analyzer/persona_loader.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/analyzer/reporter.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/analyzer/subscription.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/analyzer/usage_snapshot.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/installer/__init__.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/installer/claude_pane.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/installer/codex_hook_trust.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/installer/login_item.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/installer/setup_hook.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/installer/statusline_settings.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/loaders/__init__.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/loaders/_shared_cache_file.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/loaders/agy_disk_cache.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/loaders/agy_loader.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/loaders/agy_quota_probe.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/loaders/cache_quarantine.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/loaders/chromium_cache.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/loaders/claude_desktop.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/loaders/claude_paths.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/loaders/claude_usage.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/loaders/codex_disk_cache.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/loaders/codex_events.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/loaders/codex_fork_replay.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/loaders/codex_loader.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/loaders/codex_paths.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/loaders/disk_cache_common.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/loaders/disk_cache_lifecycle.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/loaders/grok_loader.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/loaders/grok_quota_probe.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/loaders/history_disk_cache.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/loaders/history_loader.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/loaders/jsonl_limits.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/loaders/jsonl_utils.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/loaders/muse_loader.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/main.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/menubar/__init__.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/menubar/actions.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/menubar/fsevents_watch.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/menubar/grok.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/menubar/manual_refresh.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/menubar/new_badge.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/menubar/popover.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/menubar/update.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/menubar/whats_new.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/panels/dynamic_height.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/panels/panel_scale.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/panels/panel_window.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/panels/panel_window_state.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/panels/window_drag.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/quota/__init__.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/quota/agy_window_keeper.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/quota/burn_rate.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/quota/keeper_outcome.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/quota/quota_snapshot.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/quota/usage_rate.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/setup.cfg +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_adapters_claude.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_adapters_codex.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_adapters_rate_limits.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_aggregator.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_agy_adapter.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_agy_disk_cache.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_agy_loader.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_agy_quota_probe.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_agy_window_keeper.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_analyzer_blocks.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_app_version.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_burn_rate.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_cache_quarantine.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_chromium_cache.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_claude_desktop.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_claude_pane.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_claude_paths.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_codex_consistency.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_codex_hook_trust.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_codex_loader.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_desktop_fresh_cache.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_desktop_quota_display.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_desktop_quota_review.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_diagnoser.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_disk_cache_shards.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_doctor.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_dynamic_height.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_fsevents_watch.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_grok_loader.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_grok_quota_probe.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_history_loader.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_history_persist.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_hook_scripts_are_standalone.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_html_report_snapshot.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_i18n_format_fallback.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_i18n_key_parity.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_i18n_packaged_path.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_insights.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_jsonl_utils.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_keeper_menu.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_keeper_outcome.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_ledger_backfill.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_login_item.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_main.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_manual_refresh.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_menubar_state.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_menubar_update.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_muse_loader.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_new_badge.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_packaged_resources.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_packaging.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_panel_footer_layout.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_panel_scale.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_panel_window_state.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_panels_lazy_import.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_persona_loader.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_pricing.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_project_resolver.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_quota_label.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_report_cold_read.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_report_daily_chart.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_report_filter_js.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_report_share_mask.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_reporter_build_data.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_service_status.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_session_resume.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_setup_app.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_setup_hook.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_setup_hook_coexistence.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_setup_hook_resume.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_setup_hook_terse.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_subprocess_utils.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_subscription.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_taskbar_menu.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_time_utils.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_tui.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_tui_sprite.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_ui_tables.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_update_checker.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_update_gate.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_update_release_notes.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_usage_cli.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_usage_client.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_usage_diagnosis_snapshot.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_usage_dir_sweeper.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_usage_lang.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_usage_logging.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_usage_rate.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_usage_snapshot.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_usage_snapshot_replay.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_usage_statusline.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_usage_statusline_agy.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_usage_statusline_forwarder.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_usage_statusline_grok.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_usage_terse_mode.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_usage_terse_reminder.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_whats_new.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_wheel_packaging.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_win_login_item.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tests/test_windows_manifest.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tui/__init__.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tui/app.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/tui/sprite.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/ui/__init__.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/ui/html_report.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/ui/report_charts.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/ui/report_daily_chart.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/ui/report_filter.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/ui/report_ledger.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/ui/report_scripts.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/ui/report_styles.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/ui/tables.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/updates/__init__.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/updates/checker.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/updates/gate.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/updates/release_notes.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/updates/whats_new.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_app/__init__.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_app/cli.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_app/doctor.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_cli.egg-info/dependency_links.txt +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_cli.egg-info/entry_points.txt +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_cli.egg-info/requires.txt +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_cli.egg-info/top_level.txt +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_common/__init__.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_common/app_version.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_common/i18n.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_common/prefs.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_common/pricing.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_common/project_resolver.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_common/service_status.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_common/subprocess_utils.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_common/time_utils.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_common/usage_dir_sweeper.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_common/usage_lang.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_common/usage_logging.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_hooks/__init__.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_hooks/usage_session_resume.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_hooks/usage_statusline.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_hooks/usage_statusline_agy.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_hooks/usage_statusline_forwarder.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_hooks/usage_statusline_grok.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_hooks/usage_terse_mode.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/usage_hooks/usage_terse_reminder.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/wintray/__init__.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/wintray/login_item.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/wintray/prefs.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/wintray/quota_label.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/wintray/taskbar_menu.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/wintray/taskbar_obstacles.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/wintray/taskbar_overlay.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/wintray/watch.py +0 -0
- {usage_cli-0.33.10 → usage_cli-0.34.0}/wintray/whats_new.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: usage-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.34.0
|
|
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
|
|
@@ -26,7 +26,7 @@ Dynamic: license-file
|
|
|
26
26
|
|
|
27
27
|
# usage
|
|
28
28
|
|
|
29
|
-
### Your Claude Code, Codex, Antigravity
|
|
29
|
+
### Your Claude Code, Codex, Antigravity, Grok CLI, and Muse Code quota, always on screen.
|
|
30
30
|
|
|
31
31
|
`usage` puts your 5-hour and weekly limits in the macOS menu bar or Windows system tray, colored from green to red. Hitting the limit halfway through a long refactor is a bad way to find out you were running low. Now you see it coming. There's nothing to run and no page to open.
|
|
32
32
|
|
|
@@ -47,8 +47,8 @@ Dynamic: license-file
|
|
|
47
47
|
<img src="docs/showcase-v3.en.png" alt="usage — Claude Code, Codex, and Antigravity quota pinned to the macOS menu bar" width="820">
|
|
48
48
|
</p>
|
|
49
49
|
|
|
50
|
-
- **Every quota at a glance:** Claude Code, Codex, and
|
|
51
|
-
- **Reads what's already on your machine:** Claude Code, Codex,
|
|
50
|
+
- **Every quota at a glance:** Claude Code, Codex, Antigravity, and Muse Code session and weekly limits with reset countdowns, plus Grok CLI's weekly credit.
|
|
51
|
+
- **Reads what's already on your machine:** Claude Code, Codex, Grok CLI, and Muse Code tokens and cost come from local logs. Antigravity quota comes from Google's official endpoint, using the sign-in its CLI already stores. Muse quota comes from a small background model call through its own signed-in CLI.
|
|
52
52
|
- **Warns before tokens go to waste:** The Claude Code status line flags a bloating context window and a cold prompt cache. A banner shows Claude or Codex outages.
|
|
53
53
|
- **Helpers inside Claude Code:** A side pane, shorter replies, a progress hand-off for new sessions, and quota-aware planning. All optional.
|
|
54
54
|
- **Reports and 16 themes:** HTML reports of token trends and cost, and 16 panel themes to pick from.
|
|
@@ -88,7 +88,7 @@ Then click the menu bar or tray icon.
|
|
|
88
88
|
|
|
89
89
|
</details>
|
|
90
90
|
|
|
91
|
-
`usage` needs data from at least one of Claude Code, Codex, Antigravity,
|
|
91
|
+
`usage` needs data from at least one of Claude Code, Codex, Antigravity, Grok CLI, or Muse Code, or a running Claude Desktop app.
|
|
92
92
|
|
|
93
93
|
## First Launch: Set Up the Status Line
|
|
94
94
|
|
|
@@ -107,13 +107,13 @@ Once set up, the bottom of the Claude Code window will show a status line like t
|
|
|
107
107
|
|
|
108
108
|
### On Screen
|
|
109
109
|
|
|
110
|
-
- **Menu bar monitor:** Quota color-coded from green to red. Click for the full session, weekly, and per-project breakdown.
|
|
110
|
+
- **Menu bar monitor:** Quota color-coded from green to red. Click for the full session, weekly, and per-project breakdown. On macOS, the menu bar keeps at most three tools with the highest Session usage, in Claude → Codex → Antigravity → Grok → Muse order; the panel still shows all tools.
|
|
111
111
|
- **Antigravity card:** Shows the Gemini pool by default. Tap the `Gemini ⇄` tag to switch to the separate Claude / GPT pool; the choice is remembered.
|
|
112
112
|
- **Grok CLI card:** The weekly credit percentage from Grok CLI's local debug log. Its tokens also count toward cost and project totals.
|
|
113
|
-
- **Muse Code
|
|
113
|
+
- **Muse Code card:** Shows 5-hour (Session) and weekly quota with reset countdowns. Available in all 15 quota themes, with a menu bar item and optional low, exhausted, and recovered quota notifications. Hidden automatically when Muse is not installed. Tokens and cost still count toward today's cost, project totals, reports, and the CLI.
|
|
114
114
|
- **Outage alerts:** An orange-red banner when Claude Code, Claude API, or Codex API is down or degraded, read from their public status pages.
|
|
115
115
|
- **Context and cache warnings:** The status line nudges you to `/clear` or `/compact` before the context window bloats, and says why the prompt cache missed.
|
|
116
|
-
- **Hide what you don't use:** Hide the Claude Code, Codex, Grok CLI, or
|
|
116
|
+
- **Hide what you don't use:** Hide the Claude Code, Codex, Grok CLI, Antigravity, or Muse Code section from the menu bar and panels in one click.
|
|
117
117
|
|
|
118
118
|
### Inside Claude Code
|
|
119
119
|
|
|
@@ -130,9 +130,9 @@ Once set up, the bottom of the Claude Code window will show a status line like t
|
|
|
130
130
|
### Reports and More
|
|
131
131
|
|
|
132
132
|
- **HTML reports:** Daily and weekly token trends, project rankings, cost, and a Year in Review with a contribution heatmap. Export .html, .csv, or .png fully offline, with optional project-name masking.
|
|
133
|
-
- **Terminal integration:** `usage status --json` hands your Claude Code, Codex, Antigravity, and
|
|
133
|
+
- **Terminal integration:** `usage status --json` hands your Claude Code, Codex, Antigravity, Grok, and Muse quota to Starship, tmux, or your own scripts. [Ready-made snippets](docs/DEVELOPMENT.md#quota-status-for-other-tools-usage-status).
|
|
134
134
|
- **AI Update Daily:** A daily public [page](https://aqua5230.github.io/ai-updates/) of Claude Code, Codex, and Antigravity changes, with plain-language summaries in five languages.
|
|
135
|
-
- **Your layout:** Drag the panel anywhere, drag quota cards to reorder them, and switch among 16 themes. The UI follows your system language: Traditional Chinese, Simplified Chinese, English, Japanese, or Korean.
|
|
135
|
+
- **Your layout:** Drag the panel anywhere, drag quota cards to reorder them, and switch among 16 themes. The UI follows your system language: Traditional Chinese, Simplified Chinese, English, Japanese, or Korean. With four or more quota cards visible, all cards switch to a compact layout; three or fewer keep the usual layout.
|
|
136
136
|
|
|
137
137
|
<details>
|
|
138
138
|
<summary>Thresholds, versions, and fine print</summary>
|
|
@@ -146,8 +146,8 @@ Once set up, the bottom of the Claude Code window will show a status line like t
|
|
|
146
146
|
- **Linux:** CI verifies `usage setup` on Ubuntu.
|
|
147
147
|
- **Progress Concierge:** When you `/resume` a conversation that sat long enough for its cache to expire, it warns how many tokens the next message will re-send and suggests `/compact` first.
|
|
148
148
|
- **Beginner Mode (macOS and Windows):** Terms appear above the prompt in your UI language. Press 9 to mark them understood; skipped terms come back after 1, 3, then 7 days. Understood terms return as a one-question multiple-choice quiz 7, 21, then 60 days later, at most one a day, and a wrong answer puts the term back in the hints. `/terms` opens your term history. Picking terms asks Claude Haiku through your Claude Code, and pauses while your 5-hour quota is at 90% or more.
|
|
149
|
-
- **Quota-Aware Mode (macOS and Windows):** Claude Code gets one line with what is left and when it resets when a 5-hour quota passes 80%, 90%, or 95%, or a weekly quota passes 95%. It covers Claude Code, Codex, and
|
|
150
|
-
- **Auto-start 5-hour session:** Claude gets Haiku, Antigravity gets Gemini 3.8 Flash Low, and Codex gets its cheapest model. The quota used is negligible.
|
|
149
|
+
- **Quota-Aware Mode (macOS and Windows):** Claude Code gets one line with what is left and when it resets when a 5-hour quota passes 80%, 90%, or 95%, or a weekly quota passes 95%. It covers Claude Code, Codex, Antigravity, and Muse, says each level once per conversation, and treats each quota and model group separately, so a used-up one only matters for work that runs on it.
|
|
150
|
+
- **Auto-start 5-hour session:** Claude gets Haiku, Antigravity gets Gemini 3.8 Flash Low, and Codex gets its cheapest model. The quota used is negligible.
|
|
151
151
|
- **Panel:** It stays put when another app takes focus; a second click on the menu bar icon, or Escape, closes it. Card order is shared across every theme with quota cards (all except World Cup 2026) and survives restarts.
|
|
152
152
|
- **HTML reports:** A "What you worked on" section lists the names Claude Code gave your recent conversations, and masking covers those titles too.
|
|
153
153
|
- **AI Update Daily:** Unreviewed items show the original source text. The full history is kept.
|
|
@@ -225,7 +225,7 @@ Switch between **16 visual themes** directly from the UI:
|
|
|
225
225
|
| Claude Code & Codex usage | ✅ | ✅ | ✅ |
|
|
226
226
|
| Antigravity usage (Gemini and Claude / GPT) | ✅ | — | — |
|
|
227
227
|
| Grok CLI usage | ✅ | — | — |
|
|
228
|
-
| Muse Code
|
|
228
|
+
| Muse Code quota, tokens, and cost | ✅ | — | — |
|
|
229
229
|
| Claude Code & Codex service-status alerts | ✅ | — | — |
|
|
230
230
|
| HTML deep reports & UI | ✅ | ✅ | — |
|
|
231
231
|
| Claude Code helpers (Token Saver, Progress Concierge, Beginner and Quota-Aware modes, health check) | ✅ | — | — |
|
|
@@ -235,7 +235,7 @@ Switch between **16 visual themes** directly from the UI:
|
|
|
235
235
|
## When usage Isn't the Right Fit
|
|
236
236
|
|
|
237
237
|
- You only live in the terminal and don't want another menu bar icon running in the background — a one-off CLI check fits better.
|
|
238
|
-
- You don't use Claude Code, Codex, Antigravity, Grok CLI, or Claude Desktop — there's no usage data for `usage` to read.
|
|
238
|
+
- You don't use Claude Code, Codex, Antigravity, Grok CLI, Muse Code, or Claude Desktop — there's no usage data for `usage` to read.
|
|
239
239
|
- You want a menu bar on Linux. Only macOS and Windows have one today, though the terminal interface (`uvx usage-cli`) runs on Linux.
|
|
240
240
|
|
|
241
241
|
## Windows Support
|
|
@@ -269,9 +269,10 @@ If the menu bar shows `--`, it's usually not broken — there's just no local da
|
|
|
269
269
|
|
|
270
270
|
## Privacy & Data Sources
|
|
271
271
|
|
|
272
|
-
- **Local logs:** Claude Code, Codex, Grok CLI, and Muse Code numbers are read from log files on your machine. Their contents are never uploaded.
|
|
272
|
+
- **Local logs:** Claude Code, Codex, Grok CLI, and Muse Code token and cost numbers are read from log files on your machine. Their contents are never uploaded.
|
|
273
273
|
- **Claude Desktop:** Without the Claude Code CLI or a status line, keep Claude Desktop open and `usage` reads its local plan-usage history. No cookies, login tokens, or API calls are needed. Reset times appear only when its local cache confirms them; they are never estimated.
|
|
274
274
|
- **Antigravity**, only if you use it, needs network access: quota is fetched from Google's official quota endpoint with the OAuth credential the Antigravity CLI already stored after sign-in — read from macOS Keychain, Windows Credential Manager, or a local token file depending on CLI version. `usage` never writes that credential back and keeps any refreshed access token in memory only; the call itself reads quota metadata.
|
|
275
|
+
- **Muse quota:** Muse stores no quota file locally. `usage` runs `muse exec --json` through Muse's own sign-in and saves the returned quota to `~/.usage/muse_quota.json`. This connects to Muse and makes a real model call, using a little Muse quota. It runs in an empty folder with `--no-session-log`. With Muse installed and a conversation available, it fetches the first snapshot; later probes require a snapshot over 30 minutes old and a newer Muse conversation. After failure, it waits at least 30 minutes and requires new Muse usage before retrying. Hide its card under **Hide sections**.
|
|
275
276
|
- **Other background network activity:** Public Claude and Codex status pages to flag outages, a public model-pricing table to estimate cost (built-in prices are used offline), and an occasional GitHub check for a new version.
|
|
276
277
|
- **Beginner Mode**, only if you turn it on, sends Claude Code's latest answer to Claude Haiku through your own Claude Code sign-in to pick terms. Your term list stays in `~/.usage/glossary.json`.
|
|
277
278
|
- **Long-term history:** Claude Code deletes transcripts older than 30 days by default. While the app runs, `usage` saves daily totals to `~/.usage/usage_snapshot.json` and `~/.usage/year_ledger.json` every 6 hours, so reports keep older days. Nothing leaves your machine. When you move to a new computer, copy the whole `~/.usage/` folder too.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# usage
|
|
6
6
|
|
|
7
|
-
### Your Claude Code, Codex, Antigravity
|
|
7
|
+
### Your Claude Code, Codex, Antigravity, Grok CLI, and Muse Code quota, always on screen.
|
|
8
8
|
|
|
9
9
|
`usage` puts your 5-hour and weekly limits in the macOS menu bar or Windows system tray, colored from green to red. Hitting the limit halfway through a long refactor is a bad way to find out you were running low. Now you see it coming. There's nothing to run and no page to open.
|
|
10
10
|
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
<img src="docs/showcase-v3.en.png" alt="usage — Claude Code, Codex, and Antigravity quota pinned to the macOS menu bar" width="820">
|
|
26
26
|
</p>
|
|
27
27
|
|
|
28
|
-
- **Every quota at a glance:** Claude Code, Codex, and
|
|
29
|
-
- **Reads what's already on your machine:** Claude Code, Codex,
|
|
28
|
+
- **Every quota at a glance:** Claude Code, Codex, Antigravity, and Muse Code session and weekly limits with reset countdowns, plus Grok CLI's weekly credit.
|
|
29
|
+
- **Reads what's already on your machine:** Claude Code, Codex, Grok CLI, and Muse Code tokens and cost come from local logs. Antigravity quota comes from Google's official endpoint, using the sign-in its CLI already stores. Muse quota comes from a small background model call through its own signed-in CLI.
|
|
30
30
|
- **Warns before tokens go to waste:** The Claude Code status line flags a bloating context window and a cold prompt cache. A banner shows Claude or Codex outages.
|
|
31
31
|
- **Helpers inside Claude Code:** A side pane, shorter replies, a progress hand-off for new sessions, and quota-aware planning. All optional.
|
|
32
32
|
- **Reports and 16 themes:** HTML reports of token trends and cost, and 16 panel themes to pick from.
|
|
@@ -66,7 +66,7 @@ Then click the menu bar or tray icon.
|
|
|
66
66
|
|
|
67
67
|
</details>
|
|
68
68
|
|
|
69
|
-
`usage` needs data from at least one of Claude Code, Codex, Antigravity,
|
|
69
|
+
`usage` needs data from at least one of Claude Code, Codex, Antigravity, Grok CLI, or Muse Code, or a running Claude Desktop app.
|
|
70
70
|
|
|
71
71
|
## First Launch: Set Up the Status Line
|
|
72
72
|
|
|
@@ -85,13 +85,13 @@ Once set up, the bottom of the Claude Code window will show a status line like t
|
|
|
85
85
|
|
|
86
86
|
### On Screen
|
|
87
87
|
|
|
88
|
-
- **Menu bar monitor:** Quota color-coded from green to red. Click for the full session, weekly, and per-project breakdown.
|
|
88
|
+
- **Menu bar monitor:** Quota color-coded from green to red. Click for the full session, weekly, and per-project breakdown. On macOS, the menu bar keeps at most three tools with the highest Session usage, in Claude → Codex → Antigravity → Grok → Muse order; the panel still shows all tools.
|
|
89
89
|
- **Antigravity card:** Shows the Gemini pool by default. Tap the `Gemini ⇄` tag to switch to the separate Claude / GPT pool; the choice is remembered.
|
|
90
90
|
- **Grok CLI card:** The weekly credit percentage from Grok CLI's local debug log. Its tokens also count toward cost and project totals.
|
|
91
|
-
- **Muse Code
|
|
91
|
+
- **Muse Code card:** Shows 5-hour (Session) and weekly quota with reset countdowns. Available in all 15 quota themes, with a menu bar item and optional low, exhausted, and recovered quota notifications. Hidden automatically when Muse is not installed. Tokens and cost still count toward today's cost, project totals, reports, and the CLI.
|
|
92
92
|
- **Outage alerts:** An orange-red banner when Claude Code, Claude API, or Codex API is down or degraded, read from their public status pages.
|
|
93
93
|
- **Context and cache warnings:** The status line nudges you to `/clear` or `/compact` before the context window bloats, and says why the prompt cache missed.
|
|
94
|
-
- **Hide what you don't use:** Hide the Claude Code, Codex, Grok CLI, or
|
|
94
|
+
- **Hide what you don't use:** Hide the Claude Code, Codex, Grok CLI, Antigravity, or Muse Code section from the menu bar and panels in one click.
|
|
95
95
|
|
|
96
96
|
### Inside Claude Code
|
|
97
97
|
|
|
@@ -108,9 +108,9 @@ Once set up, the bottom of the Claude Code window will show a status line like t
|
|
|
108
108
|
### Reports and More
|
|
109
109
|
|
|
110
110
|
- **HTML reports:** Daily and weekly token trends, project rankings, cost, and a Year in Review with a contribution heatmap. Export .html, .csv, or .png fully offline, with optional project-name masking.
|
|
111
|
-
- **Terminal integration:** `usage status --json` hands your Claude Code, Codex, Antigravity, and
|
|
111
|
+
- **Terminal integration:** `usage status --json` hands your Claude Code, Codex, Antigravity, Grok, and Muse quota to Starship, tmux, or your own scripts. [Ready-made snippets](docs/DEVELOPMENT.md#quota-status-for-other-tools-usage-status).
|
|
112
112
|
- **AI Update Daily:** A daily public [page](https://aqua5230.github.io/ai-updates/) of Claude Code, Codex, and Antigravity changes, with plain-language summaries in five languages.
|
|
113
|
-
- **Your layout:** Drag the panel anywhere, drag quota cards to reorder them, and switch among 16 themes. The UI follows your system language: Traditional Chinese, Simplified Chinese, English, Japanese, or Korean.
|
|
113
|
+
- **Your layout:** Drag the panel anywhere, drag quota cards to reorder them, and switch among 16 themes. The UI follows your system language: Traditional Chinese, Simplified Chinese, English, Japanese, or Korean. With four or more quota cards visible, all cards switch to a compact layout; three or fewer keep the usual layout.
|
|
114
114
|
|
|
115
115
|
<details>
|
|
116
116
|
<summary>Thresholds, versions, and fine print</summary>
|
|
@@ -124,8 +124,8 @@ Once set up, the bottom of the Claude Code window will show a status line like t
|
|
|
124
124
|
- **Linux:** CI verifies `usage setup` on Ubuntu.
|
|
125
125
|
- **Progress Concierge:** When you `/resume` a conversation that sat long enough for its cache to expire, it warns how many tokens the next message will re-send and suggests `/compact` first.
|
|
126
126
|
- **Beginner Mode (macOS and Windows):** Terms appear above the prompt in your UI language. Press 9 to mark them understood; skipped terms come back after 1, 3, then 7 days. Understood terms return as a one-question multiple-choice quiz 7, 21, then 60 days later, at most one a day, and a wrong answer puts the term back in the hints. `/terms` opens your term history. Picking terms asks Claude Haiku through your Claude Code, and pauses while your 5-hour quota is at 90% or more.
|
|
127
|
-
- **Quota-Aware Mode (macOS and Windows):** Claude Code gets one line with what is left and when it resets when a 5-hour quota passes 80%, 90%, or 95%, or a weekly quota passes 95%. It covers Claude Code, Codex, and
|
|
128
|
-
- **Auto-start 5-hour session:** Claude gets Haiku, Antigravity gets Gemini 3.8 Flash Low, and Codex gets its cheapest model. The quota used is negligible.
|
|
127
|
+
- **Quota-Aware Mode (macOS and Windows):** Claude Code gets one line with what is left and when it resets when a 5-hour quota passes 80%, 90%, or 95%, or a weekly quota passes 95%. It covers Claude Code, Codex, Antigravity, and Muse, says each level once per conversation, and treats each quota and model group separately, so a used-up one only matters for work that runs on it.
|
|
128
|
+
- **Auto-start 5-hour session:** Claude gets Haiku, Antigravity gets Gemini 3.8 Flash Low, and Codex gets its cheapest model. The quota used is negligible.
|
|
129
129
|
- **Panel:** It stays put when another app takes focus; a second click on the menu bar icon, or Escape, closes it. Card order is shared across every theme with quota cards (all except World Cup 2026) and survives restarts.
|
|
130
130
|
- **HTML reports:** A "What you worked on" section lists the names Claude Code gave your recent conversations, and masking covers those titles too.
|
|
131
131
|
- **AI Update Daily:** Unreviewed items show the original source text. The full history is kept.
|
|
@@ -203,7 +203,7 @@ Switch between **16 visual themes** directly from the UI:
|
|
|
203
203
|
| Claude Code & Codex usage | ✅ | ✅ | ✅ |
|
|
204
204
|
| Antigravity usage (Gemini and Claude / GPT) | ✅ | — | — |
|
|
205
205
|
| Grok CLI usage | ✅ | — | — |
|
|
206
|
-
| Muse Code
|
|
206
|
+
| Muse Code quota, tokens, and cost | ✅ | — | — |
|
|
207
207
|
| Claude Code & Codex service-status alerts | ✅ | — | — |
|
|
208
208
|
| HTML deep reports & UI | ✅ | ✅ | — |
|
|
209
209
|
| Claude Code helpers (Token Saver, Progress Concierge, Beginner and Quota-Aware modes, health check) | ✅ | — | — |
|
|
@@ -213,7 +213,7 @@ Switch between **16 visual themes** directly from the UI:
|
|
|
213
213
|
## When usage Isn't the Right Fit
|
|
214
214
|
|
|
215
215
|
- You only live in the terminal and don't want another menu bar icon running in the background — a one-off CLI check fits better.
|
|
216
|
-
- You don't use Claude Code, Codex, Antigravity, Grok CLI, or Claude Desktop — there's no usage data for `usage` to read.
|
|
216
|
+
- You don't use Claude Code, Codex, Antigravity, Grok CLI, Muse Code, or Claude Desktop — there's no usage data for `usage` to read.
|
|
217
217
|
- You want a menu bar on Linux. Only macOS and Windows have one today, though the terminal interface (`uvx usage-cli`) runs on Linux.
|
|
218
218
|
|
|
219
219
|
## Windows Support
|
|
@@ -247,9 +247,10 @@ If the menu bar shows `--`, it's usually not broken — there's just no local da
|
|
|
247
247
|
|
|
248
248
|
## Privacy & Data Sources
|
|
249
249
|
|
|
250
|
-
- **Local logs:** Claude Code, Codex, Grok CLI, and Muse Code numbers are read from log files on your machine. Their contents are never uploaded.
|
|
250
|
+
- **Local logs:** Claude Code, Codex, Grok CLI, and Muse Code token and cost numbers are read from log files on your machine. Their contents are never uploaded.
|
|
251
251
|
- **Claude Desktop:** Without the Claude Code CLI or a status line, keep Claude Desktop open and `usage` reads its local plan-usage history. No cookies, login tokens, or API calls are needed. Reset times appear only when its local cache confirms them; they are never estimated.
|
|
252
252
|
- **Antigravity**, only if you use it, needs network access: quota is fetched from Google's official quota endpoint with the OAuth credential the Antigravity CLI already stored after sign-in — read from macOS Keychain, Windows Credential Manager, or a local token file depending on CLI version. `usage` never writes that credential back and keeps any refreshed access token in memory only; the call itself reads quota metadata.
|
|
253
|
+
- **Muse quota:** Muse stores no quota file locally. `usage` runs `muse exec --json` through Muse's own sign-in and saves the returned quota to `~/.usage/muse_quota.json`. This connects to Muse and makes a real model call, using a little Muse quota. It runs in an empty folder with `--no-session-log`. With Muse installed and a conversation available, it fetches the first snapshot; later probes require a snapshot over 30 minutes old and a newer Muse conversation. After failure, it waits at least 30 minutes and requires new Muse usage before retrying. Hide its card under **Hide sections**.
|
|
253
254
|
- **Other background network activity:** Public Claude and Codex status pages to flag outages, a public model-pricing table to estimate cost (built-in prices are used offline), and an occasional GitHub check for a new version.
|
|
254
255
|
- **Beginner Mode**, only if you turn it on, sends Claude Code's latest answer to Claude Haiku through your own Claude Code sign-in to pick terms. Your term list stays in `~/.usage/glossary.json`.
|
|
255
256
|
- **Long-term history:** Claude Code deletes transcripts older than 30 days by default. While the app runs, `usage` saves daily totals to `~/.usage/usage_snapshot.json` and `~/.usage/year_ledger.json` every 6 hours, so reports keep older days. Nothing leaves your machine. When you move to a new computer, copy the whole `~/.usage/` folder too.
|
|
@@ -16,7 +16,7 @@ from typing import Any, TypedDict
|
|
|
16
16
|
|
|
17
17
|
from adapters import rate_limits
|
|
18
18
|
from adapters.types import UsageEntry
|
|
19
|
-
from loaders import agy_loader, agy_quota_probe, codex_loader
|
|
19
|
+
from loaders import agy_loader, agy_quota_probe, codex_loader, muse_quota_probe
|
|
20
20
|
from loaders.claude_paths import claude_config_dirs
|
|
21
21
|
|
|
22
22
|
_DISPATCH = {
|
|
@@ -303,6 +303,11 @@ def load_windows() -> dict[str, QuotaWindow]:
|
|
|
303
303
|
windows[group_key] = window
|
|
304
304
|
for model in group.models:
|
|
305
305
|
windows[f"antigravity-model:{model.lower()}"] = window
|
|
306
|
+
muse = muse_quota_probe.read_cache()
|
|
307
|
+
if muse:
|
|
308
|
+
window = _window(muse["weekly"]["used_percent"], muse["weekly"]["resets_at"], 7 * 24 * 60)
|
|
309
|
+
if window:
|
|
310
|
+
windows["muse"] = window
|
|
306
311
|
return windows
|
|
307
312
|
|
|
308
313
|
|
|
@@ -109,6 +109,7 @@
|
|
|
109
109
|
"agy_stale_tooltip": "顯示的是快取舊值。額度直接向官方 API 取得,通常每幾分鐘自動更新;出現舊值代表最近取用失敗(多半是 agy 尚未登入,或網路問題),成功後會自動恢復。",
|
|
110
110
|
"grok_name": "Grok",
|
|
111
111
|
"muse_name": "Muse",
|
|
112
|
+
"muse_stale_tooltip": "資料來自上次 Muse 額度探測,可能已過期;Muse 有新用量後會自動重試。",
|
|
112
113
|
"grok_stale_minutes": "約 {minutes} 分鐘前",
|
|
113
114
|
"grok_stale_hours": "約 {hours} 小時前",
|
|
114
115
|
"grok_stale_tooltip": "資料來自 Grok CLI 本機 log,不是即時 API;請最近執行過 grok 指令後再查看,才會更新。",
|
|
@@ -667,6 +668,7 @@
|
|
|
667
668
|
"agy_stale_tooltip": "Showing a cached value. Quota is fetched straight from the official API, usually updating every few minutes; an old value means the last fetch failed (often agy isn't signed in, or there's a network issue), and it recovers automatically once a fetch succeeds.",
|
|
668
669
|
"grok_name": "Grok",
|
|
669
670
|
"muse_name": "Muse",
|
|
671
|
+
"muse_stale_tooltip": "Data comes from the last Muse quota probe and may be outdated. It will retry automatically after Muse has new usage.",
|
|
670
672
|
"grok_stale_minutes": "about {minutes} minutes ago",
|
|
671
673
|
"grok_stale_hours": "about {hours} hours ago",
|
|
672
674
|
"grok_stale_tooltip": "Data comes from Grok CLI's local log, not a live API. Run a grok command recently for it to update.",
|
|
@@ -1225,6 +1227,7 @@
|
|
|
1225
1227
|
"agy_stale_tooltip": "显示的是缓存旧值。额度直接从官方 API 获取,通常每隔几分钟自动更新;出现旧值说明最近获取失败(多半是 agy 尚未登录,或网络问题),成功后会自动恢复。",
|
|
1226
1228
|
"grok_name": "Grok",
|
|
1227
1229
|
"muse_name": "Muse",
|
|
1230
|
+
"muse_stale_tooltip": "资料来自上次 Muse 额度探测,可能已过期;Muse 有新用量后会自动重试。",
|
|
1228
1231
|
"grok_stale_minutes": "约 {minutes} 分钟前",
|
|
1229
1232
|
"grok_stale_hours": "约 {hours} 小时前",
|
|
1230
1233
|
"grok_stale_tooltip": "资料来自 Grok CLI 本地日志,不是即时 API;请最近运行过 grok 指令后再查看,才会更新。",
|
|
@@ -1783,6 +1786,7 @@
|
|
|
1783
1786
|
"agy_stale_tooltip": "キャッシュされた値を表示しています。利用枠は公式 API から直接取得し、通常数分ごとに自動更新されます。古い値が表示されるのは直近の取得に失敗したためです(多くの場合 agy が未サインイン、またはネットワークの問題)。取得に成功すると自動的に復旧します。",
|
|
1784
1787
|
"grok_name": "Grok",
|
|
1785
1788
|
"muse_name": "Muse",
|
|
1789
|
+
"muse_stale_tooltip": "前回の Muse 利用枠確認のデータです。古い可能性があります。Muse に新しい利用があると自動で再試行します。",
|
|
1786
1790
|
"grok_stale_minutes": "約 {minutes} 分前",
|
|
1787
1791
|
"grok_stale_hours": "約 {hours} 時間前",
|
|
1788
1792
|
"grok_stale_tooltip": "データは Grok CLI のローカルログから読み取るもので、リアルタイム API ではありません。更新するには最近 grok コマンドを実行してください。",
|
|
@@ -2341,6 +2345,7 @@
|
|
|
2341
2345
|
"agy_stale_tooltip": "캐시된 값을 표시하고 있습니다. 사용량은 공식 API에서 직접 가져오며 보통 몇 분마다 자동 업데이트됩니다. 예전 값이 보이는 건 최근 가져오기에 실패했기 때문입니다(대개 agy 미로그인 또는 네트워크 문제). 가져오기에 성공하면 자동으로 복구됩니다.",
|
|
2342
2346
|
"grok_name": "Grok",
|
|
2343
2347
|
"muse_name": "Muse",
|
|
2348
|
+
"muse_stale_tooltip": "마지막 Muse 할당량 확인 데이터로, 오래되었을 수 있습니다. Muse에 새 사용량이 생기면 자동으로 다시 시도합니다.",
|
|
2344
2349
|
"grok_stale_minutes": "약 {minutes}분 전",
|
|
2345
2350
|
"grok_stale_hours": "약 {hours}시간 전",
|
|
2346
2351
|
"grok_stale_tooltip": "데이터는 Grok CLI의 로컬 로그에서 읽으며 실시간 API가 아닙니다. 업데이트하려면 최근에 grok 명령을 실행하세요.",
|
|
@@ -94,7 +94,7 @@ _TERSE_REMINDER_MARKER = "usage-terse-reminder"
|
|
|
94
94
|
_TERSE_REMINDER_MARKERS = (_TERSE_REMINDER_MARKER, "usage_terse_reminder")
|
|
95
95
|
# Independent, opt-in Claude Code UserPromptSubmit hook.
|
|
96
96
|
QUOTA_AWARE_HOOK_TARGET = Path(os.path.expanduser("~/.claude/usage-quota-aware.py"))
|
|
97
|
-
QUOTA_AWARE_HOOK_VERSION = "1.
|
|
97
|
+
QUOTA_AWARE_HOOK_VERSION = "1.3"
|
|
98
98
|
QUOTA_AWARE_MATCHER = ""
|
|
99
99
|
_QUOTA_AWARE_MARKER = "usage-quota-aware"
|
|
100
100
|
_QUOTA_AWARE_MARKERS = (_QUOTA_AWARE_MARKER, "usage_quota_aware")
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
# Copyright (C) 2026 lollapalooza <https://github.com/aqua5230>
|
|
3
|
+
|
|
4
|
+
"""Read shared Muse quota snapshots and schedule bounded background probes."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import atexit
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
import math
|
|
12
|
+
import os
|
|
13
|
+
import shutil
|
|
14
|
+
import subprocess
|
|
15
|
+
import tempfile
|
|
16
|
+
import threading
|
|
17
|
+
import time
|
|
18
|
+
from contextlib import suppress
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import TypedDict, TypeGuard
|
|
21
|
+
|
|
22
|
+
from loaders import muse_loader
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
CACHE_PATH = Path.home() / ".usage/muse_quota.json"
|
|
26
|
+
PROBE_DIR = Path.home() / ".usage/muse-probe"
|
|
27
|
+
MUSE_MODEL = "muse-spark-1.3"
|
|
28
|
+
STALE_SECONDS = 30 * 60
|
|
29
|
+
_probe_lock = threading.Lock()
|
|
30
|
+
_probe_running = False
|
|
31
|
+
_retry_after = 0.0
|
|
32
|
+
_last_attempt_session: float | None = None
|
|
33
|
+
_probe_process: subprocess.Popen[str] | None = None
|
|
34
|
+
_probe_shutdown = False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class MuseQuotaWindow(TypedDict):
|
|
38
|
+
used_percent: float
|
|
39
|
+
resets_at: float
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class MuseQuotaResult(TypedDict):
|
|
43
|
+
checked_at: float
|
|
44
|
+
window: MuseQuotaWindow
|
|
45
|
+
weekly: MuseQuotaWindow
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def find_muse() -> str | None:
|
|
49
|
+
"""Find Muse even when the app inherits a limited GUI PATH."""
|
|
50
|
+
executable = shutil.which("muse")
|
|
51
|
+
if executable is not None:
|
|
52
|
+
return executable
|
|
53
|
+
for candidate in ("~/.local/bin/muse", "/opt/homebrew/bin/muse", "/usr/local/bin/muse"):
|
|
54
|
+
expanded = os.path.expanduser(candidate)
|
|
55
|
+
if os.path.isfile(expanded) and os.access(expanded, os.X_OK):
|
|
56
|
+
return expanded
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _number(value: object) -> TypeGuard[int | float]:
|
|
61
|
+
if isinstance(value, bool) or not isinstance(value, int | float):
|
|
62
|
+
return False
|
|
63
|
+
try:
|
|
64
|
+
return math.isfinite(float(value))
|
|
65
|
+
except OverflowError:
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def validate_muse_cache(payload: object) -> MuseQuotaResult | None:
|
|
70
|
+
if not isinstance(payload, dict) or not _number(payload.get("checked_at")):
|
|
71
|
+
return None
|
|
72
|
+
windows: list[MuseQuotaWindow] = []
|
|
73
|
+
for name in ("window", "weekly"):
|
|
74
|
+
window = payload.get(name)
|
|
75
|
+
if not isinstance(window, dict):
|
|
76
|
+
return None
|
|
77
|
+
used, reset = window.get("used_percent"), window.get("resets_at")
|
|
78
|
+
if not _number(used) or not _number(reset) or not 0 <= used <= 100:
|
|
79
|
+
return None
|
|
80
|
+
windows.append(MuseQuotaWindow(used_percent=float(used), resets_at=float(reset)))
|
|
81
|
+
return MuseQuotaResult(
|
|
82
|
+
checked_at=float(payload["checked_at"]), window=windows[0], weekly=windows[1]
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def parse_muse_allowance(stdout: str, checked_at: float) -> MuseQuotaResult | None:
|
|
87
|
+
for line in stdout.splitlines():
|
|
88
|
+
try:
|
|
89
|
+
record = json.loads(line)
|
|
90
|
+
if not isinstance(record, dict) or record.get("payload_type") != "run.usage":
|
|
91
|
+
continue
|
|
92
|
+
allowance = record["payload"]["allowance"]
|
|
93
|
+
payload: dict[str, object] = {"checked_at": checked_at}
|
|
94
|
+
for name in ("window", "weekly"):
|
|
95
|
+
raw = allowance[name]
|
|
96
|
+
if not _number(raw["resets_at_ms"]):
|
|
97
|
+
break
|
|
98
|
+
payload[name] = {
|
|
99
|
+
"used_percent": raw["used_percent"],
|
|
100
|
+
"resets_at": raw["resets_at_ms"] / 1000,
|
|
101
|
+
}
|
|
102
|
+
result = validate_muse_cache(payload)
|
|
103
|
+
if result is not None:
|
|
104
|
+
return result
|
|
105
|
+
except (ValueError, KeyError, TypeError, OverflowError, RecursionError):
|
|
106
|
+
continue
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def read_cache() -> MuseQuotaResult | None:
|
|
111
|
+
"""Ignore invalid shared snapshots without deleting or quarantining them."""
|
|
112
|
+
try:
|
|
113
|
+
return validate_muse_cache(json.loads(CACHE_PATH.read_text(encoding="utf-8")))
|
|
114
|
+
except (OSError, ValueError, OverflowError, RecursionError):
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def latest_session_mtime() -> float | None:
|
|
119
|
+
latest = None
|
|
120
|
+
for path in muse_loader.session_paths():
|
|
121
|
+
try:
|
|
122
|
+
modified = path.stat().st_mtime
|
|
123
|
+
except OSError:
|
|
124
|
+
continue
|
|
125
|
+
latest = modified if latest is None else max(latest, modified)
|
|
126
|
+
return latest
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def is_stale(quota: MuseQuotaResult, latest: float | None, now: float) -> bool:
|
|
130
|
+
return (
|
|
131
|
+
latest is not None
|
|
132
|
+
and latest > quota["checked_at"]
|
|
133
|
+
and now - quota["checked_at"] > STALE_SECONDS
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _write_cache(result: MuseQuotaResult) -> None:
|
|
138
|
+
temporary_path = None
|
|
139
|
+
try:
|
|
140
|
+
CACHE_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
141
|
+
fd, temporary_path = tempfile.mkstemp(dir=CACHE_PATH.parent, suffix=".tmp")
|
|
142
|
+
with os.fdopen(fd, "w", encoding="utf-8") as snapshot:
|
|
143
|
+
json.dump(result, snapshot)
|
|
144
|
+
os.replace(temporary_path, CACHE_PATH)
|
|
145
|
+
temporary_path = None
|
|
146
|
+
finally:
|
|
147
|
+
if temporary_path is not None:
|
|
148
|
+
with suppress(OSError):
|
|
149
|
+
os.unlink(temporary_path)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _stop_process(process: subprocess.Popen[str]) -> None:
|
|
153
|
+
if process.poll() is not None:
|
|
154
|
+
return
|
|
155
|
+
try:
|
|
156
|
+
process.terminate()
|
|
157
|
+
try:
|
|
158
|
+
process.wait(timeout=5)
|
|
159
|
+
except subprocess.TimeoutExpired:
|
|
160
|
+
process.kill()
|
|
161
|
+
process.wait()
|
|
162
|
+
except OSError:
|
|
163
|
+
logger.warning("Could not stop Muse quota probe", exc_info=True)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _cleanup_probe() -> None:
|
|
167
|
+
global _probe_shutdown
|
|
168
|
+
with _probe_lock:
|
|
169
|
+
_probe_shutdown = True
|
|
170
|
+
process = _probe_process
|
|
171
|
+
if process is not None:
|
|
172
|
+
_stop_process(process)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
atexit.register(_cleanup_probe)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _probe_worker(executable: str) -> None:
|
|
179
|
+
global _probe_running, _retry_after, _probe_process
|
|
180
|
+
succeeded = False
|
|
181
|
+
process = None
|
|
182
|
+
try:
|
|
183
|
+
PROBE_DIR.mkdir(parents=True, exist_ok=True)
|
|
184
|
+
with _probe_lock:
|
|
185
|
+
if _probe_shutdown:
|
|
186
|
+
return
|
|
187
|
+
process = subprocess.Popen(
|
|
188
|
+
[
|
|
189
|
+
executable,
|
|
190
|
+
"exec",
|
|
191
|
+
"--model",
|
|
192
|
+
MUSE_MODEL,
|
|
193
|
+
"--reasoning-effort",
|
|
194
|
+
"low",
|
|
195
|
+
"--trust-workspace",
|
|
196
|
+
"--disable-web-tools",
|
|
197
|
+
"--no-session-log",
|
|
198
|
+
"--json",
|
|
199
|
+
"Reply with OK only.",
|
|
200
|
+
],
|
|
201
|
+
stdin=subprocess.DEVNULL,
|
|
202
|
+
stdout=subprocess.PIPE,
|
|
203
|
+
stderr=subprocess.PIPE,
|
|
204
|
+
encoding="utf-8",
|
|
205
|
+
cwd=PROBE_DIR,
|
|
206
|
+
)
|
|
207
|
+
_probe_process = process
|
|
208
|
+
try:
|
|
209
|
+
stdout, _ = process.communicate(timeout=120)
|
|
210
|
+
except subprocess.TimeoutExpired:
|
|
211
|
+
process.kill()
|
|
212
|
+
process.communicate()
|
|
213
|
+
raise
|
|
214
|
+
result = parse_muse_allowance(stdout, time.time()) if process.returncode == 0 else None
|
|
215
|
+
if result is not None:
|
|
216
|
+
_write_cache(result)
|
|
217
|
+
succeeded = True
|
|
218
|
+
else:
|
|
219
|
+
logger.warning(
|
|
220
|
+
"Muse quota probe returned no valid allowance (exit %s)", process.returncode
|
|
221
|
+
)
|
|
222
|
+
except (OSError, subprocess.SubprocessError, UnicodeError):
|
|
223
|
+
logger.warning("Muse quota probe failed", exc_info=True)
|
|
224
|
+
finally:
|
|
225
|
+
if process is not None:
|
|
226
|
+
_stop_process(process)
|
|
227
|
+
with _probe_lock:
|
|
228
|
+
_probe_process = None
|
|
229
|
+
if not succeeded:
|
|
230
|
+
_retry_after = time.monotonic() + STALE_SECONDS
|
|
231
|
+
_probe_running = False
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def load_quota_snapshot(*, allow_probe: bool = True) -> tuple[MuseQuotaResult | None, float | None]:
|
|
235
|
+
"""Read sessions once and return the snapshot plus its projection context.
|
|
236
|
+
|
|
237
|
+
``allow_probe=False`` only reads: a probe spends Muse quota, so a hidden card never starts one.
|
|
238
|
+
"""
|
|
239
|
+
global _probe_running, _retry_after, _last_attempt_session
|
|
240
|
+
cached = read_cache()
|
|
241
|
+
executable = find_muse()
|
|
242
|
+
if executable is None:
|
|
243
|
+
return None, None
|
|
244
|
+
latest = latest_session_mtime()
|
|
245
|
+
if (
|
|
246
|
+
not allow_probe
|
|
247
|
+
or latest is None
|
|
248
|
+
or (cached is not None and not is_stale(cached, latest, time.time()))
|
|
249
|
+
):
|
|
250
|
+
return cached, latest
|
|
251
|
+
with _probe_lock:
|
|
252
|
+
# A worker may have finished since the first cache read.
|
|
253
|
+
cached = read_cache()
|
|
254
|
+
if (
|
|
255
|
+
latest is None
|
|
256
|
+
or (cached is not None and not is_stale(cached, latest, time.time()))
|
|
257
|
+
or _probe_running
|
|
258
|
+
or _probe_shutdown
|
|
259
|
+
or time.monotonic() < _retry_after
|
|
260
|
+
or (_last_attempt_session is not None and latest <= _last_attempt_session)
|
|
261
|
+
):
|
|
262
|
+
return cached, latest
|
|
263
|
+
_probe_running = True
|
|
264
|
+
_last_attempt_session = latest
|
|
265
|
+
try:
|
|
266
|
+
threading.Thread(target=_probe_worker, args=(executable,), daemon=True).start()
|
|
267
|
+
except RuntimeError:
|
|
268
|
+
_probe_running = False
|
|
269
|
+
_retry_after = time.monotonic() + STALE_SECONDS
|
|
270
|
+
logger.warning("Could not start Muse quota probe thread", exc_info=True)
|
|
271
|
+
return cached, latest
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def load_quota() -> MuseQuotaResult | None:
|
|
275
|
+
"""Return the current snapshot immediately; at most one worker may refresh it."""
|
|
276
|
+
return load_quota_snapshot()[0]
|
|
@@ -58,6 +58,7 @@ class AgyQuotaProjection:
|
|
|
58
58
|
stale: AgyStaleState | None
|
|
59
59
|
five_hour: AgyQuotaWindow | None
|
|
60
60
|
group_switchable: bool = False
|
|
61
|
+
weekly_only: bool = False
|
|
61
62
|
|
|
62
63
|
|
|
63
64
|
@dataclass(frozen=True, slots=True)
|
|
@@ -153,6 +154,7 @@ def project_quota(
|
|
|
153
154
|
return AgyQuotaProjection(
|
|
154
155
|
group_name=selected.name,
|
|
155
156
|
group_switchable=len(quota.groups) >= 2,
|
|
157
|
+
weekly_only=_matches_group(selected.name, "argon"),
|
|
156
158
|
session=session,
|
|
157
159
|
weekly=weekly,
|
|
158
160
|
stale=stale,
|
|
@@ -89,6 +89,7 @@ from menubar.prefs import (
|
|
|
89
89
|
_hide_claude_enabled,
|
|
90
90
|
_hide_codex_enabled,
|
|
91
91
|
_hide_grok_enabled,
|
|
92
|
+
_hide_muse_enabled,
|
|
92
93
|
_quota_card_order,
|
|
93
94
|
_quota_notification_thresholds,
|
|
94
95
|
_quota_notifications_enabled,
|
|
@@ -507,6 +508,9 @@ class AppDelegate(NSObject):
|
|
|
507
508
|
def toggleHideGrok_(self, sender: Any) -> None:
|
|
508
509
|
self._toggle_hide_section(sender, "hide_grok_section", "hide_grok", _hide_grok_enabled)
|
|
509
510
|
|
|
511
|
+
def toggleHideMuse_(self, sender: Any) -> None:
|
|
512
|
+
self._toggle_hide_section(sender, "hide_muse_section", "hide_muse", _hide_muse_enabled)
|
|
513
|
+
|
|
510
514
|
def toggleQuotaNotifications_(self, sender: Any) -> None:
|
|
511
515
|
self._mark_switch_menu_action()
|
|
512
516
|
prefs = _load_preferences()
|
|
@@ -844,25 +848,7 @@ class AppDelegate(NSObject):
|
|
|
844
848
|
|
|
845
849
|
def _process_quota_notifications(self, state: PopoverState) -> None:
|
|
846
850
|
try:
|
|
847
|
-
events = self._quota_notifier.update(
|
|
848
|
-
{
|
|
849
|
-
"claude_session": (
|
|
850
|
-
state.claude_session.percent,
|
|
851
|
-
state.claude_session.available,
|
|
852
|
-
),
|
|
853
|
-
"claude_weekly": (state.claude_weekly.percent, state.claude_weekly.available),
|
|
854
|
-
"codex_session": (state.codex_session.percent, state.codex_session.available),
|
|
855
|
-
"codex_weekly": (state.codex_weekly.percent, state.codex_weekly.available),
|
|
856
|
-
"agy_session": (
|
|
857
|
-
state.agy_session.percent,
|
|
858
|
-
state.agy_session.available and state.agy_stale is None,
|
|
859
|
-
),
|
|
860
|
-
"agy_weekly": (
|
|
861
|
-
state.agy_weekly.percent,
|
|
862
|
-
state.agy_weekly.available and state.agy_stale is None,
|
|
863
|
-
),
|
|
864
|
-
}
|
|
865
|
-
)
|
|
851
|
+
events = self._quota_notifier.update(menubar_notify.notification_channels(state))
|
|
866
852
|
for event in events:
|
|
867
853
|
if _quota_notifications_enabled() and not self.mock:
|
|
868
854
|
self._send_quota_notification(event, state)
|