usage-cli 0.34.0__tar.gz → 0.34.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (280) hide show
  1. {usage_cli-0.34.0/usage_cli.egg-info → usage_cli-0.34.2}/PKG-INFO +2 -2
  2. {usage_cli-0.34.0 → usage_cli-0.34.2}/i18n.json +25 -10
  3. {usage_cli-0.34.0 → usage_cli-0.34.2}/loaders/muse_quota_probe.py +2 -0
  4. {usage_cli-0.34.0 → usage_cli-0.34.2}/menubar/app.py +1 -9
  5. {usage_cli-0.34.0 → usage_cli-0.34.2}/menubar/menu.py +11 -0
  6. {usage_cli-0.34.0 → usage_cli-0.34.2}/menubar/refresh.py +8 -12
  7. {usage_cli-0.34.0 → usage_cli-0.34.2}/menubar/state.py +28 -43
  8. {usage_cli-0.34.0 → usage_cli-0.34.2}/panels/payload.py +7 -4
  9. {usage_cli-0.34.0 → usage_cli-0.34.2}/pyproject.toml +2 -2
  10. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_analyzer_pipeline.py +1 -1
  11. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_compact_cards.py +3 -1
  12. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_desktop_quota_display.py +2 -3
  13. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_menubar.py +29 -31
  14. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_menubar_grok.py +2 -3
  15. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_menubar_notify.py +3 -4
  16. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_muse_quota_probe.py +11 -0
  17. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_packaged_resources.py +2 -1
  18. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_panels.py +3 -3
  19. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_web_panel_payload.py +1 -1
  20. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_wintray.py +6 -5
  21. {usage_cli-0.34.0 → usage_cli-0.34.2/usage_cli.egg-info}/PKG-INFO +2 -2
  22. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_cli.egg-info/requires.txt +1 -1
  23. {usage_cli-0.34.0 → usage_cli-0.34.2}/wintray/app.py +20 -31
  24. {usage_cli-0.34.0 → usage_cli-0.34.2}/wintray/menu.py +2 -0
  25. {usage_cli-0.34.0 → usage_cli-0.34.2}/LICENSE +0 -0
  26. {usage_cli-0.34.0 → usage_cli-0.34.2}/README.md +0 -0
  27. {usage_cli-0.34.0 → usage_cli-0.34.2}/THIRD_PARTY_NOTICES.md +0 -0
  28. {usage_cli-0.34.0 → usage_cli-0.34.2}/adapters/__init__.py +0 -0
  29. {usage_cli-0.34.0 → usage_cli-0.34.2}/adapters/agy.py +0 -0
  30. {usage_cli-0.34.0 → usage_cli-0.34.2}/adapters/claude.py +0 -0
  31. {usage_cli-0.34.0 → usage_cli-0.34.2}/adapters/codex.py +0 -0
  32. {usage_cli-0.34.0 → usage_cli-0.34.2}/adapters/grok.py +0 -0
  33. {usage_cli-0.34.0 → usage_cli-0.34.2}/adapters/muse.py +0 -0
  34. {usage_cli-0.34.0 → usage_cli-0.34.2}/adapters/rate_limits.py +0 -0
  35. {usage_cli-0.34.0 → usage_cli-0.34.2}/adapters/registry.py +0 -0
  36. {usage_cli-0.34.0 → usage_cli-0.34.2}/adapters/types.py +0 -0
  37. {usage_cli-0.34.0 → usage_cli-0.34.2}/analyzer/__init__.py +0 -0
  38. {usage_cli-0.34.0 → usage_cli-0.34.2}/analyzer/aggregator.py +0 -0
  39. {usage_cli-0.34.0 → usage_cli-0.34.2}/analyzer/blocks.py +0 -0
  40. {usage_cli-0.34.0 → usage_cli-0.34.2}/analyzer/diagnoser.py +0 -0
  41. {usage_cli-0.34.0 → usage_cli-0.34.2}/analyzer/diagnosis_snapshot.py +0 -0
  42. {usage_cli-0.34.0 → usage_cli-0.34.2}/analyzer/dispatch_ledger.py +0 -0
  43. {usage_cli-0.34.0 → usage_cli-0.34.2}/analyzer/history_persist.py +0 -0
  44. {usage_cli-0.34.0 → usage_cli-0.34.2}/analyzer/insights.py +0 -0
  45. {usage_cli-0.34.0 → usage_cli-0.34.2}/analyzer/ledger_backfill.py +0 -0
  46. {usage_cli-0.34.0 → usage_cli-0.34.2}/analyzer/persona_loader.py +0 -0
  47. {usage_cli-0.34.0 → usage_cli-0.34.2}/analyzer/reporter.py +0 -0
  48. {usage_cli-0.34.0 → usage_cli-0.34.2}/analyzer/subscription.py +0 -0
  49. {usage_cli-0.34.0 → usage_cli-0.34.2}/analyzer/usage_snapshot.py +0 -0
  50. {usage_cli-0.34.0 → usage_cli-0.34.2}/installer/__init__.py +0 -0
  51. {usage_cli-0.34.0 → usage_cli-0.34.2}/installer/claude_pane.py +0 -0
  52. {usage_cli-0.34.0 → usage_cli-0.34.2}/installer/codex_hook_trust.py +0 -0
  53. {usage_cli-0.34.0 → usage_cli-0.34.2}/installer/login_item.py +0 -0
  54. {usage_cli-0.34.0 → usage_cli-0.34.2}/installer/session_hooks.py +0 -0
  55. {usage_cli-0.34.0 → usage_cli-0.34.2}/installer/setup_hook.py +0 -0
  56. {usage_cli-0.34.0 → usage_cli-0.34.2}/installer/statusline_settings.py +0 -0
  57. {usage_cli-0.34.0 → usage_cli-0.34.2}/loaders/__init__.py +0 -0
  58. {usage_cli-0.34.0 → usage_cli-0.34.2}/loaders/_shared_cache_file.py +0 -0
  59. {usage_cli-0.34.0 → usage_cli-0.34.2}/loaders/agy_disk_cache.py +0 -0
  60. {usage_cli-0.34.0 → usage_cli-0.34.2}/loaders/agy_loader.py +0 -0
  61. {usage_cli-0.34.0 → usage_cli-0.34.2}/loaders/agy_quota_probe.py +0 -0
  62. {usage_cli-0.34.0 → usage_cli-0.34.2}/loaders/cache_quarantine.py +0 -0
  63. {usage_cli-0.34.0 → usage_cli-0.34.2}/loaders/chromium_cache.py +0 -0
  64. {usage_cli-0.34.0 → usage_cli-0.34.2}/loaders/claude_desktop.py +0 -0
  65. {usage_cli-0.34.0 → usage_cli-0.34.2}/loaders/claude_paths.py +0 -0
  66. {usage_cli-0.34.0 → usage_cli-0.34.2}/loaders/claude_usage.py +0 -0
  67. {usage_cli-0.34.0 → usage_cli-0.34.2}/loaders/codex_disk_cache.py +0 -0
  68. {usage_cli-0.34.0 → usage_cli-0.34.2}/loaders/codex_events.py +0 -0
  69. {usage_cli-0.34.0 → usage_cli-0.34.2}/loaders/codex_fork_replay.py +0 -0
  70. {usage_cli-0.34.0 → usage_cli-0.34.2}/loaders/codex_loader.py +0 -0
  71. {usage_cli-0.34.0 → usage_cli-0.34.2}/loaders/codex_paths.py +0 -0
  72. {usage_cli-0.34.0 → usage_cli-0.34.2}/loaders/disk_cache_common.py +0 -0
  73. {usage_cli-0.34.0 → usage_cli-0.34.2}/loaders/disk_cache_lifecycle.py +0 -0
  74. {usage_cli-0.34.0 → usage_cli-0.34.2}/loaders/grok_loader.py +0 -0
  75. {usage_cli-0.34.0 → usage_cli-0.34.2}/loaders/grok_quota_probe.py +0 -0
  76. {usage_cli-0.34.0 → usage_cli-0.34.2}/loaders/history_disk_cache.py +0 -0
  77. {usage_cli-0.34.0 → usage_cli-0.34.2}/loaders/history_loader.py +0 -0
  78. {usage_cli-0.34.0 → usage_cli-0.34.2}/loaders/jsonl_limits.py +0 -0
  79. {usage_cli-0.34.0 → usage_cli-0.34.2}/loaders/jsonl_utils.py +0 -0
  80. {usage_cli-0.34.0 → usage_cli-0.34.2}/loaders/muse_loader.py +0 -0
  81. {usage_cli-0.34.0 → usage_cli-0.34.2}/main.py +0 -0
  82. {usage_cli-0.34.0 → usage_cli-0.34.2}/menubar/__init__.py +0 -0
  83. {usage_cli-0.34.0 → usage_cli-0.34.2}/menubar/actions.py +0 -0
  84. {usage_cli-0.34.0 → usage_cli-0.34.2}/menubar/agy.py +0 -0
  85. {usage_cli-0.34.0 → usage_cli-0.34.2}/menubar/chrome.py +0 -0
  86. {usage_cli-0.34.0 → usage_cli-0.34.2}/menubar/fsevents_watch.py +0 -0
  87. {usage_cli-0.34.0 → usage_cli-0.34.2}/menubar/grok.py +0 -0
  88. {usage_cli-0.34.0 → usage_cli-0.34.2}/menubar/manual_refresh.py +0 -0
  89. {usage_cli-0.34.0 → usage_cli-0.34.2}/menubar/muse.py +0 -0
  90. {usage_cli-0.34.0 → usage_cli-0.34.2}/menubar/new_badge.py +0 -0
  91. {usage_cli-0.34.0 → usage_cli-0.34.2}/menubar/notify.py +0 -0
  92. {usage_cli-0.34.0 → usage_cli-0.34.2}/menubar/popover.py +0 -0
  93. {usage_cli-0.34.0 → usage_cli-0.34.2}/menubar/prefs.py +0 -0
  94. {usage_cli-0.34.0 → usage_cli-0.34.2}/menubar/title.py +0 -0
  95. {usage_cli-0.34.0 → usage_cli-0.34.2}/menubar/update.py +0 -0
  96. {usage_cli-0.34.0 → usage_cli-0.34.2}/menubar/whats_new.py +0 -0
  97. {usage_cli-0.34.0 → usage_cli-0.34.2}/panels/__init__.py +0 -0
  98. {usage_cli-0.34.0 → usage_cli-0.34.2}/panels/base.py +0 -0
  99. {usage_cli-0.34.0 → usage_cli-0.34.2}/panels/catalog.py +0 -0
  100. {usage_cli-0.34.0 → usage_cli-0.34.2}/panels/dynamic_height.py +0 -0
  101. {usage_cli-0.34.0 → usage_cli-0.34.2}/panels/panel_scale.py +0 -0
  102. {usage_cli-0.34.0 → usage_cli-0.34.2}/panels/panel_window.py +0 -0
  103. {usage_cli-0.34.0 → usage_cli-0.34.2}/panels/panel_window_state.py +0 -0
  104. {usage_cli-0.34.0 → usage_cli-0.34.2}/panels/web_panel.py +0 -0
  105. {usage_cli-0.34.0 → usage_cli-0.34.2}/panels/window_drag.py +0 -0
  106. {usage_cli-0.34.0 → usage_cli-0.34.2}/quota/__init__.py +0 -0
  107. {usage_cli-0.34.0 → usage_cli-0.34.2}/quota/agy_window_keeper.py +0 -0
  108. {usage_cli-0.34.0 → usage_cli-0.34.2}/quota/burn_rate.py +0 -0
  109. {usage_cli-0.34.0 → usage_cli-0.34.2}/quota/codex_window_keeper.py +0 -0
  110. {usage_cli-0.34.0 → usage_cli-0.34.2}/quota/keeper_outcome.py +0 -0
  111. {usage_cli-0.34.0 → usage_cli-0.34.2}/quota/notifications.py +0 -0
  112. {usage_cli-0.34.0 → usage_cli-0.34.2}/quota/quota_snapshot.py +0 -0
  113. {usage_cli-0.34.0 → usage_cli-0.34.2}/quota/status_payload.py +0 -0
  114. {usage_cli-0.34.0 → usage_cli-0.34.2}/quota/usage_rate.py +0 -0
  115. {usage_cli-0.34.0 → usage_cli-0.34.2}/quota/window_keeper.py +0 -0
  116. {usage_cli-0.34.0 → usage_cli-0.34.2}/setup.cfg +0 -0
  117. {usage_cli-0.34.0 → usage_cli-0.34.2}/setup_app.py +0 -0
  118. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_adapters_claude.py +0 -0
  119. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_adapters_codex.py +0 -0
  120. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_adapters_rate_limits.py +0 -0
  121. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_aggregator.py +0 -0
  122. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_agy_adapter.py +0 -0
  123. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_agy_disk_cache.py +0 -0
  124. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_agy_loader.py +0 -0
  125. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_agy_quota_probe.py +0 -0
  126. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_agy_window_keeper.py +0 -0
  127. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_analyzer_blocks.py +0 -0
  128. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_app_version.py +0 -0
  129. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_burn_rate.py +0 -0
  130. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_cache_quarantine.py +0 -0
  131. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_check_upstream.py +0 -0
  132. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_chromium_cache.py +0 -0
  133. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_claude_desktop.py +0 -0
  134. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_claude_pane.py +0 -0
  135. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_claude_paths.py +0 -0
  136. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_codex_consistency.py +0 -0
  137. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_codex_hook_trust.py +0 -0
  138. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_codex_loader.py +0 -0
  139. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_codex_window_keeper.py +0 -0
  140. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_desktop_fresh_cache.py +0 -0
  141. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_desktop_quota_review.py +0 -0
  142. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_diagnoser.py +0 -0
  143. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_disk_cache_shards.py +0 -0
  144. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_dispatch_ledger.py +0 -0
  145. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_doctor.py +0 -0
  146. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_dynamic_height.py +0 -0
  147. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_fsevents_watch.py +0 -0
  148. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_grok_loader.py +0 -0
  149. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_grok_quota_probe.py +0 -0
  150. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_history_loader.py +0 -0
  151. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_history_persist.py +0 -0
  152. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_hook_scripts_are_standalone.py +0 -0
  153. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_html_report_snapshot.py +0 -0
  154. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_i18n_format_fallback.py +0 -0
  155. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_i18n_key_parity.py +0 -0
  156. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_i18n_packaged_path.py +0 -0
  157. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_insights.py +0 -0
  158. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_jsonl_utils.py +0 -0
  159. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_keeper_menu.py +0 -0
  160. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_keeper_outcome.py +0 -0
  161. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_ledger_backfill.py +0 -0
  162. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_login_item.py +0 -0
  163. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_main.py +0 -0
  164. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_manual_refresh.py +0 -0
  165. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_menubar_agy.py +0 -0
  166. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_menubar_muse.py +0 -0
  167. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_menubar_prefs.py +0 -0
  168. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_menubar_state.py +0 -0
  169. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_menubar_update.py +0 -0
  170. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_muse_coverage.py +0 -0
  171. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_muse_loader.py +0 -0
  172. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_muse_review_regressions.py +0 -0
  173. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_new_badge.py +0 -0
  174. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_packaging.py +0 -0
  175. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_panel_footer_layout.py +0 -0
  176. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_panel_scale.py +0 -0
  177. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_panel_window_state.py +0 -0
  178. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_panels_lazy_import.py +0 -0
  179. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_persona_loader.py +0 -0
  180. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_pricing.py +0 -0
  181. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_project_resolver.py +0 -0
  182. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_quota_aware.py +0 -0
  183. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_quota_label.py +0 -0
  184. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_report_cold_read.py +0 -0
  185. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_report_daily_chart.py +0 -0
  186. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_report_filter_js.py +0 -0
  187. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_report_share_mask.py +0 -0
  188. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_reporter_build_data.py +0 -0
  189. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_service_status.py +0 -0
  190. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_session_resume.py +0 -0
  191. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_setup_app.py +0 -0
  192. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_setup_hook.py +0 -0
  193. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_setup_hook_coexistence.py +0 -0
  194. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_setup_hook_resume.py +0 -0
  195. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_setup_hook_terse.py +0 -0
  196. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_subprocess_utils.py +0 -0
  197. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_subscription.py +0 -0
  198. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_taskbar_menu.py +0 -0
  199. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_time_utils.py +0 -0
  200. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_tui.py +0 -0
  201. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_tui_sprite.py +0 -0
  202. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_ui_tables.py +0 -0
  203. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_update_checker.py +0 -0
  204. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_update_gate.py +0 -0
  205. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_update_release_notes.py +0 -0
  206. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_usage_cli.py +0 -0
  207. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_usage_client.py +0 -0
  208. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_usage_diagnosis_snapshot.py +0 -0
  209. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_usage_dir_sweeper.py +0 -0
  210. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_usage_lang.py +0 -0
  211. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_usage_logging.py +0 -0
  212. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_usage_notifications.py +0 -0
  213. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_usage_rate.py +0 -0
  214. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_usage_snapshot.py +0 -0
  215. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_usage_snapshot_replay.py +0 -0
  216. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_usage_statusline.py +0 -0
  217. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_usage_statusline_agy.py +0 -0
  218. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_usage_statusline_forwarder.py +0 -0
  219. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_usage_statusline_grok.py +0 -0
  220. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_usage_terse_mode.py +0 -0
  221. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_usage_terse_reminder.py +0 -0
  222. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_whats_new.py +0 -0
  223. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_wheel_packaging.py +0 -0
  224. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_win_login_item.py +0 -0
  225. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_window_keeper.py +0 -0
  226. {usage_cli-0.34.0 → usage_cli-0.34.2}/tests/test_windows_manifest.py +0 -0
  227. {usage_cli-0.34.0 → usage_cli-0.34.2}/tui/__init__.py +0 -0
  228. {usage_cli-0.34.0 → usage_cli-0.34.2}/tui/app.py +0 -0
  229. {usage_cli-0.34.0 → usage_cli-0.34.2}/tui/sprite.py +0 -0
  230. {usage_cli-0.34.0 → usage_cli-0.34.2}/ui/__init__.py +0 -0
  231. {usage_cli-0.34.0 → usage_cli-0.34.2}/ui/html_report.py +0 -0
  232. {usage_cli-0.34.0 → usage_cli-0.34.2}/ui/report_charts.py +0 -0
  233. {usage_cli-0.34.0 → usage_cli-0.34.2}/ui/report_daily_chart.py +0 -0
  234. {usage_cli-0.34.0 → usage_cli-0.34.2}/ui/report_filter.py +0 -0
  235. {usage_cli-0.34.0 → usage_cli-0.34.2}/ui/report_ledger.py +0 -0
  236. {usage_cli-0.34.0 → usage_cli-0.34.2}/ui/report_scripts.py +0 -0
  237. {usage_cli-0.34.0 → usage_cli-0.34.2}/ui/report_styles.py +0 -0
  238. {usage_cli-0.34.0 → usage_cli-0.34.2}/ui/tables.py +0 -0
  239. {usage_cli-0.34.0 → usage_cli-0.34.2}/updates/__init__.py +0 -0
  240. {usage_cli-0.34.0 → usage_cli-0.34.2}/updates/checker.py +0 -0
  241. {usage_cli-0.34.0 → usage_cli-0.34.2}/updates/gate.py +0 -0
  242. {usage_cli-0.34.0 → usage_cli-0.34.2}/updates/release_notes.py +0 -0
  243. {usage_cli-0.34.0 → usage_cli-0.34.2}/updates/whats_new.py +0 -0
  244. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_app/__init__.py +0 -0
  245. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_app/cli.py +0 -0
  246. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_app/doctor.py +0 -0
  247. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_cli.egg-info/SOURCES.txt +0 -0
  248. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_cli.egg-info/dependency_links.txt +0 -0
  249. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_cli.egg-info/entry_points.txt +0 -0
  250. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_cli.egg-info/top_level.txt +0 -0
  251. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_common/__init__.py +0 -0
  252. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_common/app_version.py +0 -0
  253. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_common/i18n.py +0 -0
  254. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_common/prefs.py +0 -0
  255. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_common/pricing.py +0 -0
  256. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_common/project_resolver.py +0 -0
  257. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_common/service_status.py +0 -0
  258. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_common/subprocess_utils.py +0 -0
  259. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_common/time_utils.py +0 -0
  260. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_common/usage_dir_sweeper.py +0 -0
  261. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_common/usage_lang.py +0 -0
  262. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_common/usage_logging.py +0 -0
  263. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_hooks/__init__.py +0 -0
  264. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_hooks/usage_quota_aware.py +0 -0
  265. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_hooks/usage_session_resume.py +0 -0
  266. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_hooks/usage_statusline.py +0 -0
  267. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_hooks/usage_statusline_agy.py +0 -0
  268. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_hooks/usage_statusline_forwarder.py +0 -0
  269. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_hooks/usage_statusline_grok.py +0 -0
  270. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_hooks/usage_terse_mode.py +0 -0
  271. {usage_cli-0.34.0 → usage_cli-0.34.2}/usage_hooks/usage_terse_reminder.py +0 -0
  272. {usage_cli-0.34.0 → usage_cli-0.34.2}/wintray/__init__.py +0 -0
  273. {usage_cli-0.34.0 → usage_cli-0.34.2}/wintray/login_item.py +0 -0
  274. {usage_cli-0.34.0 → usage_cli-0.34.2}/wintray/prefs.py +0 -0
  275. {usage_cli-0.34.0 → usage_cli-0.34.2}/wintray/quota_label.py +0 -0
  276. {usage_cli-0.34.0 → usage_cli-0.34.2}/wintray/taskbar_menu.py +0 -0
  277. {usage_cli-0.34.0 → usage_cli-0.34.2}/wintray/taskbar_obstacles.py +0 -0
  278. {usage_cli-0.34.0 → usage_cli-0.34.2}/wintray/taskbar_overlay.py +0 -0
  279. {usage_cli-0.34.0 → usage_cli-0.34.2}/wintray/watch.py +0 -0
  280. {usage_cli-0.34.0 → usage_cli-0.34.2}/wintray/whats_new.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: usage-cli
