usage-cli 0.33.0__tar.gz → 0.33.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {usage_cli-0.33.0 → usage_cli-0.33.1}/PKG-INFO +5 -2
- {usage_cli-0.33.0 → usage_cli-0.33.1}/README.md +3 -1
- usage_cli-0.33.1/THIRD_PARTY_NOTICES.md +96 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/adapters/rate_limits.py +23 -2
- {usage_cli-0.33.0 → usage_cli-0.33.1}/i18n.json +20 -10
- usage_cli-0.33.1/loaders/_shared_cache_file.py +67 -0
- usage_cli-0.33.1/loaders/chromium_cache.py +215 -0
- usage_cli-0.33.1/loaders/claude_desktop.py +160 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/menubar/state.py +12 -1
- {usage_cli-0.33.0 → usage_cli-0.33.1}/pyproject.toml +2 -1
- usage_cli-0.33.1/tests/test_chromium_cache.py +249 -0
- usage_cli-0.33.1/tests/test_claude_desktop.py +166 -0
- usage_cli-0.33.1/tests/test_desktop_quota_display.py +232 -0
- usage_cli-0.33.1/tests/test_desktop_quota_review.py +269 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_quota_label.py +113 -6
- usage_cli-0.33.1/tests/test_taskbar_menu.py +189 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_wintray.py +86 -3
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tui/app.py +4 -2
- {usage_cli-0.33.0 → usage_cli-0.33.1}/usage_cli.egg-info/PKG-INFO +5 -2
- {usage_cli-0.33.0 → usage_cli-0.33.1}/usage_cli.egg-info/SOURCES.txt +9 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/usage_cli.egg-info/requires.txt +1 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/usage_client.py +58 -15
- {usage_cli-0.33.0 → usage_cli-0.33.1}/wintray/app.py +102 -2
- {usage_cli-0.33.0 → usage_cli-0.33.1}/wintray/menu.py +16 -1
- {usage_cli-0.33.0 → usage_cli-0.33.1}/wintray/quota_label.py +12 -2
- usage_cli-0.33.1/wintray/taskbar_menu.py +86 -0
- usage_cli-0.33.0/THIRD_PARTY_NOTICES.md +0 -28
- {usage_cli-0.33.0 → usage_cli-0.33.1}/LICENSE +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/adapters/__init__.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/adapters/agy.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/adapters/claude.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/adapters/codex.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/adapters/grok.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/adapters/muse.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/adapters/registry.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/adapters/types.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/analyzer/__init__.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/analyzer/aggregator.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/analyzer/blocks.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/analyzer/diagnoser.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/analyzer/dispatch_ledger.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/analyzer/insights.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/analyzer/persona_loader.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/analyzer/reporter.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/analyzer/subscription.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/analyzer/usage_snapshot.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/doctor.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/fsevents_watch.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/i18n.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/installer/__init__.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/installer/claude_pane.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/installer/codex_hook_trust.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/installer/login_item.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/installer/session_hooks.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/installer/setup_hook.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/installer/statusline_settings.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/loaders/__init__.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/loaders/agy_disk_cache.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/loaders/agy_loader.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/loaders/agy_quota_probe.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/loaders/cache_quarantine.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/loaders/claude_paths.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/loaders/codex_disk_cache.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/loaders/codex_events.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/loaders/codex_fork_replay.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/loaders/codex_loader.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/loaders/codex_paths.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/loaders/disk_cache_common.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/loaders/disk_cache_lifecycle.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/loaders/grok_loader.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/loaders/grok_quota_probe.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/loaders/history_disk_cache.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/loaders/history_loader.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/loaders/jsonl_limits.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/loaders/jsonl_utils.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/loaders/muse_loader.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/main.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/menubar/__init__.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/menubar/actions.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/menubar/agy.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/menubar/app.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/menubar/chrome.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/menubar/grok.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/menubar/manual_refresh.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/menubar/menu.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/menubar/new_badge.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/menubar/notify.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/menubar/popover.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/menubar/prefs.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/menubar/refresh.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/menubar/title.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/menubar/update.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/menubar/whats_new.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/panels/__init__.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/panels/base.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/panels/dynamic_height.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/panels/panel_scale.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/panels/panel_window.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/panels/panel_window_state.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/panels/payload.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/panels/web_panel.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/panels/window_drag.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/prefs.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/pricing.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/project_resolver.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/quota/__init__.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/quota/agy_window_keeper.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/quota/burn_rate.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/quota/codex_window_keeper.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/quota/keeper_outcome.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/quota/quota_snapshot.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/quota/usage_rate.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/quota/window_keeper.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/service_status.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/setup.cfg +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/setup_app.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_adapters_claude.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_adapters_codex.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_adapters_rate_limits.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_aggregator.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_agy_adapter.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_agy_disk_cache.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_agy_loader.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_agy_quota_probe.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_agy_window_keeper.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_analyzer_blocks.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_analyzer_pipeline.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_burn_rate.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_cache_quarantine.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_check_upstream.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_claude_pane.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_claude_paths.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_codex_consistency.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_codex_hook_trust.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_codex_loader.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_codex_window_keeper.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_diagnoser.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_disk_cache_shards.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_dispatch_ledger.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_doctor.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_dynamic_height.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_fsevents_watch.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_grok_loader.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_grok_quota_probe.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_history_loader.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_hook_scripts_are_standalone.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_html_report_snapshot.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_i18n_format_fallback.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_i18n_key_parity.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_i18n_packaged_path.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_insights.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_jsonl_utils.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_keeper_menu.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_keeper_outcome.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_login_item.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_main.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_manual_refresh.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_menubar.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_menubar_agy.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_menubar_grok.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_menubar_notify.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_menubar_prefs.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_menubar_state.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_menubar_update.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_muse_loader.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_new_badge.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_packaged_resources.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_packaging.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_panel_footer_layout.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_panel_scale.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_panel_window_state.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_panels.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_panels_lazy_import.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_persona_loader.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_pricing.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_project_resolver.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_quota_aware.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_report_daily_chart.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_report_filter_js.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_reporter_build_data.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_service_status.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_session_resume.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_setup_app.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_setup_hook.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_setup_hook_coexistence.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_setup_hook_resume.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_setup_hook_terse.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_subprocess_utils.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_subscription.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_time_utils.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_tui.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_tui_sprite.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_ui_tables.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_update_checker.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_update_gate.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_update_release_notes.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_usage_cli.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_usage_client.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_usage_diagnosis_snapshot.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_usage_dir_sweeper.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_usage_lang.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_usage_logging.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_usage_notifications.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_usage_rate.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_usage_snapshot.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_usage_snapshot_replay.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_usage_statusline.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_usage_statusline_agy.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_usage_statusline_forwarder.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_usage_statusline_grok.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_usage_terse_mode.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_usage_terse_reminder.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_web_panel_payload.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_whats_new.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_wheel_packaging.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_win_login_item.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_window_keeper.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tests/test_windows_manifest.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tui/__init__.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/tui/sprite.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/ui/__init__.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/ui/html_report.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/ui/report_charts.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/ui/report_daily_chart.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/ui/report_filter.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/ui/report_ledger.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/ui/report_scripts.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/ui/report_styles.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/ui/tables.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/updates/__init__.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/updates/checker.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/updates/gate.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/updates/release_notes.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/updates/whats_new.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/usage_cli.egg-info/dependency_links.txt +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/usage_cli.egg-info/entry_points.txt +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/usage_cli.egg-info/top_level.txt +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/usage_cli.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/usage_common/__init__.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/usage_common/subprocess_utils.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/usage_common/time_utils.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/usage_common/usage_dir_sweeper.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/usage_common/usage_lang.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/usage_common/usage_logging.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/usage_diagnosis_snapshot.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/usage_notifications.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/usage_quota_aware.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/usage_session_resume.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/usage_statusline.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/usage_statusline_agy.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/usage_statusline_forwarder.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/usage_statusline_grok.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/usage_terse_mode.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/usage_terse_reminder.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/wintray/__init__.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/wintray/login_item.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/wintray/prefs.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/wintray/taskbar_obstacles.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/wintray/taskbar_overlay.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/wintray/watch.py +0 -0
- {usage_cli-0.33.0 → usage_cli-0.33.1}/wintray/whats_new.py +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: usage-cli
|
|
3
|
-
Version: 0.33.
|
|
3
|
+
Version: 0.33.1
|
|
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
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
License-File: LICENSE
|
|
9
9
|
License-File: THIRD_PARTY_NOTICES.md
|
|
10
|
+
Requires-Dist: zstandard<1.0.0,>=0.25.0
|
|
10
11
|
Requires-Dist: pyobjc-framework-Cocoa>=11.0; sys_platform == "darwin"
|
|
11
12
|
Requires-Dist: pyobjc-framework-Quartz>=11.0; sys_platform == "darwin"
|
|
12
13
|
Requires-Dist: pyobjc-framework-UserNotifications>=11.0; sys_platform == "darwin"
|
|
@@ -101,6 +102,8 @@ It lands in your Applications folder automatically. Open it once; if macOS 15 or
|
|
|
101
102
|
|
|
102
103
|
## Privacy & Data Sources
|
|
103
104
|
|
|
105
|
+
Claude Desktop chat quota is also supported without installing the standalone Claude Code CLI or configuring a status line. Keep Claude Desktop open: when Claude Code quota files are unavailable, usage reads its local `plan-usage-history.json` (including Microsoft Store installations on Windows). When a recent response in its local Chromium block-file HTTP cache matches the organization, usage also reads the exact session and weekly reset times. Newer cached observations take precedence over throttled history samples; older cache must match the percentages. Missing, unsupported, expired, or inconsistent cache data leaves the countdown unknown; times are never estimated. Desktop samples normally update every 5–15 minutes. The panel shows the observation age, marks it stale after 30 minutes, and stops displaying it after two hours. The most recent organization sample is used; custom desktop profiles are not discovered. No cookies, login tokens, or API calls are needed. These quota caches contain no per-project token counts. Desktop sessions that also write compatible Claude Code logs under `~/.claude/projects/` are counted by the existing project/token reports.
|
|
106
|
+
|
|
104
107
|
- Claude Code and Codex numbers are read from local log files on your machine.
|
|
105
108
|
- Antigravity quota requires network access, and only if you use it: quota is fetched from Google's official quota endpoint using 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` reads that credential without writing it back and keeps any refreshed access token in memory only; the call itself reads quota metadata.
|
|
106
109
|
- Background network activity: the Antigravity quota/token endpoints above, public Claude and Codex status pages to flag outages, a public model-pricing table to estimate cost (falls back to built-in prices offline), and occasionally checking GitHub for a new version. Claude Code and Codex log contents are never uploaded.
|
|
@@ -109,7 +112,7 @@ It lands in your Applications folder automatically. Open it once; if macOS 15 or
|
|
|
109
112
|
## Requirements
|
|
110
113
|
|
|
111
114
|
- macOS 12 (Monterey) or newer, or Windows 10/11
|
|
112
|
-
- Claude Code, Codex, Antigravity, or Grok CLI
|
|
115
|
+
- Local usage data from Claude Code, Codex, Antigravity, or Grok CLI, or a running Claude Desktop app with plan-usage history.
|
|
113
116
|
- (Source runs only) Python 3.13.
|
|
114
117
|
|
|
115
118
|
## Install
|
|
@@ -80,6 +80,8 @@ It lands in your Applications folder automatically. Open it once; if macOS 15 or
|
|
|
80
80
|
|
|
81
81
|
## Privacy & Data Sources
|
|
82
82
|
|
|
83
|
+
Claude Desktop chat quota is also supported without installing the standalone Claude Code CLI or configuring a status line. Keep Claude Desktop open: when Claude Code quota files are unavailable, usage reads its local `plan-usage-history.json` (including Microsoft Store installations on Windows). When a recent response in its local Chromium block-file HTTP cache matches the organization, usage also reads the exact session and weekly reset times. Newer cached observations take precedence over throttled history samples; older cache must match the percentages. Missing, unsupported, expired, or inconsistent cache data leaves the countdown unknown; times are never estimated. Desktop samples normally update every 5–15 minutes. The panel shows the observation age, marks it stale after 30 minutes, and stops displaying it after two hours. The most recent organization sample is used; custom desktop profiles are not discovered. No cookies, login tokens, or API calls are needed. These quota caches contain no per-project token counts. Desktop sessions that also write compatible Claude Code logs under `~/.claude/projects/` are counted by the existing project/token reports.
|
|
84
|
+
|
|
83
85
|
- Claude Code and Codex numbers are read from local log files on your machine.
|
|
84
86
|
- Antigravity quota requires network access, and only if you use it: quota is fetched from Google's official quota endpoint using 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` reads that credential without writing it back and keeps any refreshed access token in memory only; the call itself reads quota metadata.
|
|
85
87
|
- Background network activity: the Antigravity quota/token endpoints above, public Claude and Codex status pages to flag outages, a public model-pricing table to estimate cost (falls back to built-in prices offline), and occasionally checking GitHub for a new version. Claude Code and Codex log contents are never uploaded.
|
|
@@ -88,7 +90,7 @@ It lands in your Applications folder automatically. Open it once; if macOS 15 or
|
|
|
88
90
|
## Requirements
|
|
89
91
|
|
|
90
92
|
- macOS 12 (Monterey) or newer, or Windows 10/11
|
|
91
|
-
- Claude Code, Codex, Antigravity, or Grok CLI
|
|
93
|
+
- Local usage data from Claude Code, Codex, Antigravity, or Grok CLI, or a running Claude Desktop app with plan-usage history.
|
|
92
94
|
- (Source runs only) Python 3.13.
|
|
93
95
|
|
|
94
96
|
## Install
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
The Windows taskbar label anchoring in `wintray/taskbar_overlay.py` is adapted
|
|
4
|
+
from [Codex Usage Widget](https://github.com/ognjeeen/codex-usage-widget),
|
|
5
|
+
commit `940d722d4044242bbe73f8056014449c2ef827a7`, particularly
|
|
6
|
+
`Infrastructure/Windows/TaskbarWindowInterop.cs`.
|
|
7
|
+
|
|
8
|
+
MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2026 Ognjen Marinković
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
## Python Zstandard
|
|
32
|
+
|
|
33
|
+
The local Claude HTTP cache reader uses python-zstandard, including its bundled
|
|
34
|
+
Zstandard library, under their BSD licenses.
|
|
35
|
+
|
|
36
|
+
Copyright (c) 2016, Gregory Szorc
|
|
37
|
+
All rights reserved.
|
|
38
|
+
|
|
39
|
+
Redistribution and use in source and binary forms, with or without modification,
|
|
40
|
+
are permitted provided that the following conditions are met:
|
|
41
|
+
|
|
42
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
43
|
+
list of conditions and the following disclaimer.
|
|
44
|
+
|
|
45
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
46
|
+
this list of conditions and the following disclaimer in the documentation
|
|
47
|
+
and/or other materials provided with the distribution.
|
|
48
|
+
|
|
49
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
50
|
+
may be used to endorse or promote products derived from this software without
|
|
51
|
+
specific prior written permission.
|
|
52
|
+
|
|
53
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
54
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
55
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
56
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
57
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
58
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
59
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
60
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
61
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
62
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
## Zstandard
|
|
66
|
+
|
|
67
|
+
BSD License
|
|
68
|
+
|
|
69
|
+
For Zstandard software
|
|
70
|
+
|
|
71
|
+
Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved.
|
|
72
|
+
|
|
73
|
+
Redistribution and use in source and binary forms, with or without modification,
|
|
74
|
+
are permitted provided that the following conditions are met:
|
|
75
|
+
|
|
76
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
77
|
+
list of conditions and the following disclaimer.
|
|
78
|
+
|
|
79
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
80
|
+
this list of conditions and the following disclaimer in the documentation
|
|
81
|
+
and/or other materials provided with the distribution.
|
|
82
|
+
|
|
83
|
+
* Neither the name Facebook, nor Meta, nor the names of its contributors may
|
|
84
|
+
be used to endorse or promote products derived from this software without
|
|
85
|
+
specific prior written permission.
|
|
86
|
+
|
|
87
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
88
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
89
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
90
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
91
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
92
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
93
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
94
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
95
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
96
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -10,6 +10,8 @@ import os
|
|
|
10
10
|
from datetime import datetime, timezone
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
|
+
import usage_client
|
|
14
|
+
|
|
13
15
|
from .types import RateLimits
|
|
14
16
|
|
|
15
17
|
STATUS_FILE = os.path.expanduser("~/.claude/usage-status.json")
|
|
@@ -42,8 +44,12 @@ def _read_status() -> dict[str, Any] | None:
|
|
|
42
44
|
|
|
43
45
|
def load_rate_limits() -> RateLimits | None:
|
|
44
46
|
data = _read_status()
|
|
45
|
-
if data is None:
|
|
46
|
-
|
|
47
|
+
if data is None or not usage_client._has_complete_rate_limits(data):
|
|
48
|
+
snapshot = usage_client.read_quota_fallback()
|
|
49
|
+
if snapshot is not None:
|
|
50
|
+
return _snapshot_rate_limits(snapshot)
|
|
51
|
+
if data is None:
|
|
52
|
+
return None
|
|
47
53
|
|
|
48
54
|
rl = data.get("rate_limits") or {}
|
|
49
55
|
five = rl.get("five_hour") or {}
|
|
@@ -74,3 +80,18 @@ def load_rate_limits() -> RateLimits | None:
|
|
|
74
80
|
model=model_name,
|
|
75
81
|
updated_at=data.get("_received_at", ""),
|
|
76
82
|
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _snapshot_rate_limits(quota: usage_client.UsageSnapshot) -> RateLimits:
|
|
86
|
+
return RateLimits(
|
|
87
|
+
five_hour_pct=quota.current_percent,
|
|
88
|
+
seven_day_pct=quota.weekly_percent,
|
|
89
|
+
five_hour_resets_at=int(quota.current_reset_at)
|
|
90
|
+
if quota.current_reset_at is not None
|
|
91
|
+
else None,
|
|
92
|
+
seven_day_resets_at=int(quota.weekly_reset_at)
|
|
93
|
+
if quota.weekly_reset_at is not None
|
|
94
|
+
else None,
|
|
95
|
+
model="",
|
|
96
|
+
updated_at=datetime.fromtimestamp(quota.polled_at, timezone.utc).isoformat(),
|
|
97
|
+
)
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"awaiting_rate_limits": "請對 Claude Code 發送一句訊息以同步配額",
|
|
70
70
|
"hook_broken_not_installed": "⚠ 狀態列沒在更新,請按「設定狀態列」重新安裝",
|
|
71
71
|
"hook_broken_restart": "⚠ 狀態列沒在運作,請重開一次 Claude Code",
|
|
72
|
-
"usage_status_missing": "
|
|
72
|
+
"usage_status_missing": "尚無額度資料。請保持 Claude 桌面版開啟,或設定狀態列後啟動終端機的 Claude Code。",
|
|
73
73
|
"usage_status_missing_active": "⚠ 配額資料只能靠終端機的狀態列同步,桌面版/背景工作階段不會觸發,請開一次終端機的 Claude Code 來同步",
|
|
74
74
|
"usage_status_no_quota": "⚠ 狀態檔尚無配額資料,等 Claude Code 再刷新一次 statusLine",
|
|
75
75
|
"status_error": "錯誤 ({message})",
|
|
@@ -542,7 +542,9 @@
|
|
|
542
542
|
"quota_aware_enabled_msg": "已開啟額度感知模式。請重啟 Claude Code 套用。",
|
|
543
543
|
"quota_aware_disabled_msg": "已關閉額度感知模式。請重啟 Claude Code 套用。",
|
|
544
544
|
"quota_aware_action_failed": "無法切換額度感知模式。",
|
|
545
|
-
"claude_beginner_error": "新手模式:{error}"
|
|
545
|
+
"claude_beginner_error": "新手模式:{error}",
|
|
546
|
+
"claude_desktop_updated": "Claude 桌面版 · {minutes} 分鐘前更新",
|
|
547
|
+
"claude_desktop_stale": "Claude 桌面版 · 資料過期 · {minutes} 分鐘前更新"
|
|
546
548
|
},
|
|
547
549
|
"en": {
|
|
548
550
|
"panel_default_name": "Default",
|
|
@@ -614,7 +616,7 @@
|
|
|
614
616
|
"awaiting_rate_limits": "Send a message in Claude Code to sync your quota",
|
|
615
617
|
"hook_broken_not_installed": "⚠ Status line is not updating. Click \"Set Up Status Line\" to reinstall it.",
|
|
616
618
|
"hook_broken_restart": "⚠ Status line hook is not running. Restart Claude Code once.",
|
|
617
|
-
"usage_status_missing": "
|
|
619
|
+
"usage_status_missing": "No quota data yet. Keep Claude Desktop open, or set up the status line and start Claude Code in a terminal.",
|
|
618
620
|
"usage_status_missing_active": "⚠ Quota data only syncs via the terminal status line — the desktop app and headless sessions don't trigger it. Open Claude Code in a terminal once to sync.",
|
|
619
621
|
"usage_status_no_quota": "⚠ Status file has no quota data yet. Wait for Claude Code to refresh statusLine once.",
|
|
620
622
|
"status_error": "Error ({message})",
|
|
@@ -1087,7 +1089,9 @@
|
|
|
1087
1089
|
"quota_aware_enabled_msg": "Quota-Aware Mode enabled. Restart Claude Code to apply.",
|
|
1088
1090
|
"quota_aware_disabled_msg": "Quota-Aware Mode disabled. Restart Claude Code to apply.",
|
|
1089
1091
|
"quota_aware_action_failed": "Could not change Quota-Aware Mode.",
|
|
1090
|
-
"claude_beginner_error": "Beginner Mode: {error}"
|
|
1092
|
+
"claude_beginner_error": "Beginner Mode: {error}",
|
|
1093
|
+
"claude_desktop_updated": "Claude Desktop · updated {minutes}m ago",
|
|
1094
|
+
"claude_desktop_stale": "Claude Desktop · stale · updated {minutes}m ago"
|
|
1091
1095
|
},
|
|
1092
1096
|
"zh-CN": {
|
|
1093
1097
|
"panel_default_name": "默认",
|
|
@@ -1159,7 +1163,7 @@
|
|
|
1159
1163
|
"awaiting_rate_limits": "请对 Claude Code 发送一句消息以同步配额",
|
|
1160
1164
|
"hook_broken_not_installed": "⚠ 状态栏没有更新,请点「设置状态栏」重新安装",
|
|
1161
1165
|
"hook_broken_restart": "⚠ 状态栏没有运行,请重开一次 Claude Code",
|
|
1162
|
-
"usage_status_missing": "
|
|
1166
|
+
"usage_status_missing": "尚无额度数据。请保持 Claude 桌面版打开,或设置状态栏后启动终端中的 Claude Code。",
|
|
1163
1167
|
"usage_status_missing_active": "⚠ 配额数据只能靠终端的状态栏同步,桌面版/后台会话不会触发,请打开一次终端的 Claude Code 来同步",
|
|
1164
1168
|
"usage_status_no_quota": "⚠ 状态文件还没有配额数据,等 Claude Code 再刷新一次 statusLine",
|
|
1165
1169
|
"status_error": "错误 ({message})",
|
|
@@ -1632,7 +1636,9 @@
|
|
|
1632
1636
|
"quota_aware_enabled_msg": "已开启额度感知模式。请重启 Claude Code 应用。",
|
|
1633
1637
|
"quota_aware_disabled_msg": "已关闭额度感知模式。请重启 Claude Code 应用。",
|
|
1634
1638
|
"quota_aware_action_failed": "无法切换额度感知模式。",
|
|
1635
|
-
"claude_beginner_error": "新手模式:{error}"
|
|
1639
|
+
"claude_beginner_error": "新手模式:{error}",
|
|
1640
|
+
"claude_desktop_updated": "Claude 桌面版 · {minutes} 分钟前更新",
|
|
1641
|
+
"claude_desktop_stale": "Claude 桌面版 · 数据过期 · {minutes} 分钟前更新"
|
|
1636
1642
|
},
|
|
1637
1643
|
"ja": {
|
|
1638
1644
|
"panel_default_name": "デフォルト",
|
|
@@ -1704,7 +1710,7 @@
|
|
|
1704
1710
|
"awaiting_rate_limits": "Claude Code にメッセージを送信してクォータを同期してください",
|
|
1705
1711
|
"hook_broken_not_installed": "⚠ ステータスラインが更新されていません。「ステータスラインを設定」を押して再インストールしてください。",
|
|
1706
1712
|
"hook_broken_restart": "⚠ ステータスライン hook が動作していません。Claude Code を一度再起動してください。",
|
|
1707
|
-
"usage_status_missing": "
|
|
1713
|
+
"usage_status_missing": "利用枠データがありません。Claude デスクトップを開いたままにするか、ステータスラインを設定してターミナルの Claude Code を起動してください。",
|
|
1708
1714
|
"usage_status_missing_active": "⚠ クォータ情報はターミナルのステータスラインでのみ同期されます。デスクトップアプリやヘッドレスセッションでは同期されません。ターミナルで Claude Code を一度開いてください。",
|
|
1709
1715
|
"usage_status_no_quota": "⚠ ステータスファイルにクォータデータがまだありません。Claude Code が statusLine をもう一度更新するまでお待ちください。",
|
|
1710
1716
|
"status_error": "エラー ({message})",
|
|
@@ -2177,7 +2183,9 @@
|
|
|
2177
2183
|
"quota_aware_enabled_msg": "クォータ認識モードを有効にしました。Claude Code を再起動してください。",
|
|
2178
2184
|
"quota_aware_disabled_msg": "クォータ認識モードを無効にしました。Claude Code を再起動してください。",
|
|
2179
2185
|
"quota_aware_action_failed": "クォータ認識モードを切り替えられませんでした。",
|
|
2180
|
-
"claude_beginner_error": "初心者モード:{error}"
|
|
2186
|
+
"claude_beginner_error": "初心者モード:{error}",
|
|
2187
|
+
"claude_desktop_updated": "Claude デスクトップ · {minutes}分前に更新",
|
|
2188
|
+
"claude_desktop_stale": "Claude デスクトップ · 古いデータ · {minutes}分前に更新"
|
|
2181
2189
|
},
|
|
2182
2190
|
"ko": {
|
|
2183
2191
|
"panel_default_name": "기본",
|
|
@@ -2249,7 +2257,7 @@
|
|
|
2249
2257
|
"awaiting_rate_limits": "Claude Code에 메시지를 보내 할당량을 동기화하세요",
|
|
2250
2258
|
"hook_broken_not_installed": "⚠ 상태 표시줄이 업데이트되지 않습니다. \"상태 표시줄 설정\"을 눌러 다시 설치하세요.",
|
|
2251
2259
|
"hook_broken_restart": "⚠ 상태 표시줄 hook이 작동하지 않습니다. Claude Code를 한 번 다시 시작하세요.",
|
|
2252
|
-
"usage_status_missing": "
|
|
2260
|
+
"usage_status_missing": "사용 한도 데이터가 없습니다. Claude 데스크톱을 열어 두거나 상태 표시줄을 설정한 뒤 터미널에서 Claude Code를 실행하세요.",
|
|
2253
2261
|
"usage_status_missing_active": "⚠ 할당량 데이터는 터미널 상태 표시줄로만 동기화됩니다. 데스크톱 앱이나 헤드리스 세션에서는 동기화되지 않습니다. 터미널에서 Claude Code를 한 번 열어주세요.",
|
|
2254
2262
|
"usage_status_no_quota": "⚠ 상태 파일에 아직 할당량 데이터가 없습니다. Claude Code가 statusLine을 한 번 더 새로 고칠 때까지 기다리세요.",
|
|
2255
2263
|
"status_error": "오류 ({message})",
|
|
@@ -2722,6 +2730,8 @@
|
|
|
2722
2730
|
"quota_aware_enabled_msg": "할당량 인식 모드를 켰습니다. Claude Code를 다시 시작해 주세요.",
|
|
2723
2731
|
"quota_aware_disabled_msg": "할당량 인식 모드를 껐습니다. Claude Code를 다시 시작해 주세요.",
|
|
2724
2732
|
"quota_aware_action_failed": "할당량 인식 모드를 변경할 수 없습니다.",
|
|
2725
|
-
"claude_beginner_error": "초보자 모드: {error}"
|
|
2733
|
+
"claude_beginner_error": "초보자 모드: {error}",
|
|
2734
|
+
"claude_desktop_updated": "Claude 데스크톱 · {minutes}분 전 업데이트",
|
|
2735
|
+
"claude_desktop_stale": "Claude 데스크톱 · 오래된 데이터 · {minutes}분 전 업데이트"
|
|
2726
2736
|
}
|
|
2727
2737
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
|
|
3
|
+
"""Read a cache file while Chromium holds it open, without changing the file."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import ctypes as C
|
|
8
|
+
import sys
|
|
9
|
+
from ctypes import wintypes as W
|
|
10
|
+
from functools import lru_cache
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@lru_cache(maxsize=1)
|
|
15
|
+
def _kernel() -> C.CDLL:
|
|
16
|
+
if sys.platform != "win32":
|
|
17
|
+
raise OSError("Windows shared cache reader is unavailable")
|
|
18
|
+
kernel = C.WinDLL("kernel32", use_last_error=True)
|
|
19
|
+
kernel.CreateFileW.argtypes = [
|
|
20
|
+
W.LPCWSTR,
|
|
21
|
+
W.DWORD,
|
|
22
|
+
W.DWORD,
|
|
23
|
+
C.c_void_p,
|
|
24
|
+
W.DWORD,
|
|
25
|
+
W.DWORD,
|
|
26
|
+
C.c_void_p,
|
|
27
|
+
]
|
|
28
|
+
kernel.CreateFileW.restype = W.HANDLE
|
|
29
|
+
kernel.SetFilePointerEx.argtypes = [W.HANDLE, C.c_longlong, C.c_void_p, W.DWORD]
|
|
30
|
+
kernel.SetFilePointerEx.restype = W.BOOL
|
|
31
|
+
kernel.ReadFile.argtypes = [W.HANDLE, C.c_void_p, W.DWORD, C.POINTER(W.DWORD), C.c_void_p]
|
|
32
|
+
kernel.ReadFile.restype = W.BOOL
|
|
33
|
+
kernel.CloseHandle.argtypes = [W.HANDLE]
|
|
34
|
+
kernel.CloseHandle.restype = W.BOOL
|
|
35
|
+
return kernel
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def read_range(path: Path, offset: int, length: int) -> bytes:
|
|
39
|
+
if offset < 0 or length < 0 or length > 16 * 1024 * 1024:
|
|
40
|
+
raise ValueError("Invalid cache read bounds")
|
|
41
|
+
if sys.platform != "win32":
|
|
42
|
+
with path.open("rb") as file:
|
|
43
|
+
file.seek(offset)
|
|
44
|
+
return file.read(length)
|
|
45
|
+
kernel = _kernel()
|
|
46
|
+
# GENERIC_READ, FILE_SHARE_READ | WRITE | DELETE, OPEN_EXISTING. Chromium
|
|
47
|
+
# requests delete access; ordinary CRT opens do not always share that access.
|
|
48
|
+
handle = kernel.CreateFileW(str(path), 0x80000000, 7, None, 3, 0, None)
|
|
49
|
+
if handle == C.c_void_p(-1).value:
|
|
50
|
+
raise OSError("Cannot open the cache file for reading")
|
|
51
|
+
try:
|
|
52
|
+
if not kernel.SetFilePointerEx(handle, offset, None, 0):
|
|
53
|
+
raise OSError("Cannot seek in the cache file")
|
|
54
|
+
buffer = C.create_string_buffer(length)
|
|
55
|
+
count = W.DWORD()
|
|
56
|
+
if not kernel.ReadFile(handle, buffer, length, C.byref(count), None):
|
|
57
|
+
raise OSError("Cannot read the cache file")
|
|
58
|
+
return buffer.raw[: count.value]
|
|
59
|
+
finally:
|
|
60
|
+
kernel.CloseHandle(handle)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def read_bounded(path: Path, limit: int) -> bytes:
|
|
64
|
+
size = path.stat().st_size
|
|
65
|
+
if size > limit:
|
|
66
|
+
raise ValueError("Cache file exceeds the size limit")
|
|
67
|
+
return read_range(path, 0, size)
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
|
|
3
|
+
"""Read one local JSON response from Chromium's block-file HTTP cache.
|
|
4
|
+
|
|
5
|
+
Formats: Chromium net/disk_cache/blockfile/{disk_format,addr}.h and
|
|
6
|
+
net/http/http_response_info.cc. No cookies, credentials, or requests are used.
|
|
7
|
+
Unsupported formats and incomplete/in-flight entries return no response.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import re
|
|
14
|
+
import struct
|
|
15
|
+
import zlib
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import cast
|
|
19
|
+
|
|
20
|
+
from loaders._shared_cache_file import read_bounded, read_range
|
|
21
|
+
|
|
22
|
+
MAX_BODY = 1024 * 1024
|
|
23
|
+
MAX_WINDOW = 8 * 1024 * 1024
|
|
24
|
+
MAX_INDEX = 4 * 1024 * 1024
|
|
25
|
+
MAX_ENTRIES = 16 * 1024 * 1024
|
|
26
|
+
BLOCK_SIZES = {1: 36, 2: 256, 3: 1024, 4: 4096, 5: 8, 6: 256, 7: 256}
|
|
27
|
+
INDEX_HEADER = 368
|
|
28
|
+
BLOCK_HEADER = 8192
|
|
29
|
+
TIME_EPOCH = 11644473600
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class CachedResponse:
|
|
34
|
+
data: dict[str, object]
|
|
35
|
+
fetched_at: float
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _response_info(raw: bytes) -> tuple[float, bytes]:
|
|
39
|
+
if len(raw) < 28 or struct.unpack_from("<I", raw)[0] != len(raw) - 4:
|
|
40
|
+
raise ValueError("Invalid response info")
|
|
41
|
+
flags = struct.unpack_from("<I", raw, 4)[0]
|
|
42
|
+
if flags & 255 != 3 or flags & (1 << 12):
|
|
43
|
+
raise ValueError("Unsupported or truncated response")
|
|
44
|
+
extra = struct.unpack_from("<I", raw, 8)[0] if flags & (1 << 31) else 0
|
|
45
|
+
offset = 12 if flags & (1 << 31) else 8
|
|
46
|
+
fetched_at = struct.unpack_from("<q", raw, offset + 8)[0] / 1e6 - TIME_EPOCH
|
|
47
|
+
offset += 16 + (8 if extra & 4 else 0)
|
|
48
|
+
length = struct.unpack_from("<I", raw, offset)[0]
|
|
49
|
+
headers = raw[offset + 4 : offset + 4 + length]
|
|
50
|
+
if len(headers) != length or not re.match(rb"^HTTP/\S+ 200(?: |\x00)", headers):
|
|
51
|
+
raise ValueError("No successful response headers")
|
|
52
|
+
if b"application/json" not in headers.lower():
|
|
53
|
+
raise ValueError("Not a JSON response")
|
|
54
|
+
return fetched_at, headers
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _decode(raw: bytes, headers: bytes) -> dict[str, object]:
|
|
58
|
+
# Chrome may store a zstd frame even when the original HTTP response used
|
|
59
|
+
# another encoding. Detect the on-disk frame first.
|
|
60
|
+
if raw.startswith(b"\x28\xb5\x2f\xfd"):
|
|
61
|
+
try:
|
|
62
|
+
import zstandard
|
|
63
|
+
except (ImportError, OSError) as exc:
|
|
64
|
+
raise ValueError("Zstandard decoder is unavailable") from exc
|
|
65
|
+
try:
|
|
66
|
+
size = zstandard.frame_content_size(raw)
|
|
67
|
+
if size != zstandard.CONTENTSIZE_UNKNOWN and size > MAX_BODY:
|
|
68
|
+
raise ValueError("Response body exceeds the size limit")
|
|
69
|
+
if zstandard.get_frame_parameters(raw).window_size > MAX_WINDOW:
|
|
70
|
+
raise ValueError("Response compression window exceeds the size limit")
|
|
71
|
+
decoder = zstandard.ZstdDecompressor(max_window_size=MAX_WINDOW)
|
|
72
|
+
raw = decoder.decompress(raw, max_output_size=MAX_BODY, allow_extra_data=False)
|
|
73
|
+
except zstandard.ZstdError as exc:
|
|
74
|
+
raise ValueError("Invalid Zstandard response") from exc
|
|
75
|
+
elif raw.startswith(b"\x1f\x8b") or b"content-encoding: deflate" in headers.lower():
|
|
76
|
+
decoder_z = zlib.decompressobj(31 if raw.startswith(b"\x1f\x8b") else 15)
|
|
77
|
+
raw = decoder_z.decompress(raw, MAX_BODY + 1)
|
|
78
|
+
if not decoder_z.eof:
|
|
79
|
+
raise ValueError("Incomplete or oversized compressed response")
|
|
80
|
+
if len(raw) > MAX_BODY:
|
|
81
|
+
raise ValueError("Response body exceeds the size limit")
|
|
82
|
+
data = json.loads(raw)
|
|
83
|
+
if not isinstance(data, dict):
|
|
84
|
+
raise ValueError("Not a JSON object")
|
|
85
|
+
return cast(dict[str, object], data)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class _BlockCache:
|
|
89
|
+
def __init__(self, path: Path) -> None:
|
|
90
|
+
self.path = path
|
|
91
|
+
self.index = read_bounded(path / "index", MAX_INDEX)
|
|
92
|
+
magic, version = struct.unpack_from("<II", self.index)
|
|
93
|
+
if magic != 0xC103CAC3 or version not in {0x20000, 0x20001, 0x30000}:
|
|
94
|
+
raise ValueError("Unsupported cache index")
|
|
95
|
+
self.table_len = struct.unpack_from("<i", self.index, 28)[0] or 65536
|
|
96
|
+
if (
|
|
97
|
+
self.table_len <= 0
|
|
98
|
+
or self.table_len & (self.table_len - 1)
|
|
99
|
+
or len(self.index) != INDEX_HEADER + 4 * self.table_len
|
|
100
|
+
):
|
|
101
|
+
raise ValueError("Invalid cache index size")
|
|
102
|
+
|
|
103
|
+
def stream(self, address: int, length: int) -> bytes:
|
|
104
|
+
if not address & 0x80000000 or not 0 < length <= MAX_BODY:
|
|
105
|
+
raise ValueError("Invalid cache stream")
|
|
106
|
+
kind = (address >> 28) & 7
|
|
107
|
+
if kind == 0:
|
|
108
|
+
raw = read_range(self.path / f"f_{address & 0x0FFFFFFF:06x}", 0, length)
|
|
109
|
+
else:
|
|
110
|
+
size = BLOCK_SIZES[kind]
|
|
111
|
+
if length > size * (((address >> 24) & 3) + 1):
|
|
112
|
+
raise ValueError("Invalid block allocation")
|
|
113
|
+
offset = BLOCK_HEADER + (address & 65535) * size
|
|
114
|
+
raw = read_range(self.path / f"data_{(address >> 16) & 255}", offset, length)
|
|
115
|
+
if len(raw) != length:
|
|
116
|
+
raise ValueError("Incomplete cache stream")
|
|
117
|
+
return raw
|
|
118
|
+
|
|
119
|
+
def _active(self, entry: bytes, file: int, block: int) -> bool:
|
|
120
|
+
bucket = struct.unpack_from("<I", entry)[0] & (self.table_len - 1)
|
|
121
|
+
address = struct.unpack_from("<I", self.index, INDEX_HEADER + bucket * 4)[0]
|
|
122
|
+
visited: set[int] = set()
|
|
123
|
+
for _ in range(128):
|
|
124
|
+
if not address or address in visited or (address >> 28) & 7 not in {2, 6}:
|
|
125
|
+
return False
|
|
126
|
+
visited.add(address)
|
|
127
|
+
if ((address >> 16) & 255, address & 65535) == (file, block):
|
|
128
|
+
return True
|
|
129
|
+
address = struct.unpack_from("<I", self.stream(address, 256), 4)[0]
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
def responses(self, url: bytes, earliest: float, now: float) -> list[CachedResponse]:
|
|
133
|
+
files = {
|
|
134
|
+
(address >> 16) & 255
|
|
135
|
+
for (address,) in struct.iter_unpack("<I", self.index[INDEX_HEADER:])
|
|
136
|
+
if (address >> 28) & 7 in {2, 6}
|
|
137
|
+
}
|
|
138
|
+
results = []
|
|
139
|
+
for file in sorted(files):
|
|
140
|
+
try:
|
|
141
|
+
raw = read_bounded(self.path / f"data_{file}", MAX_ENTRIES)
|
|
142
|
+
except (OSError, ValueError):
|
|
143
|
+
continue
|
|
144
|
+
if (
|
|
145
|
+
len(raw) < BLOCK_HEADER
|
|
146
|
+
or struct.unpack_from("<II", raw) != (0xC104CAC3, 0x20000)
|
|
147
|
+
or struct.unpack_from("<H", raw, 8)[0] != file
|
|
148
|
+
or struct.unpack_from("<i", raw, 12)[0] != 256
|
|
149
|
+
):
|
|
150
|
+
continue
|
|
151
|
+
for offset in range(BLOCK_HEADER, len(raw) - 255, 256):
|
|
152
|
+
entry = raw[offset : offset + 256]
|
|
153
|
+
try:
|
|
154
|
+
response = self._response(raw, offset, entry, file, url, earliest, now)
|
|
155
|
+
except (
|
|
156
|
+
OSError,
|
|
157
|
+
ValueError,
|
|
158
|
+
struct.error,
|
|
159
|
+
zlib.error,
|
|
160
|
+
RecursionError,
|
|
161
|
+
):
|
|
162
|
+
continue
|
|
163
|
+
if response is not None:
|
|
164
|
+
results.append(response)
|
|
165
|
+
return results
|
|
166
|
+
|
|
167
|
+
def _response(
|
|
168
|
+
self,
|
|
169
|
+
raw: bytes,
|
|
170
|
+
offset: int,
|
|
171
|
+
entry: bytes,
|
|
172
|
+
file: int,
|
|
173
|
+
url: bytes,
|
|
174
|
+
earliest: float,
|
|
175
|
+
now: float,
|
|
176
|
+
) -> CachedResponse | None:
|
|
177
|
+
if struct.unpack_from("<i", entry, 20)[0] != 0:
|
|
178
|
+
return None
|
|
179
|
+
key_len, long_key = struct.unpack_from("<iI", entry, 32)
|
|
180
|
+
if not 0 < key_len <= 4096:
|
|
181
|
+
return None
|
|
182
|
+
key = (
|
|
183
|
+
self.stream(long_key, key_len) if long_key else raw[offset + 96 : offset + 96 + key_len]
|
|
184
|
+
)
|
|
185
|
+
# Chromium prefixes keys with upload identifiers (e.g. "1/0/") and
|
|
186
|
+
# may also add a network-isolation key before the request URL.
|
|
187
|
+
if key[key.rfind(b"https://") :].partition(b"?")[0] != url:
|
|
188
|
+
return None
|
|
189
|
+
if not self._active(entry, file, (offset - BLOCK_HEADER) // 256):
|
|
190
|
+
return None
|
|
191
|
+
ranking = self.stream(struct.unpack_from("<I", entry, 8)[0], 36)
|
|
192
|
+
if struct.unpack_from("<i", ranking, 28)[0] != 0:
|
|
193
|
+
return None
|
|
194
|
+
sizes = struct.unpack_from("<4i", entry, 40)
|
|
195
|
+
addresses = struct.unpack_from("<4I", entry, 56)
|
|
196
|
+
metadata = self.stream(addresses[0], sizes[0])
|
|
197
|
+
fetched_at, headers = _response_info(metadata)
|
|
198
|
+
if not max(earliest, now - 7200) <= fetched_at <= now + 60:
|
|
199
|
+
return None
|
|
200
|
+
body = self.stream(addresses[1], sizes[1])
|
|
201
|
+
# Cache updates are concurrent; refuse entries whose metadata changed
|
|
202
|
+
# while reading. No files are copied, modified, or locked exclusively.
|
|
203
|
+
if read_range(self.path / f"data_{file}", offset, 96) != entry[:96]:
|
|
204
|
+
return None
|
|
205
|
+
if self.stream(addresses[0], sizes[0]) != metadata:
|
|
206
|
+
return None
|
|
207
|
+
return CachedResponse(_decode(body, headers), fetched_at)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def load_cached_json(path: Path, url: bytes, earliest: float, now: float) -> CachedResponse | None:
|
|
211
|
+
try:
|
|
212
|
+
responses = _BlockCache(path).responses(url, earliest, now)
|
|
213
|
+
except (OSError, ValueError, struct.error):
|
|
214
|
+
return None
|
|
215
|
+
return max(responses, key=lambda response: response.fetched_at, default=None)
|