usage-cli 0.34.11__tar.gz → 0.34.13__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.34.11/usage_cli.egg-info → usage_cli-0.34.13}/PKG-INFO +1 -1
- {usage_cli-0.34.11 → usage_cli-0.34.13}/adapters/claude.py +3 -4
- {usage_cli-0.34.11 → usage_cli-0.34.13}/adapters/rate_limits.py +2 -1
- {usage_cli-0.34.11 → usage_cli-0.34.13}/analyzer/blocks.py +4 -1
- {usage_cli-0.34.11 → usage_cli-0.34.13}/analyzer/reporter.py +4 -1
- {usage_cli-0.34.11 → usage_cli-0.34.13}/i18n.json +44 -14
- {usage_cli-0.34.11 → usage_cli-0.34.13}/installer/login_item.py +3 -2
- {usage_cli-0.34.11 → usage_cli-0.34.13}/installer/session_hooks.py +21 -10
- {usage_cli-0.34.11 → usage_cli-0.34.13}/installer/setup_hook.py +64 -33
- {usage_cli-0.34.11 → usage_cli-0.34.13}/installer/statusline_settings.py +8 -3
- {usage_cli-0.34.11 → usage_cli-0.34.13}/loaders/agy_disk_cache.py +3 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/loaders/agy_loader.py +25 -6
- {usage_cli-0.34.11 → usage_cli-0.34.13}/loaders/agy_quota_probe.py +3 -9
- {usage_cli-0.34.11 → usage_cli-0.34.13}/loaders/codex_loader.py +11 -4
- {usage_cli-0.34.11 → usage_cli-0.34.13}/loaders/muse_quota_probe.py +82 -31
- {usage_cli-0.34.11 → usage_cli-0.34.13}/main.py +9 -1
- {usage_cli-0.34.11 → usage_cli-0.34.13}/menubar/actions.py +23 -7
- {usage_cli-0.34.11 → usage_cli-0.34.13}/menubar/app.py +9 -10
- {usage_cli-0.34.11 → usage_cli-0.34.13}/menubar/fsevents_watch.py +7 -5
- {usage_cli-0.34.11 → usage_cli-0.34.13}/menubar/menu.py +62 -9
- {usage_cli-0.34.11 → usage_cli-0.34.13}/menubar/notify.py +24 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/menubar/prefs.py +24 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/menubar/state.py +3 -5
- {usage_cli-0.34.11 → usage_cli-0.34.13}/menubar/title.py +2 -2
- {usage_cli-0.34.11 → usage_cli-0.34.13}/pyproject.toml +1 -1
- {usage_cli-0.34.11 → usage_cli-0.34.13}/quota/agy_window_keeper.py +3 -9
- {usage_cli-0.34.11 → usage_cli-0.34.13}/quota/codex_window_keeper.py +3 -9
- {usage_cli-0.34.11 → usage_cli-0.34.13}/quota/notifications.py +5 -1
- {usage_cli-0.34.11 → usage_cli-0.34.13}/quota/window_keeper.py +3 -9
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_adapters_claude.py +22 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_adapters_rate_limits.py +17 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_agy_loader.py +22 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_agy_quota_probe.py +19 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_agy_window_keeper.py +44 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_analyzer_blocks.py +20 -0
- usage_cli-0.34.13/tests/test_check_release_version.py +47 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_check_upstream.py +8 -1
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_claude_expired_windows.py +18 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_claude_pane.py +80 -1
- usage_cli-0.34.13/tests/test_cli_lookup.py +123 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_codex_loader.py +44 -1
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_codex_window_keeper.py +20 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_fsevents_watch.py +27 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_html_report_snapshot.py +8 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_login_item.py +4 -2
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_menubar.py +284 -8
- usage_cli-0.34.13/tests/test_menubar_permission.py +136 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_menubar_state.py +38 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_muse_quota_probe.py +99 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_quota_aware.py +67 -0
- usage_cli-0.34.13/tests/test_quota_threshold_menu.py +202 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_report_filter_js.py +54 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_reporter_build_data.py +55 -1
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_session_resume.py +139 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_setup_hook.py +214 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_ui_tables.py +99 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_usage_cli.py +52 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_usage_statusline_agy.py +5 -1
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_window_keeper.py +20 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_wintray.py +37 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/ui/html_report.py +1 -1
- {usage_cli-0.34.11 → usage_cli-0.34.13}/ui/report_filter.py +7 -5
- {usage_cli-0.34.11 → usage_cli-0.34.13}/ui/tables.py +17 -9
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_app/cli.py +12 -3
- {usage_cli-0.34.11 → usage_cli-0.34.13/usage_cli.egg-info}/PKG-INFO +1 -1
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_cli.egg-info/SOURCES.txt +5 -0
- usage_cli-0.34.13/usage_common/cli_lookup.py +62 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_common/pricing.py +6 -1
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_hooks/usage_quota_aware.py +38 -17
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_hooks/usage_session_resume.py +36 -5
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_hooks/usage_statusline.py +12 -2
- {usage_cli-0.34.11 → usage_cli-0.34.13}/wintray/app.py +37 -7
- {usage_cli-0.34.11 → usage_cli-0.34.13}/wintray/menu.py +23 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/LICENSE +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/README.md +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/THIRD_PARTY_NOTICES.md +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/adapters/__init__.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/adapters/agy.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/adapters/codex.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/adapters/grok.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/adapters/muse.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/adapters/registry.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/adapters/types.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/analyzer/__init__.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/analyzer/aggregator.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/analyzer/diagnoser.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/analyzer/diagnosis_snapshot.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/analyzer/dispatch_ledger.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/analyzer/history_persist.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/analyzer/insights.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/analyzer/ledger_backfill.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/analyzer/persona_loader.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/analyzer/subscription.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/analyzer/usage_snapshot.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/installer/__init__.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/installer/claude_pane.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/installer/codex_hook_trust.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/loaders/__init__.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/loaders/_shared_cache_file.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/loaders/cache_quarantine.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/loaders/chromium_cache.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/loaders/claude_desktop.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/loaders/claude_paths.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/loaders/claude_usage.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/loaders/codex_disk_cache.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/loaders/codex_events.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/loaders/codex_fork_replay.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/loaders/codex_paths.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/loaders/disk_cache_common.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/loaders/disk_cache_lifecycle.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/loaders/grok_loader.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/loaders/grok_quota_probe.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/loaders/history_disk_cache.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/loaders/history_loader.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/loaders/jsonl_limits.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/loaders/jsonl_utils.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/loaders/muse_loader.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/menubar/__init__.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/menubar/agy.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/menubar/chrome.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/menubar/grok.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/menubar/manual_refresh.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/menubar/muse.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/menubar/new_badge.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/menubar/popover.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/menubar/refresh.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/menubar/update.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/menubar/whats_new.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/panels/__init__.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/panels/base.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/panels/catalog.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/panels/dynamic_height.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/panels/panel_scale.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/panels/panel_window.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/panels/panel_window_state.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/panels/payload.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/panels/web_panel.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/panels/window_drag.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/quota/__init__.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/quota/burn_rate.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/quota/keeper_outcome.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/quota/quota_snapshot.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/quota/status_payload.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/quota/usage_rate.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/setup.cfg +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/setup_app.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_adapters_codex.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_aggregator.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_agy_adapter.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_agy_disk_cache.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_analyzer_pipeline.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_app_version.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_burn_rate.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_cache_quarantine.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_chromium_cache.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_claude_desktop.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_claude_paths.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_codex_consistency.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_codex_hook_trust.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_compact_cards.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_desktop_fresh_cache.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_desktop_quota_display.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_desktop_quota_review.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_diagnoser.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_disk_cache_shards.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_dispatch_ledger.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_doctor.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_dynamic_height.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_grok_loader.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_grok_quota_probe.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_history_loader.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_history_persist.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_hook_scripts_are_standalone.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_i18n_format_fallback.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_i18n_key_parity.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_i18n_packaged_path.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_insights.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_jsonl_utils.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_keeper_menu.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_keeper_outcome.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_ledger_backfill.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_main.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_manual_refresh.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_menubar_agy.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_menubar_grok.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_menubar_muse.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_menubar_notify.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_menubar_prefs.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_menubar_update.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_muse_coverage.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_muse_loader.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_muse_review_regressions.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_new_badge.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_packaged_resources.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_packaging.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_panel_footer_layout.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_panel_scale.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_panel_window_state.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_panels.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_panels_lazy_import.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_persona_loader.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_pricing.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_project_resolver.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_quota_label.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_report_chart_redesign.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_report_cold_read.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_report_daily_chart.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_report_headings.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_report_share_mask.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_service_status.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_setup_app.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_setup_hook_coexistence.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_setup_hook_resume.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_setup_hook_terse.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_subprocess_utils.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_subscription.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_taskbar_menu.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_taskbar_obstacles.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_time_utils.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_tui.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_tui_sprite.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_update_checker.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_update_gate.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_update_release_notes.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_usage_client.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_usage_diagnosis_snapshot.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_usage_dir_sweeper.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_usage_lang.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_usage_logging.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_usage_notifications.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_usage_rate.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_usage_snapshot.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_usage_snapshot_replay.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_usage_statusline.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_usage_statusline_forwarder.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_usage_statusline_grok.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_usage_terse_mode.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_usage_terse_reminder.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_web_panel_payload.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_whats_new.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_wheel_packaging.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_win_login_item.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tests/test_windows_manifest.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tui/__init__.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tui/app.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/tui/sprite.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/ui/__init__.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/ui/report_charts.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/ui/report_daily_chart.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/ui/report_ledger.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/ui/report_scripts.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/ui/report_styles.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/updates/__init__.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/updates/checker.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/updates/gate.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/updates/release_notes.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/updates/whats_new.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_app/__init__.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_app/doctor.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_cli.egg-info/dependency_links.txt +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_cli.egg-info/entry_points.txt +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_cli.egg-info/requires.txt +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_cli.egg-info/top_level.txt +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_common/__init__.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_common/app_version.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_common/i18n.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_common/prefs.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_common/project_resolver.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_common/service_status.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_common/subprocess_utils.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_common/time_utils.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_common/usage_dir_sweeper.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_common/usage_lang.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_common/usage_logging.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_hooks/__init__.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_hooks/usage_statusline_agy.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_hooks/usage_statusline_forwarder.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_hooks/usage_statusline_grok.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_hooks/usage_terse_mode.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/usage_hooks/usage_terse_reminder.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/wintray/__init__.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/wintray/login_item.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/wintray/prefs.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/wintray/quota_label.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/wintray/taskbar_menu.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/wintray/taskbar_obstacles.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/wintray/taskbar_overlay.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/wintray/watch.py +0 -0
- {usage_cli-0.34.11 → usage_cli-0.34.13}/wintray/whats_new.py +0 -0
|
@@ -16,6 +16,7 @@ from typing import Any
|
|
|
16
16
|
from loaders.claude_paths import claude_config_dirs
|
|
17
17
|
from loaders.jsonl_utils import iter_jsonl_dicts
|
|
18
18
|
from usage_common.project_resolver import resolve_project_name
|
|
19
|
+
from usage_common.time_utils import parse_optional_iso8601_utc
|
|
19
20
|
|
|
20
21
|
from .types import AgentInfo
|
|
21
22
|
|
|
@@ -190,10 +191,8 @@ def _parse_assistant_entry(data: dict[str, Any], project: str) -> list[UsageEntr
|
|
|
190
191
|
else 0
|
|
191
192
|
)
|
|
192
193
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
ts = datetime.fromisoformat(timestamp_str.replace("Z", "+00:00"))
|
|
196
|
-
except (ValueError, AttributeError):
|
|
194
|
+
ts = parse_optional_iso8601_utc(data.get("timestamp", ""))
|
|
195
|
+
if ts is None:
|
|
197
196
|
return []
|
|
198
197
|
|
|
199
198
|
message_id = message.get("id", "")
|
|
@@ -36,7 +36,8 @@ def _read_status() -> dict[str, Any] | None:
|
|
|
36
36
|
try:
|
|
37
37
|
with open(path, "r", encoding="utf-8") as f:
|
|
38
38
|
data: dict[str, Any] = json.load(f)
|
|
39
|
-
|
|
39
|
+
if isinstance(data, dict):
|
|
40
|
+
return data
|
|
40
41
|
except (json.JSONDecodeError, OSError, UnicodeDecodeError):
|
|
41
42
|
continue
|
|
42
43
|
return None
|
|
@@ -56,7 +56,10 @@ def analyze_blocks(entries: list[UsageEntry]) -> list[SessionBlock]:
|
|
|
56
56
|
block.is_active = True
|
|
57
57
|
elapsed = (now - block.start_time).total_seconds() / 60
|
|
58
58
|
if elapsed > 0:
|
|
59
|
-
|
|
59
|
+
active_tokens = (
|
|
60
|
+
block.input_tokens + block.output_tokens + block.cache_creation_tokens
|
|
61
|
+
)
|
|
62
|
+
block.burn_rate = active_tokens / elapsed
|
|
60
63
|
|
|
61
64
|
return blocks
|
|
62
65
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import contextlib
|
|
10
|
+
from bisect import bisect_right
|
|
10
11
|
from collections import defaultdict
|
|
11
12
|
from concurrent.futures import ThreadPoolExecutor
|
|
12
13
|
from dataclasses import dataclass
|
|
@@ -146,6 +147,7 @@ class CubeModelData(TypedDict):
|
|
|
146
147
|
|
|
147
148
|
|
|
148
149
|
class CubeReportData(TypedDict):
|
|
150
|
+
today: NotRequired[str]
|
|
149
151
|
dates: list[str]
|
|
150
152
|
agents: list[CubeAgentData]
|
|
151
153
|
models: list[CubeModelData]
|
|
@@ -1236,6 +1238,7 @@ def build_report_data(agents: list[AgentInfo], period: str = "month") -> ReportD
|
|
|
1236
1238
|
)
|
|
1237
1239
|
cube_rows.sort(key=lambda row: (row[0], row[1], row[2], row[3]))
|
|
1238
1240
|
cube: CubeReportData = {
|
|
1241
|
+
"today": today.isoformat(),
|
|
1239
1242
|
"dates": cube_dates,
|
|
1240
1243
|
"agents": cube_agents,
|
|
1241
1244
|
"models": cube_models,
|
|
@@ -1293,7 +1296,7 @@ def build_report_data(agents: list[AgentInfo], period: str = "month") -> ReportD
|
|
|
1293
1296
|
start_time = _session_start_text(session.start_time)
|
|
1294
1297
|
session_rows.append(
|
|
1295
1298
|
{
|
|
1296
|
-
"date_idx":
|
|
1299
|
+
"date_idx": max(0, bisect_right(cube_dates, start_time[:10]) - 1),
|
|
1297
1300
|
"project_idx": project_indices[session.project or "unknown"],
|
|
1298
1301
|
"model_idx": model_indices[session.model or "unknown"],
|
|
1299
1302
|
"start_time": start_time,
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"statusline_sync_menu": "狀態列同步額度",
|
|
38
38
|
"statusline_sync_tooltip": "從 Claude Code 的狀態列讀取額度。關掉後,Claude 的額度就不會再更新。",
|
|
39
39
|
"launch_at_login": "開機自動啟動",
|
|
40
|
+
"launch_at_login_failed": "無法設定開機自動啟動:{error}",
|
|
40
41
|
"reset_panel_position": "重設面板位置",
|
|
41
42
|
"hide_sections_menu": "隱藏區塊",
|
|
42
43
|
"tray_provider_menu": "系統匣顯示來源",
|
|
@@ -44,6 +45,10 @@
|
|
|
44
45
|
"quota_label_format": "{provider}: {value}%",
|
|
45
46
|
"quota_label_unknown": "{provider}: --",
|
|
46
47
|
"quota_notifications_menu": "用量提醒通知",
|
|
48
|
+
"quota_notifications_toggle": "開啟通知",
|
|
49
|
+
"quota_notifications_permission_denied": "系統已關閉 usage 的通知 — 打開系統設定…",
|
|
50
|
+
"quota_threshold_header": "提醒門檻",
|
|
51
|
+
"quota_threshold_item": "用到 {percent}%",
|
|
47
52
|
"window_keeper_menu": "自動啟動 5 小時計時",
|
|
48
53
|
"window_keeper_tooltip": "5 小時額度重置後,自動各送一則極小訊息(Claude 用 Haiku、agy 用 Gemini 3.8 Flash Low、Codex 用最省成本模型),讓下一輪 5 小時立刻開始計時(會消耗極少量額度)",
|
|
49
54
|
"window_keeper_sleep_title": "🐾 自動啟動 5 小時計時已開啟",
|
|
@@ -130,8 +135,8 @@
|
|
|
130
135
|
"grok_stale_minutes": "約 {minutes} 分鐘前",
|
|
131
136
|
"grok_stale_hours": "約 {hours} 小時前",
|
|
132
137
|
"grok_stale_tooltip": "資料來自 Grok CLI 本機 log,不是即時 API;請最近執行過 grok 指令後再查看,才會更新。",
|
|
133
|
-
"codex_stale_minutes": "約 {minutes}
|
|
134
|
-
"codex_stale_hours": "約 {hours}
|
|
138
|
+
"codex_stale_minutes": "約 {minutes} 分鐘前更新",
|
|
139
|
+
"codex_stale_hours": "約 {hours} 小時前更新",
|
|
135
140
|
"codex_stale_tooltip": "Claude 有即時回報所以很準;Codex 沒有,usage 會讀它本機偶爾留下的紀錄,可能比實際略舊。繼續在終端機跑 Codex 就會自動更新。",
|
|
136
141
|
"history_load_error_file": "讀不到使用紀錄檔",
|
|
137
142
|
"history_load_error_parse": "使用紀錄格式異常",
|
|
@@ -143,7 +148,7 @@
|
|
|
143
148
|
"project_range_7d": "7 日",
|
|
144
149
|
"project_range_30d": "月",
|
|
145
150
|
"project_range_all": "全部",
|
|
146
|
-
"session_label": "
|
|
151
|
+
"session_label": "5 小時",
|
|
147
152
|
"weekly_label": "本週",
|
|
148
153
|
"monthly_label": "本月",
|
|
149
154
|
"projects_empty": "無記錄",
|
|
@@ -515,6 +520,7 @@
|
|
|
515
520
|
"claude_pane_remaining": "剩",
|
|
516
521
|
"claude_pane_reset_done": "已重置",
|
|
517
522
|
"claude_pane_hours_ago": "{count}時前",
|
|
523
|
+
"claude_pane_updated_ago": "{ago}更新",
|
|
518
524
|
"update_check_failed_offline": "連不上 GitHub,請確認網路後再試。",
|
|
519
525
|
"update_check_failed_rate_limited": "GitHub 暫時拒絕請求,可能太頻繁了,稍後再試。",
|
|
520
526
|
"update_check_failed_server": "GitHub 回應錯誤,稍後再試。",
|
|
@@ -610,6 +616,7 @@
|
|
|
610
616
|
"statusline_sync_menu": "Sync quota via status line",
|
|
611
617
|
"statusline_sync_tooltip": "Reads the Claude quota from Claude Code's status line. Turn it off and the Claude quota stops updating.",
|
|
612
618
|
"launch_at_login": "Launch at Login",
|
|
619
|
+
"launch_at_login_failed": "Unable to set launch at login: {error}",
|
|
613
620
|
"reset_panel_position": "Reset panel position",
|
|
614
621
|
"hide_sections_menu": "Hide Sections",
|
|
615
622
|
"tray_provider_menu": "Tray Display Source",
|
|
@@ -617,6 +624,10 @@
|
|
|
617
624
|
"quota_label_format": "{provider}: {value}%",
|
|
618
625
|
"quota_label_unknown": "{provider}: --",
|
|
619
626
|
"quota_notifications_menu": "Usage Alert Notifications",
|
|
627
|
+
"quota_notifications_toggle": "Enable notifications",
|
|
628
|
+
"quota_notifications_permission_denied": "System notifications for usage are off — Open System Settings…",
|
|
629
|
+
"quota_threshold_header": "Alert thresholds",
|
|
630
|
+
"quota_threshold_item": "At {percent}%",
|
|
620
631
|
"window_keeper_menu": "Auto-start 5-hour Session",
|
|
621
632
|
"window_keeper_tooltip": "After your 5-hour quotas reset, automatically send one tiny message to each tool (Claude with Haiku, agy with Gemini 3.8 Flash Low, Codex with its cheapest model) so the next 5-hour sessions start counting right away (uses a tiny bit of quota).",
|
|
622
633
|
"window_keeper_sleep_title": "🐾 Auto-start 5-hour Session is on",
|
|
@@ -703,8 +714,8 @@
|
|
|
703
714
|
"grok_stale_minutes": "about {minutes} minutes ago",
|
|
704
715
|
"grok_stale_hours": "about {hours} hours ago",
|
|
705
716
|
"grok_stale_tooltip": "Data comes from Grok CLI's local log, not a live API. Run a grok command recently for it to update.",
|
|
706
|
-
"codex_stale_minutes": "about {minutes}
|
|
707
|
-
"codex_stale_hours": "about {hours}
|
|
717
|
+
"codex_stale_minutes": "updated about {minutes} min ago",
|
|
718
|
+
"codex_stale_hours": "updated about {hours} hr ago",
|
|
708
719
|
"codex_stale_tooltip": "Claude is accurate because it reports in real time; Codex does not, so usage reads the local records it leaves occasionally, which may be slightly older than reality. Keep running Codex in the terminal and it will update automatically.",
|
|
709
720
|
"history_load_error_file": "Couldn't read usage files",
|
|
710
721
|
"history_load_error_parse": "Usage data format issue",
|
|
@@ -1088,6 +1099,7 @@
|
|
|
1088
1099
|
"claude_pane_remaining": "left",
|
|
1089
1100
|
"claude_pane_reset_done": "Reset",
|
|
1090
1101
|
"claude_pane_hours_ago": "{count}h ago",
|
|
1102
|
+
"claude_pane_updated_ago": "updated {ago}",
|
|
1091
1103
|
"update_check_failed_offline": "Cannot reach GitHub. Check your connection and try again.",
|
|
1092
1104
|
"update_check_failed_rate_limited": "GitHub is limiting requests. Try again later.",
|
|
1093
1105
|
"update_check_failed_server": "GitHub returned an error. Try again later.",
|
|
@@ -1183,6 +1195,7 @@
|
|
|
1183
1195
|
"statusline_sync_menu": "状态栏同步额度",
|
|
1184
1196
|
"statusline_sync_tooltip": "从 Claude Code 的状态栏读取额度。关闭后,Claude 的额度就不会再更新。",
|
|
1185
1197
|
"launch_at_login": "开机自动启动",
|
|
1198
|
+
"launch_at_login_failed": "无法设置开机自动启动:{error}",
|
|
1186
1199
|
"reset_panel_position": "重设面板位置",
|
|
1187
1200
|
"hide_sections_menu": "隐藏区块",
|
|
1188
1201
|
"tray_provider_menu": "托盘显示来源",
|
|
@@ -1190,6 +1203,10 @@
|
|
|
1190
1203
|
"quota_label_format": "{provider}: {value}%",
|
|
1191
1204
|
"quota_label_unknown": "{provider}: --",
|
|
1192
1205
|
"quota_notifications_menu": "用量提醒通知",
|
|
1206
|
+
"quota_notifications_toggle": "开启通知",
|
|
1207
|
+
"quota_notifications_permission_denied": "系统已关闭 usage 的通知 — 打开系统设置…",
|
|
1208
|
+
"quota_threshold_header": "提醒阈值",
|
|
1209
|
+
"quota_threshold_item": "用到 {percent}%",
|
|
1193
1210
|
"window_keeper_menu": "自动启动 5 小时计时",
|
|
1194
1211
|
"window_keeper_tooltip": "5 小时额度重置后,自动各发送一则极小消息(Claude 用 Haiku、agy 用 Gemini 3.8 Flash Low、Codex 用最省成本模型),让下一轮 5 小时立刻开始计时(会消耗极少量额度)",
|
|
1195
1212
|
"window_keeper_sleep_title": "🐾 已开启自动启动 5 小时计时",
|
|
@@ -1276,8 +1293,8 @@
|
|
|
1276
1293
|
"grok_stale_minutes": "约 {minutes} 分钟前",
|
|
1277
1294
|
"grok_stale_hours": "约 {hours} 小时前",
|
|
1278
1295
|
"grok_stale_tooltip": "资料来自 Grok CLI 本地日志,不是即时 API;请最近运行过 grok 指令后再查看,才会更新。",
|
|
1279
|
-
"codex_stale_minutes": "约 {minutes}
|
|
1280
|
-
"codex_stale_hours": "约 {hours}
|
|
1296
|
+
"codex_stale_minutes": "约 {minutes} 分钟前更新",
|
|
1297
|
+
"codex_stale_hours": "约 {hours} 小时前更新",
|
|
1281
1298
|
"codex_stale_tooltip": "Claude 有实时回报所以很准;Codex 没有,usage 会读取它本机偶尔留下的记录,可能比实际略旧。继续在终端运行 Codex 就会自动更新。",
|
|
1282
1299
|
"history_load_error_file": "读不到使用记录文件",
|
|
1283
1300
|
"history_load_error_parse": "使用记录格式异常",
|
|
@@ -1289,7 +1306,7 @@
|
|
|
1289
1306
|
"project_range_7d": "7 日",
|
|
1290
1307
|
"project_range_30d": "月",
|
|
1291
1308
|
"project_range_all": "全部",
|
|
1292
|
-
"session_label": "
|
|
1309
|
+
"session_label": "5 小时",
|
|
1293
1310
|
"weekly_label": "本周",
|
|
1294
1311
|
"monthly_label": "本月",
|
|
1295
1312
|
"projects_empty": "无记录",
|
|
@@ -1661,6 +1678,7 @@
|
|
|
1661
1678
|
"claude_pane_remaining": "剩",
|
|
1662
1679
|
"claude_pane_reset_done": "已重置",
|
|
1663
1680
|
"claude_pane_hours_ago": "{count}时前",
|
|
1681
|
+
"claude_pane_updated_ago": "{ago}更新",
|
|
1664
1682
|
"update_check_failed_offline": "连不上 GitHub,请确认网络后再试。",
|
|
1665
1683
|
"update_check_failed_rate_limited": "GitHub 暂时拒绝请求,可能太频繁了,稍后再试。",
|
|
1666
1684
|
"update_check_failed_server": "GitHub 响应错误,稍后再试。",
|
|
@@ -1756,6 +1774,7 @@
|
|
|
1756
1774
|
"statusline_sync_menu": "ステータスラインで使用量を同期",
|
|
1757
1775
|
"statusline_sync_tooltip": "Claude Code のステータスラインから使用量を読み取ります。オフにすると Claude の使用量は更新されなくなります。",
|
|
1758
1776
|
"launch_at_login": "ログイン時に起動",
|
|
1777
|
+
"launch_at_login_failed": "ログイン時の起動を設定できません:{error}",
|
|
1759
1778
|
"reset_panel_position": "パネルの位置をリセット",
|
|
1760
1779
|
"hide_sections_menu": "セクションを非表示",
|
|
1761
1780
|
"tray_provider_menu": "トレイの表示元",
|
|
@@ -1763,6 +1782,10 @@
|
|
|
1763
1782
|
"quota_label_format": "{provider}: {value}%",
|
|
1764
1783
|
"quota_label_unknown": "{provider}: --",
|
|
1765
1784
|
"quota_notifications_menu": "使用量アラート通知",
|
|
1785
|
+
"quota_notifications_toggle": "通知を有効にする",
|
|
1786
|
+
"quota_notifications_permission_denied": "usage の通知はシステムで無効です — システム設定を開く…",
|
|
1787
|
+
"quota_threshold_header": "通知のしきい値",
|
|
1788
|
+
"quota_threshold_item": "使用量 {percent}%",
|
|
1766
1789
|
"window_keeper_menu": "5 時間セッションを自動開始",
|
|
1767
1790
|
"window_keeper_tooltip": "5 時間枠のリセット後、各ツールにごく小さなメッセージを自動送信します(Claude は Haiku、agy は Gemini 3.8 Flash Low、Codex は最も低コストなモデル)。次の 5 時間のカウントをすぐに開始します(ごくわずかな枠を消費します)。",
|
|
1768
1791
|
"window_keeper_sleep_title": "🐾 5 時間セッションの自動開始をオンにしました",
|
|
@@ -1849,8 +1872,8 @@
|
|
|
1849
1872
|
"grok_stale_minutes": "約 {minutes} 分前",
|
|
1850
1873
|
"grok_stale_hours": "約 {hours} 時間前",
|
|
1851
1874
|
"grok_stale_tooltip": "データは Grok CLI のローカルログから読み取るもので、リアルタイム API ではありません。更新するには最近 grok コマンドを実行してください。",
|
|
1852
|
-
"codex_stale_minutes": "約 {minutes}
|
|
1853
|
-
"codex_stale_hours": "約 {hours}
|
|
1875
|
+
"codex_stale_minutes": "約 {minutes} 分前に更新",
|
|
1876
|
+
"codex_stale_hours": "約 {hours} 時間前に更新",
|
|
1854
1877
|
"codex_stale_tooltip": "Claude はリアルタイムに報告するので正確です。Codex にはそれがないため、usage は Codex がローカルに時々残す記録を読み取り、実際より少し古い場合があります。ターミナルで Codex を使い続けると自動的に更新されます。",
|
|
1855
1878
|
"history_load_error_file": "使用履歴ファイルを読み込めません",
|
|
1856
1879
|
"history_load_error_parse": "使用履歴データの形式に問題があります",
|
|
@@ -1862,7 +1885,7 @@
|
|
|
1862
1885
|
"project_range_7d": "7日間",
|
|
1863
1886
|
"project_range_30d": "月",
|
|
1864
1887
|
"project_range_all": "全期間",
|
|
1865
|
-
"session_label": "
|
|
1888
|
+
"session_label": "5時間",
|
|
1866
1889
|
"weekly_label": "週間",
|
|
1867
1890
|
"monthly_label": "月間",
|
|
1868
1891
|
"projects_empty": "記録なし",
|
|
@@ -2234,6 +2257,7 @@
|
|
|
2234
2257
|
"claude_pane_remaining": "残り",
|
|
2235
2258
|
"claude_pane_reset_done": "リセット済み",
|
|
2236
2259
|
"claude_pane_hours_ago": "{count}時間前",
|
|
2260
|
+
"claude_pane_updated_ago": "{ago}に更新",
|
|
2237
2261
|
"update_check_failed_offline": "GitHub に接続できません。ネット接続を確認して再試行してください。",
|
|
2238
2262
|
"update_check_failed_rate_limited": "GitHub がリクエストを制限しています。後でもう一度お試しください。",
|
|
2239
2263
|
"update_check_failed_server": "GitHub でエラーが発生しました。後でもう一度お試しください。",
|
|
@@ -2329,6 +2353,7 @@
|
|
|
2329
2353
|
"statusline_sync_menu": "상태 표시줄로 사용량 동기화",
|
|
2330
2354
|
"statusline_sync_tooltip": "Claude Code 상태 표시줄에서 사용량을 읽습니다. 끄면 Claude 사용량이 더 이상 업데이트되지 않습니다.",
|
|
2331
2355
|
"launch_at_login": "로그인 시 시작",
|
|
2356
|
+
"launch_at_login_failed": "로그인 시 시작을 설정할 수 없습니다: {error}",
|
|
2332
2357
|
"reset_panel_position": "패널 위치 재설정",
|
|
2333
2358
|
"hide_sections_menu": "섹션 숨기기",
|
|
2334
2359
|
"tray_provider_menu": "트레이 표시 소스",
|
|
@@ -2336,6 +2361,10 @@
|
|
|
2336
2361
|
"quota_label_format": "{provider}: {value}%",
|
|
2337
2362
|
"quota_label_unknown": "{provider}: --",
|
|
2338
2363
|
"quota_notifications_menu": "사용량 알림",
|
|
2364
|
+
"quota_notifications_toggle": "알림 켜기",
|
|
2365
|
+
"quota_notifications_permission_denied": "시스템에서 usage 알림이 꺼져 있습니다 — 시스템 설정 열기…",
|
|
2366
|
+
"quota_threshold_header": "알림 기준",
|
|
2367
|
+
"quota_threshold_item": "사용량 {percent}%",
|
|
2339
2368
|
"window_keeper_menu": "5시간 세션 자동 시작",
|
|
2340
2369
|
"window_keeper_tooltip": "5시간 할당량이 리셋되면 각 도구에 아주 작은 메시지를 자동 전송합니다 (Claude는 Haiku, agy는 Gemini 3.8 Flash Low, Codex는 가장 저렴한 모델). 다음 5시간 카운트를 바로 시작합니다 (극소량의 할당량을 소모합니다).",
|
|
2341
2370
|
"window_keeper_sleep_title": "🐾 5시간 세션 자동 시작이 켜졌어요",
|
|
@@ -2422,8 +2451,8 @@
|
|
|
2422
2451
|
"grok_stale_minutes": "약 {minutes}분 전",
|
|
2423
2452
|
"grok_stale_hours": "약 {hours}시간 전",
|
|
2424
2453
|
"grok_stale_tooltip": "데이터는 Grok CLI의 로컬 로그에서 읽으며 실시간 API가 아닙니다. 업데이트하려면 최근에 grok 명령을 실행하세요.",
|
|
2425
|
-
"codex_stale_minutes": "약 {minutes}분 전",
|
|
2426
|
-
"codex_stale_hours": "약 {hours}시간 전",
|
|
2454
|
+
"codex_stale_minutes": "약 {minutes}분 전 업데이트",
|
|
2455
|
+
"codex_stale_hours": "약 {hours}시간 전 업데이트",
|
|
2427
2456
|
"codex_stale_tooltip": "Claude는 실시간으로 보고하므로 정확합니다. Codex는 그렇지 않아 usage가 Codex가 로컬에 가끔 남기는 기록을 읽으며, 실제보다 조금 오래된 값일 수 있습니다. 터미널에서 Codex를 계속 실행하면 자동으로 업데이트됩니다.",
|
|
2428
2457
|
"history_load_error_file": "사용 기록 파일을 읽을 수 없습니다",
|
|
2429
2458
|
"history_load_error_parse": "사용 기록 형식에 문제가 있습니다",
|
|
@@ -2435,7 +2464,7 @@
|
|
|
2435
2464
|
"project_range_7d": "7일",
|
|
2436
2465
|
"project_range_30d": "월",
|
|
2437
2466
|
"project_range_all": "전체",
|
|
2438
|
-
"session_label": "
|
|
2467
|
+
"session_label": "5시간",
|
|
2439
2468
|
"weekly_label": "주간",
|
|
2440
2469
|
"monthly_label": "월간",
|
|
2441
2470
|
"projects_empty": "기록 없음",
|
|
@@ -2807,6 +2836,7 @@
|
|
|
2807
2836
|
"claude_pane_remaining": "남음",
|
|
2808
2837
|
"claude_pane_reset_done": "리셋됨",
|
|
2809
2838
|
"claude_pane_hours_ago": "{count}시간 전",
|
|
2839
|
+
"claude_pane_updated_ago": "{ago} 업데이트",
|
|
2810
2840
|
"update_check_failed_offline": "GitHub에 연결할 수 없어요. 인터넷 연결을 확인하고 다시 시도하세요.",
|
|
2811
2841
|
"update_check_failed_rate_limited": "GitHub가 요청을 제한하고 있어요. 잠시 후 다시 시도하세요.",
|
|
2812
2842
|
"update_check_failed_server": "GitHub에서 오류가 발생했어요. 잠시 후 다시 시도하세요.",
|
|
@@ -113,14 +113,15 @@ def _launchctl_bootstrap() -> None:
|
|
|
113
113
|
)
|
|
114
114
|
except FileNotFoundError as exc:
|
|
115
115
|
_log_launchctl_error("bootstrap", str(exc))
|
|
116
|
-
|
|
116
|
+
raise
|
|
117
117
|
except subprocess.TimeoutExpired as exc:
|
|
118
118
|
_log_launchctl_error("bootstrap", f"timed out after {exc.timeout} seconds")
|
|
119
|
-
|
|
119
|
+
raise
|
|
120
120
|
|
|
121
121
|
if result.returncode not in (0, 17):
|
|
122
122
|
stderr = _stderr_summary(result.stderr)
|
|
123
123
|
_log_launchctl_error("bootstrap", f"returncode={result.returncode} stderr={stderr!r}")
|
|
124
|
+
raise RuntimeError(stderr or f"launchctl: {result.returncode}")
|
|
124
125
|
|
|
125
126
|
|
|
126
127
|
def enable() -> None:
|
|
@@ -60,7 +60,7 @@ CODEX_CONFIG = setup_hook.CODEX_CONFIG
|
|
|
60
60
|
# session. Off by default: enabled only via the menu toggle, never by self_heal.
|
|
61
61
|
RESUME_HOOK_TARGET = Path(os.path.expanduser("~/.claude/usage-session-resume.py"))
|
|
62
62
|
RESUME_PROMPT_SIDECAR = Path(os.path.expanduser("~/.claude/usage-resume-prompt.json"))
|
|
63
|
-
RESUME_HOOK_VERSION = "1.
|
|
63
|
+
RESUME_HOOK_VERSION = "1.13"
|
|
64
64
|
RESUME_MATCHER = "startup|clear|resume"
|
|
65
65
|
RESUME_LANGS = ("zh-TW", "zh-CN", "en", "ja", "ko")
|
|
66
66
|
_RESUME_MARKER = "usage-session-resume"
|
|
@@ -95,7 +95,7 @@ _TERSE_REMINDER_MARKER = "usage-terse-reminder"
|
|
|
95
95
|
_TERSE_REMINDER_MARKERS = (_TERSE_REMINDER_MARKER, "usage_terse_reminder")
|
|
96
96
|
# Independent, opt-in Claude Code UserPromptSubmit hook.
|
|
97
97
|
QUOTA_AWARE_HOOK_TARGET = Path(os.path.expanduser("~/.claude/usage-quota-aware.py"))
|
|
98
|
-
QUOTA_AWARE_HOOK_VERSION = "1.
|
|
98
|
+
QUOTA_AWARE_HOOK_VERSION = "1.5"
|
|
99
99
|
QUOTA_AWARE_MATCHER = ""
|
|
100
100
|
_QUOTA_AWARE_MARKER = "usage-quota-aware"
|
|
101
101
|
_QUOTA_AWARE_MARKERS = (_QUOTA_AWARE_MARKER, "usage_quota_aware")
|
|
@@ -622,7 +622,9 @@ def _self_heal_codex_terse() -> None:
|
|
|
622
622
|
script_missing = not CODEX_TERSE_HOOK_TARGET.exists()
|
|
623
623
|
entry_missing = not _is_codex_terse_installed()
|
|
624
624
|
old_version = _installed_codex_terse_version()
|
|
625
|
-
version_stale = CODEX_TERSE_HOOK_TARGET.exists() and
|
|
625
|
+
version_stale = CODEX_TERSE_HOOK_TARGET.exists() and setup_hook.is_older(
|
|
626
|
+
old_version, TERSE_HOOK_VERSION
|
|
627
|
+
)
|
|
626
628
|
|
|
627
629
|
if not (script_missing or entry_missing or version_stale or not hooks_enabled):
|
|
628
630
|
return
|
|
@@ -859,7 +861,7 @@ def _self_heal_resume() -> None:
|
|
|
859
861
|
_copy_resume_script()
|
|
860
862
|
_write_resume_sidecar()
|
|
861
863
|
_append_self_heal_log("restore_resume_hook", detail)
|
|
862
|
-
elif _installed_resume_version()
|
|
864
|
+
elif setup_hook.is_older(_installed_resume_version(), RESUME_HOOK_VERSION):
|
|
863
865
|
old = _installed_resume_version()
|
|
864
866
|
_copy_resume_script()
|
|
865
867
|
_write_resume_sidecar()
|
|
@@ -896,7 +898,7 @@ def _self_heal_terse_mode() -> None:
|
|
|
896
898
|
_append_self_heal_log("restore_terse_hook", f"missing={','.join(missing)}")
|
|
897
899
|
else:
|
|
898
900
|
old = _installed_terse_version()
|
|
899
|
-
if old
|
|
901
|
+
if setup_hook.is_older(old, TERSE_HOOK_VERSION):
|
|
900
902
|
_copy_terse_script()
|
|
901
903
|
_write_terse_sidecar()
|
|
902
904
|
_append_self_heal_log(
|
|
@@ -926,7 +928,7 @@ def _self_heal_terse_reminder() -> None:
|
|
|
926
928
|
_append_self_heal_log("restore_terse_reminder_hook", f"missing={','.join(parts)}")
|
|
927
929
|
return
|
|
928
930
|
old = _installed_terse_reminder_version()
|
|
929
|
-
if old
|
|
931
|
+
if setup_hook.is_older(old, TERSE_REMINDER_HOOK_VERSION):
|
|
930
932
|
_copy_terse_reminder_script()
|
|
931
933
|
_append_self_heal_log(
|
|
932
934
|
"update_terse_reminder_hook",
|
|
@@ -1149,8 +1151,8 @@ def _debug_self_heal_failure(action: str, exc: BaseException) -> None:
|
|
|
1149
1151
|
print(f"usage self-heal {action} failed: {type(exc).__name__}: {exc}", file=sys.stderr)
|
|
1150
1152
|
|
|
1151
1153
|
|
|
1152
|
-
def
|
|
1153
|
-
"""Best-effort startup repair for usage-owned
|
|
1154
|
+
def _self_heal_statuslines() -> None:
|
|
1155
|
+
"""Best-effort startup repair for usage-owned status lines."""
|
|
1154
1156
|
try:
|
|
1155
1157
|
settings = _load_settings()
|
|
1156
1158
|
state = _detect_current_state(settings)
|
|
@@ -1158,7 +1160,11 @@ def self_heal() -> None:
|
|
|
1158
1160
|
return
|
|
1159
1161
|
_migrate_bundled_python_commands_if_needed(settings)
|
|
1160
1162
|
setup_hook._migrate_windows_statusline_command_if_needed(settings)
|
|
1161
|
-
|
|
1163
|
+
usage_settings = settings.get(BACKUP_KEY)
|
|
1164
|
+
statusline_disabled = (
|
|
1165
|
+
isinstance(usage_settings, dict) and setup_hook.DISABLED_SL_KEY in usage_settings
|
|
1166
|
+
)
|
|
1167
|
+
if not is_setup() and "statusLine" not in settings and not statusline_disabled:
|
|
1162
1168
|
exit_code = _run_quietly(setup)
|
|
1163
1169
|
if exit_code == 0:
|
|
1164
1170
|
_append_self_heal_log("install_hook", "initial setup")
|
|
@@ -1257,6 +1263,11 @@ def self_heal() -> None:
|
|
|
1257
1263
|
raise
|
|
1258
1264
|
_debug_self_heal_failure("setup_codex", exc)
|
|
1259
1265
|
|
|
1266
|
+
|
|
1267
|
+
def self_heal() -> None:
|
|
1268
|
+
"""Repair status lines and independently repair opt-in session hooks."""
|
|
1269
|
+
_self_heal_statuslines()
|
|
1270
|
+
|
|
1260
1271
|
try:
|
|
1261
1272
|
_self_heal_resume()
|
|
1262
1273
|
except BaseException as exc:
|
|
@@ -1375,7 +1386,7 @@ def _self_heal_quota_aware() -> None:
|
|
|
1375
1386
|
if not is_quota_aware_enabled():
|
|
1376
1387
|
return
|
|
1377
1388
|
old = _installed_quota_aware_version()
|
|
1378
|
-
if old
|
|
1389
|
+
if setup_hook.is_older(old, QUOTA_AWARE_HOOK_VERSION):
|
|
1379
1390
|
_copy_quota_aware_script()
|
|
1380
1391
|
_append_self_heal_log(
|
|
1381
1392
|
"update_quota_aware_hook", f"{old or 'unknown'} -> {QUOTA_AWARE_HOOK_VERSION}"
|
|
@@ -86,7 +86,7 @@ PREV_SL_KEY = "previousStatusLine"
|
|
|
86
86
|
# Where the status-line switch parks the live statusLine while it is off, so it
|
|
87
87
|
# never overwrites the pre-install backup under PREV_SL_KEY.
|
|
88
88
|
DISABLED_SL_KEY = "disabledStatusLine"
|
|
89
|
-
HOOK_VERSION = "1.
|
|
89
|
+
HOOK_VERSION = "1.18"
|
|
90
90
|
FORWARDER_VERSION = "1.1"
|
|
91
91
|
# Antigravity's Go runner hands its status-line command to cmd.exe unquoted, so
|
|
92
92
|
# every character cmd.exe treats specially has to be kept out of the path.
|
|
@@ -811,9 +811,7 @@ def _replace_grok_status_line(content: str, replacement: str) -> str | None:
|
|
|
811
811
|
else:
|
|
812
812
|
start, end = section
|
|
813
813
|
candidate = content[:start] + replacement + content[end:]
|
|
814
|
-
|
|
815
|
-
tomllib.loads(candidate)
|
|
816
|
-
except tomllib.TOMLDecodeError:
|
|
814
|
+
if _validated_table_edit(content, candidate, "ui", "status_line") != candidate:
|
|
817
815
|
return None
|
|
818
816
|
return candidate
|
|
819
817
|
|
|
@@ -887,8 +885,9 @@ def _unsetup_grok() -> bool:
|
|
|
887
885
|
restored = content[:start] + content[end:]
|
|
888
886
|
if start > 0 and restored[:start].endswith("\n"):
|
|
889
887
|
restored = restored[: start - 1] + restored[start:]
|
|
888
|
+
if _validated_table_edit(content, restored, "ui", "status_line") != restored:
|
|
889
|
+
return False
|
|
890
890
|
try:
|
|
891
|
-
tomllib.loads(restored)
|
|
892
891
|
_atomic_write_text(GROK_SETTINGS, restored)
|
|
893
892
|
GROK_PREVIOUS_STATUSLINE.unlink(missing_ok=True)
|
|
894
893
|
except (OSError, tomllib.TOMLDecodeError):
|
|
@@ -969,13 +968,31 @@ def _find_top_level_dotted_line(
|
|
|
969
968
|
return dotted_line.search(content, 0, top_level_end)
|
|
970
969
|
|
|
971
970
|
|
|
971
|
+
def _validated_table_edit(content: str, candidate: str, name: str, key: str) -> str:
|
|
972
|
+
"""Reject edits that change anything except the requested TOML key."""
|
|
973
|
+
try:
|
|
974
|
+
before, after = tomllib.loads(content), tomllib.loads(candidate)
|
|
975
|
+
except tomllib.TOMLDecodeError:
|
|
976
|
+
return content
|
|
977
|
+
|
|
978
|
+
def without_target(table: dict[str, Any], path: list[str]) -> None:
|
|
979
|
+
part = path[0]
|
|
980
|
+
if len(path) == 1:
|
|
981
|
+
table.pop(part, None)
|
|
982
|
+
elif isinstance(child := table.get(part), dict):
|
|
983
|
+
without_target(child, path[1:])
|
|
984
|
+
if not child:
|
|
985
|
+
table.pop(part)
|
|
986
|
+
|
|
987
|
+
path = [*name.split("."), key]
|
|
988
|
+
without_target(before, path)
|
|
989
|
+
without_target(after, path)
|
|
990
|
+
return candidate if before == after else content
|
|
991
|
+
|
|
992
|
+
|
|
972
993
|
def _insert_table_line(content: str, name: str, line: str) -> str:
|
|
973
994
|
def validated(candidate: str) -> str:
|
|
974
|
-
|
|
975
|
-
tomllib.loads(candidate)
|
|
976
|
-
except tomllib.TOMLDecodeError:
|
|
977
|
-
return content
|
|
978
|
-
return candidate
|
|
995
|
+
return _validated_table_edit(content, candidate, name, next(iter(tomllib.loads(line))))
|
|
979
996
|
|
|
980
997
|
table = _find_table(content, name)
|
|
981
998
|
if table is None:
|
|
@@ -1017,11 +1034,9 @@ def _replace_table_line(
|
|
|
1017
1034
|
content: str, name: str, line_regex: re.Pattern[str], replacement: str
|
|
1018
1035
|
) -> str:
|
|
1019
1036
|
def validated(candidate: str) -> str:
|
|
1020
|
-
|
|
1021
|
-
tomllib.loads(
|
|
1022
|
-
|
|
1023
|
-
return content
|
|
1024
|
-
return candidate
|
|
1037
|
+
return _validated_table_edit(
|
|
1038
|
+
content, candidate, name, next(iter(tomllib.loads(replacement)))
|
|
1039
|
+
)
|
|
1025
1040
|
|
|
1026
1041
|
table = _find_table(content, name)
|
|
1027
1042
|
if table is None:
|
|
@@ -1042,13 +1057,9 @@ def _replace_table_line(
|
|
|
1042
1057
|
return validated(candidate)
|
|
1043
1058
|
|
|
1044
1059
|
|
|
1045
|
-
def _remove_table_line(content: str, name: str, line_regex: re.Pattern[str]) -> str:
|
|
1060
|
+
def _remove_table_line(content: str, name: str, line_regex: re.Pattern[str], key: str) -> str:
|
|
1046
1061
|
def validated(candidate: str) -> str:
|
|
1047
|
-
|
|
1048
|
-
tomllib.loads(candidate)
|
|
1049
|
-
except tomllib.TOMLDecodeError:
|
|
1050
|
-
return content
|
|
1051
|
-
return candidate
|
|
1062
|
+
return _validated_table_edit(content, candidate, name, key)
|
|
1052
1063
|
|
|
1053
1064
|
table = _find_table(content, name)
|
|
1054
1065
|
if table is None:
|
|
@@ -1072,11 +1083,7 @@ def _ensure_table_line(content: str, name: str, line_regex: re.Pattern[str], lin
|
|
|
1072
1083
|
itself is absent."""
|
|
1073
1084
|
|
|
1074
1085
|
def validated(candidate: str) -> str:
|
|
1075
|
-
|
|
1076
|
-
tomllib.loads(candidate)
|
|
1077
|
-
except tomllib.TOMLDecodeError:
|
|
1078
|
-
return content
|
|
1079
|
-
return candidate
|
|
1086
|
+
return _validated_table_edit(content, candidate, name, next(iter(tomllib.loads(line))))
|
|
1080
1087
|
|
|
1081
1088
|
table = _find_table(content, name)
|
|
1082
1089
|
if table is None:
|
|
@@ -1097,7 +1104,7 @@ def _replace_tui_status_line(content: str, replacement: str) -> str:
|
|
|
1097
1104
|
|
|
1098
1105
|
|
|
1099
1106
|
def _remove_tui_status_line(content: str) -> str:
|
|
1100
|
-
return _remove_table_line(content, "tui", _SL_REGEX)
|
|
1107
|
+
return _remove_table_line(content, "tui", _SL_REGEX, "status_line")
|
|
1101
1108
|
|
|
1102
1109
|
|
|
1103
1110
|
def _read_codex_config() -> tuple[str, dict[str, Any]] | None:
|
|
@@ -1150,6 +1157,10 @@ def _setup_codex() -> None:
|
|
|
1150
1157
|
print(_t("setup_codex_config_unmodifiable"))
|
|
1151
1158
|
return
|
|
1152
1159
|
_atomic_write_text(CODEX_CONFIG, content)
|
|
1160
|
+
written = _read_codex_config()
|
|
1161
|
+
if written is None or _codex_status_line(written[1]) != CODEX_STATUS_LINE:
|
|
1162
|
+
print(_t("setup_codex_config_unmodifiable"))
|
|
1163
|
+
return
|
|
1153
1164
|
print(_t("setup_codex_configured"))
|
|
1154
1165
|
if old is not None and old not in LEGACY_CODEX_STATUS_LINES:
|
|
1155
1166
|
print(_t("setup_codex_backup_written", path=CODEX_BACKUP))
|
|
@@ -1220,16 +1231,30 @@ def _installed_forwarder_version() -> str | None:
|
|
|
1220
1231
|
return None
|
|
1221
1232
|
|
|
1222
1233
|
|
|
1234
|
+
def is_older(installed: str | None, current: str) -> bool:
|
|
1235
|
+
"""True when an installed hook should be replaced: missing, unreadable, or older.
|
|
1236
|
+
|
|
1237
|
+
Never downgrade: an older usage build would otherwise swap back a hook that a newer
|
|
1238
|
+
build installed, and the two would keep replacing each other on every self-heal.
|
|
1239
|
+
"""
|
|
1240
|
+
if installed is None:
|
|
1241
|
+
return True
|
|
1242
|
+
try:
|
|
1243
|
+
return tuple(map(int, installed.split("."))) < tuple(map(int, current.split(".")))
|
|
1244
|
+
except ValueError:
|
|
1245
|
+
return True
|
|
1246
|
+
|
|
1247
|
+
|
|
1223
1248
|
def needs_update() -> bool:
|
|
1224
1249
|
if not HOOK_TARGET.parent.exists():
|
|
1225
1250
|
return False
|
|
1226
|
-
return _installed_hook_version()
|
|
1251
|
+
return is_older(_installed_hook_version(), HOOK_VERSION)
|
|
1227
1252
|
|
|
1228
1253
|
|
|
1229
1254
|
def forwarder_needs_update() -> bool:
|
|
1230
1255
|
if not FORWARDER_TARGET.exists():
|
|
1231
1256
|
return False
|
|
1232
|
-
return _installed_forwarder_version()
|
|
1257
|
+
return is_older(_installed_forwarder_version(), FORWARDER_VERSION)
|
|
1233
1258
|
|
|
1234
1259
|
|
|
1235
1260
|
def update_hook() -> None:
|
|
@@ -1335,15 +1360,20 @@ def unsetup() -> int:
|
|
|
1335
1360
|
settings = _load_settings()
|
|
1336
1361
|
sl = settings.get("statusLine")
|
|
1337
1362
|
backup = settings.get(BACKUP_KEY)
|
|
1338
|
-
parked = backup.
|
|
1339
|
-
if sl is None:
|
|
1340
|
-
sl = parked
|
|
1363
|
+
parked = backup.get(DISABLED_SL_KEY) if isinstance(backup, dict) else None
|
|
1341
1364
|
|
|
1342
|
-
if _is_usage_hook(sl) or _is_legacy_tt_hook(sl):
|
|
1365
|
+
if _is_usage_hook(sl) or _is_legacy_tt_hook(sl) or (sl is None and parked is not None):
|
|
1343
1366
|
legacy_backup = settings.get(LEGACY_TT_BACKUP_KEY)
|
|
1344
1367
|
prev = backup.get(PREV_SL_KEY) if isinstance(backup, dict) else None
|
|
1345
1368
|
if not isinstance(prev, dict) and isinstance(legacy_backup, dict):
|
|
1346
1369
|
prev = legacy_backup.get(PREV_SL_KEY)
|
|
1370
|
+
if (
|
|
1371
|
+
not isinstance(prev, dict)
|
|
1372
|
+
and isinstance(parked, dict)
|
|
1373
|
+
and not _is_usage_hook(parked)
|
|
1374
|
+
and not _is_legacy_tt_hook(parked)
|
|
1375
|
+
):
|
|
1376
|
+
prev = parked
|
|
1347
1377
|
|
|
1348
1378
|
if isinstance(prev, dict):
|
|
1349
1379
|
settings["statusLine"] = prev
|
|
@@ -1354,6 +1384,7 @@ def unsetup() -> int:
|
|
|
1354
1384
|
|
|
1355
1385
|
if isinstance(backup, dict):
|
|
1356
1386
|
backup.pop(PREV_SL_KEY, None)
|
|
1387
|
+
backup.pop(DISABLED_SL_KEY, None)
|
|
1357
1388
|
if not backup:
|
|
1358
1389
|
del settings[BACKUP_KEY]
|
|
1359
1390
|
if isinstance(legacy_backup, dict):
|
|
@@ -110,7 +110,8 @@ def _disable_statusline_settings() -> int:
|
|
|
110
110
|
_sync_agy_statusline(False)
|
|
111
111
|
_sync_grok_statusline(False)
|
|
112
112
|
settings = _load_claude_settings()
|
|
113
|
-
|
|
113
|
+
# The switch can be on for agy alone; a user's own Claude status line is not ours to stash.
|
|
114
|
+
if "statusLine" not in settings or setup_hook._detect_current_state(settings) == "external":
|
|
114
115
|
return 0
|
|
115
116
|
usage_settings = settings.setdefault("usage", {})
|
|
116
117
|
if not isinstance(usage_settings, dict):
|
|
@@ -129,7 +130,9 @@ def _enable_statusline_settings() -> int:
|
|
|
129
130
|
_sync_grok_statusline(True)
|
|
130
131
|
settings = _load_claude_settings()
|
|
131
132
|
if "statusLine" in settings:
|
|
132
|
-
|
|
133
|
+
if setup_hook._detect_current_state(settings) in {"us-direct", "us-forwarder"}:
|
|
134
|
+
return 0
|
|
135
|
+
return setup_hook.setup()
|
|
133
136
|
raw_usage_settings = settings.get("usage")
|
|
134
137
|
usage_settings = raw_usage_settings if isinstance(raw_usage_settings, dict) else None
|
|
135
138
|
# Older versions parked the switch's statusLine under previousStatusLine.
|
|
@@ -169,7 +172,9 @@ def _statusline_enabled() -> bool:
|
|
|
169
172
|
settings = _load_claude_settings()
|
|
170
173
|
except (OSError, ValueError, json.JSONDecodeError):
|
|
171
174
|
return False
|
|
172
|
-
|
|
175
|
+
from installer import setup_hook
|
|
176
|
+
|
|
177
|
+
if setup_hook._detect_current_state(settings) in {"us-direct", "us-forwarder"}:
|
|
173
178
|
return True
|
|
174
179
|
if sys.platform not in {"darwin", "win32"}:
|
|
175
180
|
return False
|