3
- Version: 0.34.0
3
+ Version: 0.34.2
4
4
  Summary: usage — 在 macOS menu bar 與 Windows 系統匣顯示 Claude Code、Codex、Antigravity 用量的小工具(也提供終端機 TUI)
5
5
  License-Expression: AGPL-3.0-only
6
6
  Requires-Python: >=3.13
@@ -15,7 +15,7 @@ Requires-Dist: pyobjc-framework-WebKit>=11.0; sys_platform == "darwin"
15
15
  Requires-Dist: rich<16.0.0,>=15.0.0
16
16
  Provides-Extra: windows
17
17
  Requires-Dist: pystray>=0.19.5; sys_platform == "win32" and extra == "windows"
18
- Requires-Dist: pillow>=11.0.0; sys_platform == "win32" and extra == "windows"
18
+ Requires-Dist: pillow>=13.0.0; sys_platform == "win32" and extra == "windows"
19
19
  Requires-Dist: pywebview>=5.4; sys_platform == "win32" and extra == "windows"
20
20
  Requires-Dist: windows-toasts>=1.3.1; sys_platform == "win32" and extra == "windows"
21
21
  Dynamic: license-file
@@ -19,6 +19,9 @@
19
19
  "panel_world_cup": "世界盃 2026",
20
20
  "panel_ai_daily": "AI 更新日報",
21
21
  "switch_panel": "更換面板",
22
+ "settings_button": "⚙ 設定",
23
+ "statusline_sync_menu": "狀態列同步額度",
24
+ "statusline_sync_tooltip": "從 Claude Code 的狀態列讀取額度。關掉後,Claude 的額度就不會再更新。",
22
25
  "launch_at_login": "開機自動啟動",
23
26
  "reset_panel_position": "重設面板位置",
24
27
  "hide_sections_menu": "隱藏區塊",
@@ -74,8 +77,8 @@
74
77
  "service_degraded": "效能降級",
75
78
  "service_partial_outage": "部分中斷",
76
79
  "service_major_outage": "大規模中斷",
77
- "today_text": "今日:${cost} ({tokens} tokens)",
78
- "yesterday_text": "昨日:${cost} ({tokens} tokens)",
80
+ "today_label": "今日:",
81
+ "yesterday_label": "昨日:",
79
82
  "percent_used": "{value}% 已用",
80
83
  "reset_in": "重置 {time}",
81
84
  "reset_imminent": "即將重置",
@@ -578,6 +581,9 @@
578
581
  "panel_world_cup": "World Cup 2026",
579
582
  "panel_ai_daily": "AI Update Daily",
580
583
  "switch_panel": "Switch Panel",
584
+ "settings_button": "⚙ Settings",
585
+ "statusline_sync_menu": "Sync quota via status line",
586
+ "statusline_sync_tooltip": "Reads the Claude quota from Claude Code's status line. Turn it off and the Claude quota stops updating.",
581
587
  "launch_at_login": "Launch at Login",
582
588
  "reset_panel_position": "Reset panel position",
583
589
  "hide_sections_menu": "Hide Sections",
@@ -633,8 +639,8 @@
633
639
  "service_degraded": "Degraded performance",
634
640
  "service_partial_outage": "Partial outage",
635
641
  "service_major_outage": "Major outage",
636
- "today_text": "Today: ${cost} ({tokens} tokens)",
637
- "yesterday_text": "Yesterday: ${cost} ({tokens} tokens)",
642
+ "today_label": "Today:",
643
+ "yesterday_label": "Yesterday:",
638
644
  "percent_used": "{value}% used",
639
645
  "reset_in": "Resets in {time}",
640
646
  "reset_imminent": "Reset imminent",
@@ -1137,6 +1143,9 @@
1137
1143
  "panel_world_cup": "世界杯 2026",
1138
1144
  "panel_ai_daily": "AI 更新日报",
1139
1145
  "switch_panel": "切换面板",
1146
+ "settings_button": "⚙ 设置",
1147
+ "statusline_sync_menu": "状态栏同步额度",
1148
+ "statusline_sync_tooltip": "从 Claude Code 的状态栏读取额度。关闭后,Claude 的额度就不会再更新。",
1140
1149
  "launch_at_login": "开机自动启动",
1141
1150
  "reset_panel_position": "重设面板位置",
1142
1151
  "hide_sections_menu": "隐藏区块",
@@ -1192,8 +1201,8 @@
1192
1201
  "service_degraded": "性能降级",
1193
1202
  "service_partial_outage": "部分中断",
1194
1203
  "service_major_outage": "大规模中断",
1195
- "today_text": "今日:${cost} ({tokens} tokens)",
1196
- "yesterday_text": "昨天:${cost} ({tokens} tokens)",
1204
+ "today_label": "今日:",
1205
+ "yesterday_label": "昨天:",
1197
1206
  "percent_used": "{value}% 已用",
1198
1207
  "reset_in": "重置 {time}",
1199
1208
  "reset_imminent": "即将重置",
@@ -1696,6 +1705,9 @@
1696
1705
  "panel_world_cup": "ワールドカップ 2026",
1697
1706
  "panel_ai_daily": "AI更新日報",
1698
1707
  "switch_panel": "パネル切替",
1708
+ "settings_button": "⚙ 設定",
1709
+ "statusline_sync_menu": "ステータスラインで使用量を同期",
1710
+ "statusline_sync_tooltip": "Claude Code のステータスラインから使用量を読み取ります。オフにすると Claude の使用量は更新されなくなります。",
1699
1711
  "launch_at_login": "ログイン時に起動",
1700
1712
  "reset_panel_position": "パネルの位置をリセット",
1701
1713
  "hide_sections_menu": "セクションを非表示",
@@ -1751,8 +1763,8 @@
1751
1763
  "service_degraded": "パフォーマンス低下",
1752
1764
  "service_partial_outage": "一部障害",
1753
1765
  "service_major_outage": "大規模障害",
1754
- "today_text": "今日: ${cost} ({tokens} tokens)",
1755
- "yesterday_text": "昨日: ${cost} ({tokens} tokens)",
1766
+ "today_label": "今日:",
1767
+ "yesterday_label": "昨日:",
1756
1768
  "percent_used": "{value}% 使用済み",
1757
1769
  "reset_in": "{time}後にリセット",
1758
1770
  "reset_imminent": "まもなくリセット",
@@ -2255,6 +2267,9 @@
2255
2267
  "panel_world_cup": "월드컵 2026",
2256
2268
  "panel_ai_daily": "AI 업데이트 일보",
2257
2269
  "switch_panel": "패널 전환",
2270
+ "settings_button": "⚙ 설정",
2271
+ "statusline_sync_menu": "상태 표시줄로 사용량 동기화",
2272
+ "statusline_sync_tooltip": "Claude Code 상태 표시줄에서 사용량을 읽습니다. 끄면 Claude 사용량이 더 이상 업데이트되지 않습니다.",
2258
2273
  "launch_at_login": "로그인 시 시작",
2259
2274
  "reset_panel_position": "패널 위치 재설정",
2260
2275
  "hide_sections_menu": "섹션 숨기기",
@@ -2310,8 +2325,8 @@
2310
2325
  "service_degraded": "성능 저하",
2311
2326
  "service_partial_outage": "부분 장애",
2312
2327
  "service_major_outage": "대규모 장애",
2313
- "today_text": "오늘: ${cost} ({tokens} tokens)",
2314
- "yesterday_text": "어제: ${cost} ({tokens} tokens)",
2328
+ "today_label": "오늘:",
2329
+ "yesterday_label": "어제:",
2315
2330
  "percent_used": "{value}% 사용됨",
2316
2331
  "reset_in": "{time} 후 초기화",
2317
2332
  "reset_imminent": "곧 초기화",
@@ -20,6 +20,7 @@ from pathlib import Path
20
20
  from typing import TypedDict, TypeGuard
21
21
 
22
22
  from loaders import muse_loader
23
+ from usage_common.subprocess_utils import hidden_console_kwargs
23
24
 
24
25
  logger = logging.getLogger(__name__)
25
26
  CACHE_PATH = Path.home() / ".usage/muse_quota.json"
@@ -203,6 +204,7 @@ def _probe_worker(executable: str) -> None:
203
204
  stderr=subprocess.PIPE,
204
205
  encoding="utf-8",
205
206
  cwd=PROBE_DIR,
207
+ **hidden_console_kwargs(),
206
208
  )
207
209
  _probe_process = process
208
210
  try:
@@ -138,10 +138,7 @@ from menubar.state import (
138
138
  _group_name as _group_name,
139
139
  )
140
140
  from menubar.state import (
141
- _statusline_payload as _statusline_payload,
142
- )
143
- from menubar.state import (
144
- _today_title as _today_title,
141
+ _today_total as _today_total,
145
142
  )
146
143
  from menubar.state import (
147
144
  format_human_time as format_human_time,
@@ -924,7 +921,6 @@ class AppDelegate(NSObject):
924
921
 
925
922
  def _finishStatuslineAction_(self, result: dict[str, Any]) -> None:
926
923
  self._refresh()
927
- self._refresh_statusline_state()
928
924
  if result.get("ok", True):
929
925
  return
930
926
  alert = _make_alert()
@@ -932,10 +928,6 @@ class AppDelegate(NSObject):
932
928
  alert.setInformativeText_(str(result.get("output") or result.get("action") or ""))
933
929
  alert.runModal()
934
930
 
935
- def _refresh_statusline_state(self) -> None:
936
- self.latest_state.statusline = _statusline_payload(self.language)
937
- self.popover_controller.setState_(self.latest_state)
938
-
939
931
  def _analyze_usage_in_background(self, period: str) -> None:
940
932
  menubar_actions.analyze_usage_in_background(self, period)
941
933
 
@@ -11,6 +11,7 @@ from typing import Any, Protocol
11
11
  from AppKit import NSMakePoint, NSMenu, NSMenuItem
12
12
 
13
13
  from installer import claude_pane, login_item, session_hooks
14
+ from installer.statusline_settings import _statusline_enabled
14
15
  from menubar.new_badge import apply_badge
15
16
  from menubar.prefs import (
16
17
  _hide_agy_enabled,
@@ -205,6 +206,16 @@ def build_switch_menu(app: _SwitchMenuApp, sender: Any) -> None:
205
206
  menu.addItem_(NSMenuItem.separatorItem())
206
207
  # Claude Code-only switches stay collapsed in a submenu, like the panel themes.
207
208
  claude_submenu = NSMenu.alloc().initWithTitle_(_t(app.language, "claude_code_section"))
209
+ claude_submenu.addItem_(
210
+ build_menu_item(
211
+ app.language,
212
+ "statusline_sync_menu",
213
+ "toggleStatusline:",
214
+ target=app,
215
+ state=_statusline_enabled(),
216
+ tooltip_key="statusline_sync_tooltip",
217
+ )
218
+ )
208
219
  claude_pane_item = build_menu_item(
209
220
  app.language,
210
221
  "claude_pane_section_menu",
@@ -140,9 +140,8 @@ def build_result(app: _RefreshApp, sources: RefreshSources) -> dict[str, Any]:
140
140
  project_rows_7d = list(fallback_state.projects_7d)
141
141
  project_rows_30d = list(fallback_state.projects_30d)
142
142
  project_rows_all = list(fallback_state.projects_all)
143
- today_text = fallback_state.today_text
144
- yesterday_text = fallback_state.yesterday_text
145
- statusline = fallback_state.statusline
143
+ today_total = fallback_state.today_total
144
+ yesterday_total = fallback_state.yesterday_total
146
145
  hide_claude = fallback_state.hide_claude
147
146
  hide_codex = fallback_state.hide_codex
148
147
  hide_agy = agy_result.hide_agy or _hide_agy_enabled()
@@ -171,9 +170,8 @@ def build_result(app: _RefreshApp, sources: RefreshSources) -> dict[str, Any]:
171
170
  project_rows_all,
172
171
  ) = menubar_state.project_rows_for_windows(all_entries)
173
172
  sources.measure("project_rows_windows", started_at)
174
- today_text = menubar_state._today_title(app.mock, app.language, entries=all_entries)
175
- yesterday_text = menubar_state._yesterday_title(app.mock, app.language, entries=all_entries)
176
- statusline = menubar_state._statusline_payload(app.language)
173
+ today_total = menubar_state._today_total(app.mock, entries=all_entries)
174
+ yesterday_total = menubar_state._yesterday_total(app.mock, entries=all_entries)
177
175
  hide_claude = _hide_claude_enabled()
178
176
  hide_codex = _hide_codex_enabled()
179
177
  hide_agy = agy_result.hide_agy or _hide_agy_enabled()
@@ -219,9 +217,8 @@ def build_result(app: _RefreshApp, sources: RefreshSources) -> dict[str, Any]:
219
217
  language=app.language,
220
218
  group=group,
221
219
  burn_rate_trackers=app.burn_rate_trackers,
222
- today_text=today_text,
223
- yesterday_text=yesterday_text,
224
- statusline=statusline,
220
+ today_total=today_total,
221
+ yesterday_total=yesterday_total,
225
222
  show_install_button=show_install_button,
226
223
  hide_claude=hide_claude,
227
224
  hide_codex=hide_codex,
@@ -283,9 +280,8 @@ def build_result(app: _RefreshApp, sources: RefreshSources) -> dict[str, Any]:
283
280
  state.projects_7d = project_rows_7d
284
281
  state.projects_30d = project_rows_30d
285
282
  state.projects_all = project_rows_all
286
- state.today_text = today_text
287
- state.yesterday_text = yesterday_text
288
- state.statusline = statusline
283
+ state.today_total = today_total
284
+ state.yesterday_total = yesterday_total
289
285
  state.hide_claude = hide_claude
290
286
  state.hide_codex = hide_codex
291
287
  state.hide_agy = hide_agy
@@ -16,7 +16,6 @@ from datetime import time as datetime_time
16
16
  from pathlib import Path
17
17
  from typing import TYPE_CHECKING, Any, NotRequired, Protocol, TypedDict, cast
18
18
 
19
- from installer.statusline_settings import _statusline_enabled
20
19
  from loaders import codex_loader, grok_loader, muse_loader
21
20
  from loaders.claude_paths import claude_config_dirs
22
21
  from loaders.claude_usage import PollOutcome, PollState
@@ -90,6 +89,12 @@ def _bar_color(pct: float, brand: tuple[float, float, float]) -> tuple[float, fl
90
89
  return brand
91
90
 
92
91
 
92
+ @dataclass(slots=True, frozen=True)
93
+ class PeriodTotal:
94
+ tokens: int = 0
95
+ cost: float = 0.0
96
+
97
+
93
98
  @dataclass(slots=True)
94
99
  class QuotaRowState:
95
100
  title: str
@@ -155,9 +160,8 @@ class PopoverState:
155
160
  projects_all: list[tuple[str, int, float | None]]
156
161
  rate_text: str
157
162
  status_text: str
158
- today_text: str
159
- yesterday_text: str
160
- statusline: dict[str, object]
163
+ today_total: PeriodTotal
164
+ yesterday_total: PeriodTotal
161
165
  service_alerts: tuple[str, ...] = ()
162
166
  show_install_button: bool = False
163
167
  status_long: bool = False
@@ -855,9 +859,8 @@ def build_popover_state(
855
859
  language: str,
856
860
  group: int,
857
861
  burn_rate_trackers: dict[str, BurnRateTracker],
858
- today_text: str,
859
- yesterday_text: str,
860
- statusline: dict[str, object],
862
+ today_total: PeriodTotal,
863
+ yesterday_total: PeriodTotal,
861
864
  show_install_button: bool,
862
865
  hide_claude: bool,
863
866
  hide_codex: bool,
@@ -988,9 +991,8 @@ def build_popover_state(
988
991
  projects_all=projects_all,
989
992
  rate_text=_t(language, "rate_text", value=group_name),
990
993
  status_text=status_text,
991
- today_text=today_text,
992
- yesterday_text=yesterday_text,
993
- statusline=statusline,
994
+ today_total=today_total,
995
+ yesterday_total=yesterday_total,
994
996
  service_alerts=service_alerts,
995
997
  show_install_button=show_install_button,
996
998
  status_long=status_long,
@@ -1107,9 +1109,8 @@ def _missing_row(
1107
1109
 
1108
1110
 
1109
1111
  def _format_percent(value: float) -> str:
1110
- if value.is_integer():
1111
- return str(int(value))
1112
- return f"{value:.1f}"
1112
+ # Whole numbers only, rounded down so 99.7% never reads as an exhausted 100%.
1113
+ return str(int(value))
1113
1114
 
1114
1115
 
1115
1116
  def popover_dimensions(state: PopoverState, panel: UsagePanel | None = None) -> tuple[float, float]:
@@ -1222,9 +1223,8 @@ def _empty_state(language: str = "en") -> PopoverState:
1222
1223
  projects_all=[],
1223
1224
  rate_text=_t(language, "rate_text", value="--"),
1224
1225
  status_text=_t(language, "status_text", value=_t(language, "status_loading")),
1225
- today_text=_t(language, "today_text", cost="0.00", tokens="0"),
1226
- yesterday_text=_t(language, "yesterday_text", cost="0.00", tokens="0"),
1227
- statusline=_statusline_payload(language),
1226
+ today_total=PeriodTotal(),
1227
+ yesterday_total=PeriodTotal(),
1228
1228
  service_alerts=(),
1229
1229
  show_install_button=False,
1230
1230
  hide_claude=_hide_claude_enabled(),
@@ -1248,27 +1248,18 @@ def _error_state(message: str, mock: bool, language: str = "en") -> PopoverState
1248
1248
  "status_text",
1249
1249
  value=_t(language, "status_error", message=message),
1250
1250
  )
1251
- state.today_text = _today_title(mock, language)
1252
- state.yesterday_text = _yesterday_title(mock, language)
1251
+ state.today_total = _today_total(mock)
1252
+ state.yesterday_total = _yesterday_total(mock)
1253
1253
  state.show_install_button = False
1254
1254
  return state
1255
1255
 
1256
1256
 
1257
- def _statusline_payload(language: str) -> dict[str, object]:
1258
- return {
1259
- "enabled": _statusline_enabled(),
1260
- "enabledText": _t(language, "cli_enabled"),
1261
- "disabledText": _t(language, "cli_disabled"),
1262
- }
1263
-
1264
-
1265
- def _today_title(
1257
+ def _today_total(
1266
1258
  mock: bool = False,
1267
- language: str = "en",
1268
1259
  entries: list[UsageEntry] | None = None,
1269
- ) -> str:
1260
+ ) -> PeriodTotal:
1270
1261
  if mock:
1271
- return _t(language, "today_text", cost="45.20", tokens="50,193,442")
1262
+ return PeriodTotal(tokens=50_193_442, cost=45.20)
1272
1263
 
1273
1264
  try:
1274
1265
  today = datetime.now().astimezone().date()
@@ -1290,18 +1281,17 @@ def _today_title(
1290
1281
  except Exception:
1291
1282
  if os.environ.get("USAGE_DEBUG") == "1":
1292
1283
  logger.warning("today totals load failed", exc_info=True)
1293
- return _t(language, "today_text", cost="0.00", tokens="0")
1284
+ return PeriodTotal()
1294
1285
 
1295
- return _t(language, "today_text", cost=f"{total_cost:.2f}", tokens=f"{total_tokens:,}")
1286
+ return PeriodTotal(tokens=total_tokens, cost=total_cost)
1296
1287
 
1297
1288
 
1298
- def _yesterday_title(
1289
+ def _yesterday_total(
1299
1290
  mock: bool = False,
1300
- language: str = "en",
1301
1291
  entries: list[UsageEntry] | None = None,
1302
- ) -> str:
1292
+ ) -> PeriodTotal:
1303
1293
  if mock:
1304
- return _t(language, "yesterday_text", cost="41.10", tokens="48,200,000")
1294
+ return PeriodTotal(tokens=48_200_000, cost=41.10)
1305
1295
 
1306
1296
  try:
1307
1297
  yesterday = datetime.now().astimezone().date() - timedelta(days=1)
@@ -1318,11 +1308,6 @@ def _yesterday_title(
1318
1308
  except Exception:
1319
1309
  if os.environ.get("USAGE_DEBUG") == "1":
1320
1310
  logger.warning("yesterday totals load failed", exc_info=True)
1321
- return _t(language, "yesterday_text", cost="0.00", tokens="0")
1311
+ return PeriodTotal()
1322
1312
 
1323
- return _t(
1324
- language,
1325
- "yesterday_text",
1326
- cost=f"{total_cost:.2f}",
1327
- tokens=f"{total_tokens:,}",
1328
- )
1313
+ return PeriodTotal(tokens=total_tokens, cost=total_cost)
@@ -14,7 +14,7 @@ from typing import TYPE_CHECKING, Any
14
14
  from usage_common.i18n import _load_i18n_bundle
15
15
 
16
16
  if TYPE_CHECKING:
17
- from menubar.state import PopoverState, QuotaRowState
17
+ from menubar.state import PeriodTotal, PopoverState, QuotaRowState
18
18
 
19
19
  CORE_SCRIPT_FILENAME = "panels/panel_core.js"
20
20
 
@@ -155,12 +155,11 @@ def _state_payload(
155
155
  "hideMuse": state.hide_muse,
156
156
  "cardOrder": list(state.card_order),
157
157
  "historyError": state.history_error,
158
- "statusline": state.statusline,
159
158
  "footer": {
160
159
  "rate": state.rate_text,
161
160
  "status": state.status_text,
162
- "today": state.today_text,
163
- "yesterday": state.yesterday_text,
161
+ "today": _period_payload(state.today_total),
162
+ "yesterday": _period_payload(state.yesterday_total),
164
163
  "serviceAlerts": list(state.service_alerts),
165
164
  "showInstall": state.show_install_button,
166
165
  },
@@ -176,3 +175,7 @@ def _fmt_tokens(tokens: int) -> str:
176
175
 
177
176
  def _fmt_cost(cost: float | None) -> str:
178
177
  return "--" if cost is None else f"${cost:.2f}"
178
+
179
+
180
+ def _period_payload(total: PeriodTotal) -> dict[str, str]:
181
+ return {"tokensText": _fmt_tokens(total.tokens), "costText": _fmt_cost(total.cost)}
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "usage-cli"
3
- version = "0.34.0"
3
+ version = "0.34.2"
4
4
  description = "usage — 在 macOS menu bar 與 Windows 系統匣顯示 Claude Code、Codex、Antigravity 用量的小工具(也提供終端機 TUI)"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -18,7 +18,7 @@ dependencies = [
18
18
  [project.optional-dependencies]
19
19
  windows = [
20
20
  "pystray>=0.19.5; sys_platform == 'win32'",
21
- "pillow>=11.0.0; sys_platform == 'win32'",
21
+ "pillow>=13.0.0; sys_platform == 'win32'",
22
22
  "pywebview>=5.4; sys_platform == 'win32'",
23
23
  "windows-toasts>=1.3.1; sys_platform == 'win32'",
24
24
  ]
@@ -275,7 +275,7 @@ def test_html_panels_expose_analyze_action() -> None:
275
275
  assert 'data-i18n="analyze_usage"' in html, path.name
276
276
  assert "analyze_all" not in html, path.name
277
277
  assert "projectsAll" in html, path.name
278
- assert 'data-action="toggle-statusline"' in html, path.name
278
+ assert 'data-action="toggle-statusline"' not in html, path.name
279
279
 
280
280
 
281
281
  def test_generate_analysis_report_uses_analyzer_pipeline(
@@ -84,7 +84,9 @@ const context = vm.createContext({
84
84
  document: {querySelector: selector => selector.includes('[data-row=')
85
85
  ? rowEl : hasCards ? {} : null},
86
86
  getComputedStyle: () => ({getPropertyValue: () => 'red'}),
87
- window: {PanelHooks: {accentFallback: () => 'red', applyFillVisual() {}}},
87
+ window: {PanelHooks: {
88
+ accentFallback: () => 'red', percentColor: (accent) => accent, applyFillVisual() {},
89
+ }},
88
90
  colorFor: () => 'red', labels: () => ({session: 'Session'}), t: key => key,
89
91
  });
90
92
  const toggle = 'function toggle(state) { const visibleQuotaCards =' + process.argv[1] + '}';
@@ -170,9 +170,8 @@ def test_desktop_quota_reaches_popover_and_tui(quota_file: Path) -> None:
170
170
  burn_rate_trackers={
171
171
  name: BurnRateTracker() for name in ("claude_session", "claude_weekly")
172
172
  },
173
- today_text="",
174
- yesterday_text="",
175
- statusline={},
173
+ today_total=state_module.PeriodTotal(),
174
+ yesterday_total=state_module.PeriodTotal(),
176
175
  show_install_button=False,
177
176
  hide_claude=False,
178
177
  hide_codex=False,
@@ -180,9 +180,8 @@ def _build_popover_state(
180
180
  language=delegate.language,
181
181
  group=delegate.tracker.group(),
182
182
  burn_rate_trackers=delegate.burn_rate_trackers,
183
- today_text=menubar._today_title(delegate.mock, delegate.language),
184
- yesterday_text=menubar_state._yesterday_title(delegate.mock, delegate.language),
185
- statusline=menubar._statusline_payload(delegate.language),
183
+ today_total=menubar._today_total(delegate.mock),
184
+ yesterday_total=menubar_state._yesterday_total(delegate.mock),
186
185
  show_install_button=(
187
186
  not hide_claude
188
187
  and outcome.state == PollState.TOKEN_ERROR
@@ -241,7 +240,8 @@ def test_format_human_time_minutes_hours_and_days() -> None:
241
240
 
242
241
  def test_format_percent() -> None:
243
242
  assert menubar._format_percent(50.0) == "50"
244
- assert menubar._format_percent(50.5) == "50.5"
243
+ assert menubar._format_percent(50.5) == "50"
244
+ assert menubar._format_percent(99.7) == "99"
245
245
  assert menubar._format_percent(0.0) == "0"
246
246
 
247
247
 
@@ -310,7 +310,7 @@ def test_quota_row_formats_available_row() -> None:
310
310
 
311
311
  assert row.available is True
312
312
  assert row.percent == 50.5
313
- assert row.percent_text == "50.5% 已用"
313
+ assert row.percent_text == "50% 已用"
314
314
  assert row.reset_text.startswith("重置 ")
315
315
  assert row.warning is False
316
316
  assert row.color == menubar.WARN_COLOR
@@ -548,11 +548,13 @@ def test_quota_row_keeps_reset_text_when_percent_is_below_warning_floor() -> Non
548
548
  assert row.reset_text == "重置 51分鐘"
549
549
 
550
550
 
551
- def test_today_title_mock() -> None:
552
- assert menubar._today_title(mock=True, language="zh-TW") == "今日:$45.20 (50,193,442 tokens)"
551
+ def test_today_total_mock() -> None:
552
+ assert menubar._today_total(mock=True) == menubar_state.PeriodTotal(
553
+ tokens=50_193_442, cost=45.20
554
+ )
553
555
 
554
556
 
555
- def test_today_title_returns_zero_fallback_when_loaders_fail(
557
+ def test_today_total_returns_zero_fallback_when_loaders_fail(
556
558
  monkeypatch: pytest.MonkeyPatch,
557
559
  ) -> None:
558
560
  monkeypatch.setattr(
@@ -561,10 +563,10 @@ def test_today_title_returns_zero_fallback_when_loaders_fail(
561
563
  lambda *, hours_back=24: (_ for _ in ()).throw(OSError),
562
564
  )
563
565
 
564
- assert menubar._today_title(mock=False, language="zh-TW") == "今日:$0.00 (0 tokens)"
566
+ assert menubar._today_total(mock=False) == menubar_state.PeriodTotal()
565
567
 
566
568
 
567
- def test_today_title_does_not_reload_codex_when_entries_are_provided(
569
+ def test_today_total_does_not_reload_codex_when_entries_are_provided(
568
570
  monkeypatch: pytest.MonkeyPatch,
569
571
  ) -> None:
570
572
  entry = history_loader.UsageEntry(
@@ -585,12 +587,12 @@ def test_today_title_does_not_reload_codex_when_entries_are_provided(
585
587
  lambda *, hours_back=24: pytest.fail("Codex should already be included"),
586
588
  )
587
589
 
588
- assert menubar._today_title(mock=False, language="en", entries=[entry]) == (
589
- "Today: $0.01 (150 tokens)"
590
+ assert menubar._today_total(mock=False, entries=[entry]) == menubar_state.PeriodTotal(
591
+ tokens=150, cost=0.01
590
592
  )
591
593
 
592
594
 
593
- def test_yesterday_title_uses_provided_entries() -> None:
595
+ def test_yesterday_total_uses_provided_entries() -> None:
594
596
  entry = history_loader.UsageEntry(
595
597
  timestamp=datetime.now(tz=UTC) - timedelta(days=1),
596
598
  session_id="codex",
@@ -605,13 +607,13 @@ def test_yesterday_title_uses_provided_entries() -> None:
605
607
  project="usage",
606
608
  )
607
609
 
608
- assert menubar_state._yesterday_title(False, "en", entries=[entry]) == (
609
- "Yesterday: $0.01 (150 tokens)"
610
+ assert menubar_state._yesterday_total(False, entries=[entry]) == menubar_state.PeriodTotal(
611
+ tokens=150, cost=0.01
610
612
  )
611
613
 
612
614
 
613
615
  @pytest.mark.skipif(not hasattr(time, "tzset"), reason="requires POSIX timezone control")
614
- def test_yesterday_title_uses_dst_specific_day_boundaries(
616
+ def test_yesterday_total_uses_dst_specific_day_boundaries(
615
617
  monkeypatch: pytest.MonkeyPatch,
616
618
  ) -> None:
617
619
  class FixedDateTime(datetime):
@@ -647,8 +649,8 @@ def test_yesterday_title_uses_dst_specific_day_boundaries(
647
649
  entry(datetime(2026, 3, 9, 4, 0, tzinfo=UTC)),
648
650
  ]
649
651
 
650
- assert menubar_state._yesterday_title(False, "en", entries=entries) == (
651
- "Yesterday: $0.01 (10 tokens)"
652
+ assert menubar_state._yesterday_total(False, entries=entries) == (
653
+ menubar_state.PeriodTotal(tokens=10, cost=0.01)
652
654
  )
653
655
  finally:
654
656
  if previous_tz is None:
@@ -673,9 +675,8 @@ def test_empty_state() -> None:
673
675
  assert state.projects_7d == []
674
676
  assert state.projects_30d == []
675
677
  assert state.projects_all == []
676
- assert isinstance(state.statusline["enabled"], bool)
677
678
  assert state.show_install_button is False
678
- assert state.yesterday_text == "Yesterday: $0.00 (0 tokens)"
679
+ assert state.yesterday_total == menubar_state.PeriodTotal()
679
680
 
680
681
 
681
682
  @pytest.mark.parametrize("quota_enabled", [True, False])
@@ -760,11 +761,14 @@ def test_switch_panel_menu_contains_update_items(
760
761
  ]
761
762
  assert settings[7].submenu is claude_submenu
762
763
  assert [item.title for item in claude_submenu.items] == [
764
+ "Sync quota via status line",
763
765
  "Side pane",
764
766
  "Beginner Mode",
765
767
  "Quota-Aware Mode",
766
768
  ]
767
- pane, beginner, quota = claude_submenu.items
769
+ statusline, pane, beginner, quota = claude_submenu.items
770
+ assert statusline.action == "toggleStatusline:"
771
+ assert statusline.tooltip == _t("en", "statusline_sync_tooltip")
768
772
  assert pane.title == _t("en", "claude_pane_section_menu")
769
773
  assert pane.action == "toggleClaudePane:"
770
774
  assert pane.tooltip == _t("en", "claude_pane_tooltip")
@@ -1415,11 +1419,11 @@ def test_statusline_switch_survives_agy_failure(
1415
1419
  assert menubar._enable_statusline_settings() == 0
1416
1420
 
1417
1421
 
1418
- def test_error_state_uses_message_and_mock_today_title() -> None:
1422
+ def test_error_state_uses_message_and_mock_today_total() -> None:
1419
1423
  state = menubar._error_state("boom", mock=True, language="zh-TW")
1420
1424
 
1421
1425
  assert "boom" in state.status_text
1422
- assert state.today_text == "今日:$45.20 (50,193,442 tokens)"
1426
+ assert state.today_total == menubar_state.PeriodTotal(tokens=50_193_442, cost=45.20)
1423
1427
 
1424
1428
 
1425
1429
  def test_popover_size_has_positive_dimensions() -> None:
@@ -2447,7 +2451,7 @@ def test_apply_codex_refresh_result_updates_quota_before_full_refresh(
2447
2451
  assert delegate.codex_5h_pct == 18.5
2448
2452
  assert delegate.codex_model == "gpt-test"
2449
2453
  assert controller.calls == [delegate.latest_state]
2450
- assert button.titles[-1].endswith("18.5%")
2454
+ assert button.titles[-1].endswith("18%")
2451
2455
 
2452
2456
 
2453
2457
  def test_refresh_error_preserves_codex_quota(monkeypatch: pytest.MonkeyPatch) -> None:
@@ -2601,7 +2605,7 @@ def test_refresh_error_preserves_project_usage(monkeypatch: pytest.MonkeyPatch)
2601
2605
  assert state.projects_7d == [("Eric-Tools", 165, 0.01)]
2602
2606
  assert state.projects_30d == [("Eric-Tools", 165, 0.01)]
2603
2607
  assert state.projects_all == [("Eric-Tools", 165, 0.01)]
2604
- assert "165 tokens" in state.today_text
2608
+ assert state.today_total.tokens == 165
2605
2609
 
2606
2610
 
2607
2611
  def test_refresh_success_builds_popover_state_and_pings_window_keeper(
@@ -2653,11 +2657,6 @@ def test_refresh_success_builds_popover_state_and_pings_window_keeper(
2653
2657
  lambda reset_at, percent, source, mock: pings.append((reset_at, percent, source, mock)),
2654
2658
  )
2655
2659
  monkeypatch.setattr(agy_window_keeper, "maybe_ping", lambda *args: None)
2656
- monkeypatch.setattr(
2657
- menubar_state,
2658
- "_statusline_payload",
2659
- lambda language: {"enabled": False, "language": language},
2660
- )
2661
2660
 
2662
2661
  class Delegate:
2663
2662
  mock = False
@@ -3378,7 +3377,6 @@ def test_refresh_preserves_muse_on_success_and_fetch_error(
3378
3377
  "get_service_status",
3379
3378
  lambda config: ServiceStatus(config.service_name, False, "operational", "", "cache"),
3380
3379
  )
3381
- monkeypatch.setattr(state, "_statusline_payload", lambda _language: {})
3382
3380
  monkeypatch.setattr("quota.codex_window_keeper.maybe_ping", lambda _mock: None)
3383
3381
  monkeypatch.setattr("quota.agy_window_keeper.maybe_ping", lambda *_args: None)
3384
3382