usage-cli 0.32.3__tar.gz → 0.32.5__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 (246) hide show
  1. {usage_cli-0.32.3 → usage_cli-0.32.5}/PKG-INFO +6 -3
  2. {usage_cli-0.32.3 → usage_cli-0.32.5}/README.md +4 -2
  3. usage_cli-0.32.5/THIRD_PARTY_NOTICES.md +28 -0
  4. {usage_cli-0.32.3 → usage_cli-0.32.5}/doctor.py +8 -0
  5. {usage_cli-0.32.3 → usage_cli-0.32.5}/i18n.json +95 -5
  6. usage_cli-0.32.5/installer/codex_hook_trust.py +66 -0
  7. {usage_cli-0.32.3 → usage_cli-0.32.5}/installer/setup_hook.py +1 -1
  8. {usage_cli-0.32.3 → usage_cli-0.32.5}/loaders/agy_quota_probe.py +34 -1
  9. {usage_cli-0.32.3 → usage_cli-0.32.5}/menubar/agy.py +6 -0
  10. {usage_cli-0.32.3 → usage_cli-0.32.5}/menubar/app.py +5 -36
  11. {usage_cli-0.32.3 → usage_cli-0.32.5}/menubar/menu.py +14 -0
  12. usage_cli-0.32.5/menubar/notify.py +219 -0
  13. {usage_cli-0.32.3 → usage_cli-0.32.5}/menubar/prefs.py +0 -5
  14. {usage_cli-0.32.3 → usage_cli-0.32.5}/menubar/refresh.py +7 -0
  15. {usage_cli-0.32.3 → usage_cli-0.32.5}/menubar/state.py +2 -1
  16. {usage_cli-0.32.3 → usage_cli-0.32.5}/menubar/update.py +33 -50
  17. {usage_cli-0.32.3 → usage_cli-0.32.5}/pyproject.toml +2 -2
  18. {usage_cli-0.32.3 → usage_cli-0.32.5}/quota/agy_window_keeper.py +37 -54
  19. {usage_cli-0.32.3 → usage_cli-0.32.5}/quota/codex_window_keeper.py +38 -64
  20. usage_cli-0.32.5/quota/keeper_outcome.py +195 -0
  21. {usage_cli-0.32.3 → usage_cli-0.32.5}/quota/window_keeper.py +34 -62
  22. {usage_cli-0.32.3 → usage_cli-0.32.5}/setup_app.py +1 -0
  23. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_agy_quota_probe.py +88 -0
  24. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_agy_window_keeper.py +1 -1
  25. usage_cli-0.32.5/tests/test_check_upstream.py +573 -0
  26. usage_cli-0.32.5/tests/test_codex_hook_trust.py +89 -0
  27. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_doctor.py +2 -1
  28. usage_cli-0.32.5/tests/test_keeper_menu.py +83 -0
  29. usage_cli-0.32.5/tests/test_keeper_outcome.py +483 -0
  30. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_menubar.py +54 -75
  31. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_menubar_agy.py +12 -0
  32. usage_cli-0.32.5/tests/test_menubar_notify.py +91 -0
  33. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_menubar_prefs.py +0 -5
  34. usage_cli-0.32.5/tests/test_menubar_update.py +35 -0
  35. usage_cli-0.32.5/tests/test_quota_label.py +425 -0
  36. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_update_checker.py +30 -0
  37. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_update_gate.py +21 -0
  38. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_usage_cli.py +13 -0
  39. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_usage_statusline.py +162 -0
  40. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_wintray.py +296 -10
  41. {usage_cli-0.32.3 → usage_cli-0.32.5}/updates/checker.py +9 -9
  42. {usage_cli-0.32.3 → usage_cli-0.32.5}/updates/gate.py +28 -0
  43. {usage_cli-0.32.3 → usage_cli-0.32.5}/usage_cli.egg-info/PKG-INFO +6 -3
  44. {usage_cli-0.32.3 → usage_cli-0.32.5}/usage_cli.egg-info/SOURCES.txt +13 -0
  45. {usage_cli-0.32.3 → usage_cli-0.32.5}/usage_cli.py +2 -0
  46. {usage_cli-0.32.3 → usage_cli-0.32.5}/usage_statusline.py +110 -2
  47. {usage_cli-0.32.3 → usage_cli-0.32.5}/wintray/app.py +163 -29
  48. {usage_cli-0.32.3 → usage_cli-0.32.5}/wintray/menu.py +19 -0
  49. usage_cli-0.32.5/wintray/prefs.py +25 -0
  50. usage_cli-0.32.5/wintray/quota_label.py +184 -0
  51. usage_cli-0.32.5/wintray/taskbar_obstacles.py +71 -0
  52. usage_cli-0.32.5/wintray/taskbar_overlay.py +296 -0
  53. usage_cli-0.32.3/menubar/notify.py +0 -119
  54. usage_cli-0.32.3/tests/test_menubar_notify.py +0 -37
  55. {usage_cli-0.32.3 → usage_cli-0.32.5}/LICENSE +0 -0
  56. {usage_cli-0.32.3 → usage_cli-0.32.5}/adapters/__init__.py +0 -0
  57. {usage_cli-0.32.3 → usage_cli-0.32.5}/adapters/agy.py +0 -0
  58. {usage_cli-0.32.3 → usage_cli-0.32.5}/adapters/claude.py +0 -0
  59. {usage_cli-0.32.3 → usage_cli-0.32.5}/adapters/codex.py +0 -0
  60. {usage_cli-0.32.3 → usage_cli-0.32.5}/adapters/grok.py +0 -0
  61. {usage_cli-0.32.3 → usage_cli-0.32.5}/adapters/muse.py +0 -0
  62. {usage_cli-0.32.3 → usage_cli-0.32.5}/adapters/rate_limits.py +0 -0
  63. {usage_cli-0.32.3 → usage_cli-0.32.5}/adapters/registry.py +0 -0
  64. {usage_cli-0.32.3 → usage_cli-0.32.5}/adapters/types.py +0 -0
  65. {usage_cli-0.32.3 → usage_cli-0.32.5}/analyzer/__init__.py +0 -0
  66. {usage_cli-0.32.3 → usage_cli-0.32.5}/analyzer/aggregator.py +0 -0
  67. {usage_cli-0.32.3 → usage_cli-0.32.5}/analyzer/blocks.py +0 -0
  68. {usage_cli-0.32.3 → usage_cli-0.32.5}/analyzer/diagnoser.py +0 -0
  69. {usage_cli-0.32.3 → usage_cli-0.32.5}/analyzer/dispatch_ledger.py +0 -0
  70. {usage_cli-0.32.3 → usage_cli-0.32.5}/analyzer/insights.py +0 -0
  71. {usage_cli-0.32.3 → usage_cli-0.32.5}/analyzer/persona_loader.py +0 -0
  72. {usage_cli-0.32.3 → usage_cli-0.32.5}/analyzer/reporter.py +0 -0
  73. {usage_cli-0.32.3 → usage_cli-0.32.5}/analyzer/subscription.py +0 -0
  74. {usage_cli-0.32.3 → usage_cli-0.32.5}/analyzer/usage_snapshot.py +0 -0
  75. {usage_cli-0.32.3 → usage_cli-0.32.5}/fsevents_watch.py +0 -0
  76. {usage_cli-0.32.3 → usage_cli-0.32.5}/i18n.py +0 -0
  77. {usage_cli-0.32.3 → usage_cli-0.32.5}/installer/__init__.py +0 -0
  78. {usage_cli-0.32.3 → usage_cli-0.32.5}/installer/claude_pane.py +0 -0
  79. {usage_cli-0.32.3 → usage_cli-0.32.5}/installer/login_item.py +0 -0
  80. {usage_cli-0.32.3 → usage_cli-0.32.5}/installer/session_hooks.py +0 -0
  81. {usage_cli-0.32.3 → usage_cli-0.32.5}/installer/statusline_settings.py +0 -0
  82. {usage_cli-0.32.3 → usage_cli-0.32.5}/loaders/__init__.py +0 -0
  83. {usage_cli-0.32.3 → usage_cli-0.32.5}/loaders/agy_disk_cache.py +0 -0
  84. {usage_cli-0.32.3 → usage_cli-0.32.5}/loaders/agy_loader.py +0 -0
  85. {usage_cli-0.32.3 → usage_cli-0.32.5}/loaders/cache_quarantine.py +0 -0
  86. {usage_cli-0.32.3 → usage_cli-0.32.5}/loaders/claude_paths.py +0 -0
  87. {usage_cli-0.32.3 → usage_cli-0.32.5}/loaders/codex_disk_cache.py +0 -0
  88. {usage_cli-0.32.3 → usage_cli-0.32.5}/loaders/codex_events.py +0 -0
  89. {usage_cli-0.32.3 → usage_cli-0.32.5}/loaders/codex_fork_replay.py +0 -0
  90. {usage_cli-0.32.3 → usage_cli-0.32.5}/loaders/codex_loader.py +0 -0
  91. {usage_cli-0.32.3 → usage_cli-0.32.5}/loaders/codex_paths.py +0 -0
  92. {usage_cli-0.32.3 → usage_cli-0.32.5}/loaders/disk_cache_common.py +0 -0
  93. {usage_cli-0.32.3 → usage_cli-0.32.5}/loaders/disk_cache_lifecycle.py +0 -0
  94. {usage_cli-0.32.3 → usage_cli-0.32.5}/loaders/grok_loader.py +0 -0
  95. {usage_cli-0.32.3 → usage_cli-0.32.5}/loaders/grok_quota_probe.py +0 -0
  96. {usage_cli-0.32.3 → usage_cli-0.32.5}/loaders/history_disk_cache.py +0 -0
  97. {usage_cli-0.32.3 → usage_cli-0.32.5}/loaders/history_loader.py +0 -0
  98. {usage_cli-0.32.3 → usage_cli-0.32.5}/loaders/jsonl_limits.py +0 -0
  99. {usage_cli-0.32.3 → usage_cli-0.32.5}/loaders/jsonl_utils.py +0 -0
  100. {usage_cli-0.32.3 → usage_cli-0.32.5}/loaders/muse_loader.py +0 -0
  101. {usage_cli-0.32.3 → usage_cli-0.32.5}/main.py +0 -0
  102. {usage_cli-0.32.3 → usage_cli-0.32.5}/menubar/__init__.py +0 -0
  103. {usage_cli-0.32.3 → usage_cli-0.32.5}/menubar/actions.py +0 -0
  104. {usage_cli-0.32.3 → usage_cli-0.32.5}/menubar/chrome.py +0 -0
  105. {usage_cli-0.32.3 → usage_cli-0.32.5}/menubar/grok.py +0 -0
  106. {usage_cli-0.32.3 → usage_cli-0.32.5}/menubar/manual_refresh.py +0 -0
  107. {usage_cli-0.32.3 → usage_cli-0.32.5}/menubar/new_badge.py +0 -0
  108. {usage_cli-0.32.3 → usage_cli-0.32.5}/menubar/popover.py +0 -0
  109. {usage_cli-0.32.3 → usage_cli-0.32.5}/menubar/title.py +0 -0
  110. {usage_cli-0.32.3 → usage_cli-0.32.5}/panels/__init__.py +0 -0
  111. {usage_cli-0.32.3 → usage_cli-0.32.5}/panels/base.py +0 -0
  112. {usage_cli-0.32.3 → usage_cli-0.32.5}/panels/dynamic_height.py +0 -0
  113. {usage_cli-0.32.3 → usage_cli-0.32.5}/panels/panel_scale.py +0 -0
  114. {usage_cli-0.32.3 → usage_cli-0.32.5}/panels/panel_window.py +0 -0
  115. {usage_cli-0.32.3 → usage_cli-0.32.5}/panels/panel_window_state.py +0 -0
  116. {usage_cli-0.32.3 → usage_cli-0.32.5}/panels/payload.py +0 -0
  117. {usage_cli-0.32.3 → usage_cli-0.32.5}/panels/web_panel.py +0 -0
  118. {usage_cli-0.32.3 → usage_cli-0.32.5}/panels/window_drag.py +0 -0
  119. {usage_cli-0.32.3 → usage_cli-0.32.5}/prefs.py +0 -0
  120. {usage_cli-0.32.3 → usage_cli-0.32.5}/pricing.py +0 -0
  121. {usage_cli-0.32.3 → usage_cli-0.32.5}/project_resolver.py +0 -0
  122. {usage_cli-0.32.3 → usage_cli-0.32.5}/quota/__init__.py +0 -0
  123. {usage_cli-0.32.3 → usage_cli-0.32.5}/quota/burn_rate.py +0 -0
  124. {usage_cli-0.32.3 → usage_cli-0.32.5}/quota/usage_rate.py +0 -0
  125. {usage_cli-0.32.3 → usage_cli-0.32.5}/service_status.py +0 -0
  126. {usage_cli-0.32.3 → usage_cli-0.32.5}/setup.cfg +0 -0
  127. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_adapters_claude.py +0 -0
  128. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_adapters_codex.py +0 -0
  129. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_adapters_rate_limits.py +0 -0
  130. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_aggregator.py +0 -0
  131. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_agy_adapter.py +0 -0
  132. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_agy_disk_cache.py +0 -0
  133. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_agy_loader.py +0 -0
  134. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_analyzer_blocks.py +0 -0
  135. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_analyzer_pipeline.py +0 -0
  136. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_burn_rate.py +0 -0
  137. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_cache_quarantine.py +0 -0
  138. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_claude_pane.py +0 -0
  139. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_claude_paths.py +0 -0
  140. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_codex_consistency.py +0 -0
  141. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_codex_loader.py +0 -0
  142. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_codex_window_keeper.py +0 -0
  143. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_diagnoser.py +0 -0
  144. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_disk_cache_shards.py +0 -0
  145. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_dispatch_ledger.py +0 -0
  146. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_dynamic_height.py +0 -0
  147. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_fsevents_watch.py +0 -0
  148. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_grok_loader.py +0 -0
  149. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_grok_quota_probe.py +0 -0
  150. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_history_loader.py +0 -0
  151. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_hook_scripts_are_standalone.py +0 -0
  152. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_html_report_snapshot.py +0 -0
  153. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_i18n_format_fallback.py +0 -0
  154. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_i18n_key_parity.py +0 -0
  155. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_i18n_packaged_path.py +0 -0
  156. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_insights.py +0 -0
  157. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_jsonl_utils.py +0 -0
  158. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_login_item.py +0 -0
  159. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_main.py +0 -0
  160. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_manual_refresh.py +0 -0
  161. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_menubar_grok.py +0 -0
  162. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_menubar_state.py +0 -0
  163. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_muse_loader.py +0 -0
  164. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_new_badge.py +0 -0
  165. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_packaged_resources.py +0 -0
  166. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_packaging.py +0 -0
  167. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_panel_footer_layout.py +0 -0
  168. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_panel_scale.py +0 -0
  169. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_panel_window_state.py +0 -0
  170. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_panels.py +0 -0
  171. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_panels_lazy_import.py +0 -0
  172. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_persona_loader.py +0 -0
  173. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_pricing.py +0 -0
  174. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_project_resolver.py +0 -0
  175. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_report_daily_chart.py +0 -0
  176. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_report_filter_js.py +0 -0
  177. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_reporter_build_data.py +0 -0
  178. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_service_status.py +0 -0
  179. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_session_resume.py +0 -0
  180. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_setup_app.py +0 -0
  181. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_setup_hook.py +0 -0
  182. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_setup_hook_coexistence.py +0 -0
  183. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_setup_hook_resume.py +0 -0
  184. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_setup_hook_terse.py +0 -0
  185. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_subprocess_utils.py +0 -0
  186. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_subscription.py +0 -0
  187. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_time_utils.py +0 -0
  188. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_tui.py +0 -0
  189. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_tui_sprite.py +0 -0
  190. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_ui_tables.py +0 -0
  191. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_update_release_notes.py +0 -0
  192. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_usage_client.py +0 -0
  193. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_usage_diagnosis_snapshot.py +0 -0
  194. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_usage_dir_sweeper.py +0 -0
  195. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_usage_lang.py +0 -0
  196. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_usage_logging.py +0 -0
  197. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_usage_notifications.py +0 -0
  198. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_usage_rate.py +0 -0
  199. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_usage_snapshot.py +0 -0
  200. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_usage_snapshot_replay.py +0 -0
  201. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_usage_statusline_agy.py +0 -0
  202. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_usage_statusline_forwarder.py +0 -0
  203. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_usage_statusline_grok.py +0 -0
  204. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_usage_terse_mode.py +0 -0
  205. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_usage_terse_reminder.py +0 -0
  206. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_web_panel_payload.py +0 -0
  207. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_wheel_packaging.py +0 -0
  208. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_win_login_item.py +0 -0
  209. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_window_keeper.py +0 -0
  210. {usage_cli-0.32.3 → usage_cli-0.32.5}/tests/test_windows_manifest.py +0 -0
  211. {usage_cli-0.32.3 → usage_cli-0.32.5}/tui/__init__.py +0 -0
  212. {usage_cli-0.32.3 → usage_cli-0.32.5}/tui/app.py +0 -0
  213. {usage_cli-0.32.3 → usage_cli-0.32.5}/tui/sprite.py +0 -0
  214. {usage_cli-0.32.3 → usage_cli-0.32.5}/ui/__init__.py +0 -0
  215. {usage_cli-0.32.3 → usage_cli-0.32.5}/ui/html_report.py +0 -0
  216. {usage_cli-0.32.3 → usage_cli-0.32.5}/ui/report_charts.py +0 -0
  217. {usage_cli-0.32.3 → usage_cli-0.32.5}/ui/report_daily_chart.py +0 -0
  218. {usage_cli-0.32.3 → usage_cli-0.32.5}/ui/report_filter.py +0 -0
  219. {usage_cli-0.32.3 → usage_cli-0.32.5}/ui/report_ledger.py +0 -0
  220. {usage_cli-0.32.3 → usage_cli-0.32.5}/ui/report_scripts.py +0 -0
  221. {usage_cli-0.32.3 → usage_cli-0.32.5}/ui/report_styles.py +0 -0
  222. {usage_cli-0.32.3 → usage_cli-0.32.5}/ui/tables.py +0 -0
  223. {usage_cli-0.32.3 → usage_cli-0.32.5}/updates/__init__.py +0 -0
  224. {usage_cli-0.32.3 → usage_cli-0.32.5}/updates/release_notes.py +0 -0
  225. {usage_cli-0.32.3 → usage_cli-0.32.5}/usage_cli.egg-info/dependency_links.txt +0 -0
  226. {usage_cli-0.32.3 → usage_cli-0.32.5}/usage_cli.egg-info/entry_points.txt +0 -0
  227. {usage_cli-0.32.3 → usage_cli-0.32.5}/usage_cli.egg-info/requires.txt +0 -0
  228. {usage_cli-0.32.3 → usage_cli-0.32.5}/usage_cli.egg-info/top_level.txt +0 -0
  229. {usage_cli-0.32.3 → usage_cli-0.32.5}/usage_client.py +0 -0
  230. {usage_cli-0.32.3 → usage_cli-0.32.5}/usage_common/__init__.py +0 -0
  231. {usage_cli-0.32.3 → usage_cli-0.32.5}/usage_common/subprocess_utils.py +0 -0
  232. {usage_cli-0.32.3 → usage_cli-0.32.5}/usage_common/time_utils.py +0 -0
  233. {usage_cli-0.32.3 → usage_cli-0.32.5}/usage_common/usage_dir_sweeper.py +0 -0
  234. {usage_cli-0.32.3 → usage_cli-0.32.5}/usage_common/usage_lang.py +0 -0
  235. {usage_cli-0.32.3 → usage_cli-0.32.5}/usage_common/usage_logging.py +0 -0
  236. {usage_cli-0.32.3 → usage_cli-0.32.5}/usage_diagnosis_snapshot.py +0 -0
  237. {usage_cli-0.32.3 → usage_cli-0.32.5}/usage_notifications.py +0 -0
  238. {usage_cli-0.32.3 → usage_cli-0.32.5}/usage_session_resume.py +0 -0
  239. {usage_cli-0.32.3 → usage_cli-0.32.5}/usage_statusline_agy.py +0 -0
  240. {usage_cli-0.32.3 → usage_cli-0.32.5}/usage_statusline_forwarder.py +0 -0
  241. {usage_cli-0.32.3 → usage_cli-0.32.5}/usage_statusline_grok.py +0 -0
  242. {usage_cli-0.32.3 → usage_cli-0.32.5}/usage_terse_mode.py +0 -0
  243. {usage_cli-0.32.3 → usage_cli-0.32.5}/usage_terse_reminder.py +0 -0
  244. {usage_cli-0.32.3 → usage_cli-0.32.5}/wintray/__init__.py +0 -0
  245. {usage_cli-0.32.3 → usage_cli-0.32.5}/wintray/login_item.py +0 -0
  246. {usage_cli-0.32.3 → usage_cli-0.32.5}/wintray/watch.py +0 -0
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: usage-cli
3
- Version: 0.32.3
3
+ Version: 0.32.5
4
4
  Summary: usage — 在 macOS menu bar 與 Windows 系統匣顯示 Claude Code、Codex、Antigravity 用量的小工具(也提供終端機 TUI)
5
5
  License-Expression: AGPL-3.0-only
6
6
  Requires-Python: >=3.13
7
7
  Description-Content-Type: text/markdown
8
8
  License-File: LICENSE
9
+ License-File: THIRD_PARTY_NOTICES.md
9
10
  Requires-Dist: pyobjc-framework-Cocoa>=11.0; sys_platform == "darwin"
10
11
  Requires-Dist: pyobjc-framework-Quartz>=11.0; sys_platform == "darwin"
11
12
  Requires-Dist: pyobjc-framework-UserNotifications>=11.0; sys_platform == "darwin"
@@ -69,7 +70,7 @@ It lands in your Applications folder automatically. Open it once; if macOS 15 or
69
70
  - **Muse Code Spending:** Muse Code's per-request tokens and cost count toward today's cost, project totals, the HTML report, and the `usage` CLI, read from its own local session logs. Muse keeps no local quota data, so there is no Muse quota card.
70
71
  - **Service Status Alerts:** An orange-red banner appears when Claude Code, Claude API, or Codex API has an outage or degraded performance, read from their public Statuspage.io pages. Antigravity isn't covered; it has no public status page.
71
72
  - **Context Nudges & Notifications:** When your context window hits 70% — or earlier when it is filling fast — the status line nudges you to `/clear` or `/compact` to prevent token waste. You can also opt-in to system notifications for quota limits and recoveries.
72
- - **Prompt Cache Health:** The status line shows Claude Code's prompt cache hit rate with a countdown to expiry, so you can tell at a glance whether finishing now still reuses the cached context or lets it go cold and re-sends everything. Once it has gone cold, the countdown turns into how many tokens your next message will re-cache. Needs Claude Code 2.1.251 or newer; on older versions the segment simply doesn't appear.
73
+ - **Prompt Cache Health:** The status line shows Claude Code's prompt cache hit rate. For 10 minutes after the cache misses, it also says why — the model changed, the tools changed, you sat idle past the 5-minute TTL, and so on — so you can tell whether the extra tokens came from something you did. The hit rate needs Claude Code 2.1.251 or newer and the reason needs 2.1.260 or newer; on older versions those parts simply don't appear.
73
74
  - **Hide Sections:** Only use one or two of the tools? Hide the Claude Code, Codex, Grok CLI, or Antigravity section from the menu bar and panels completely with a single click.
74
75
 
75
76
  ### Workflow Helpers
@@ -179,7 +180,9 @@ See your quota, other conversations, and background jobs without leaving Claude
179
180
 
180
181
  Windows has the full core experience: the system-tray UI, Claude Code status-line hook, and Codex history parsing all work natively. Download `usage-windows.zip` from the [latest GitHub Release](https://github.com/aqua5230/usage/releases/latest), unzip it, then run `usage.exe`—no installer is needed. If SmartScreen shows **Windows protected your PC** on first launch, click **More info** → **Run anyway**. The tray UI requires Microsoft Edge WebView2 Runtime, which is normally included with Windows 10 and 11.
181
182
 
182
- The system-tray icon updates with your Claude quota percentage; its tooltip summarizes the Claude and Codex windows. Left-click opens the same 16 quota themes available on macOS (Default plus the other fifteen) in WebView2. Right-click provides Reset Panel Position and Quit; panel switching, refresh, launch at login, and update checks are in the panel menu.
183
+ The system-tray icon shows the remaining session quota percentage for Claude or Codex. Choose **Tray Display Source → Claude Code / Codex** in the right-click menu or panel menu; the change applies immediately and survives restarts (default: Claude). If Codex has no session window, the icon uses its weekly quota instead and the tooltip identifies that window. Missing quota data shows `--`. The tooltip summarizes both tools, with the selected source first. Left-click opens the same 16 quota themes available on macOS (Default plus the other fifteen) in WebView2. Right-click also provides Reset Panel Position and Quit; panel switching, refresh, launch at login, and update checks are in the panel menu.
184
+
185
+ Enable **Show Taskbar Quota** in either menu for a transparent `Codex: 92%` label inside the taskbar, immediately left of the notification area. It follows taskbar position, scaling, and light/dark theme, and hides during fullscreen use or taskbar auto-hide. Click the label to open the panel. If buttons leave insufficient space, it moves just outside the taskbar. The normal app icon remains as a menu entry point; the label follows the selected source and quota window.
183
186
 
184
187
  Windows differences: the panel opens at the bottom-right of the working area rather than next to the tray icon; update prompts use a system Yes/No dialog.
185
188
 
@@ -49,7 +49,7 @@ It lands in your Applications folder automatically. Open it once; if macOS 15 or
49
49
  - **Muse Code Spending:** Muse Code's per-request tokens and cost count toward today's cost, project totals, the HTML report, and the `usage` CLI, read from its own local session logs. Muse keeps no local quota data, so there is no Muse quota card.
50
50
  - **Service Status Alerts:** An orange-red banner appears when Claude Code, Claude API, or Codex API has an outage or degraded performance, read from their public Statuspage.io pages. Antigravity isn't covered; it has no public status page.
51
51
  - **Context Nudges & Notifications:** When your context window hits 70% — or earlier when it is filling fast — the status line nudges you to `/clear` or `/compact` to prevent token waste. You can also opt-in to system notifications for quota limits and recoveries.
52
- - **Prompt Cache Health:** The status line shows Claude Code's prompt cache hit rate with a countdown to expiry, so you can tell at a glance whether finishing now still reuses the cached context or lets it go cold and re-sends everything. Once it has gone cold, the countdown turns into how many tokens your next message will re-cache. Needs Claude Code 2.1.251 or newer; on older versions the segment simply doesn't appear.
52
+ - **Prompt Cache Health:** The status line shows Claude Code's prompt cache hit rate. For 10 minutes after the cache misses, it also says why — the model changed, the tools changed, you sat idle past the 5-minute TTL, and so on — so you can tell whether the extra tokens came from something you did. The hit rate needs Claude Code 2.1.251 or newer and the reason needs 2.1.260 or newer; on older versions those parts simply don't appear.
53
53
  - **Hide Sections:** Only use one or two of the tools? Hide the Claude Code, Codex, Grok CLI, or Antigravity section from the menu bar and panels completely with a single click.
54
54
 
55
55
  ### Workflow Helpers
@@ -159,7 +159,9 @@ See your quota, other conversations, and background jobs without leaving Claude
159
159
 
160
160
  Windows has the full core experience: the system-tray UI, Claude Code status-line hook, and Codex history parsing all work natively. Download `usage-windows.zip` from the [latest GitHub Release](https://github.com/aqua5230/usage/releases/latest), unzip it, then run `usage.exe`—no installer is needed. If SmartScreen shows **Windows protected your PC** on first launch, click **More info** → **Run anyway**. The tray UI requires Microsoft Edge WebView2 Runtime, which is normally included with Windows 10 and 11.
161
161
 
162
- The system-tray icon updates with your Claude quota percentage; its tooltip summarizes the Claude and Codex windows. Left-click opens the same 16 quota themes available on macOS (Default plus the other fifteen) in WebView2. Right-click provides Reset Panel Position and Quit; panel switching, refresh, launch at login, and update checks are in the panel menu.
162
+ The system-tray icon shows the remaining session quota percentage for Claude or Codex. Choose **Tray Display Source → Claude Code / Codex** in the right-click menu or panel menu; the change applies immediately and survives restarts (default: Claude). If Codex has no session window, the icon uses its weekly quota instead and the tooltip identifies that window. Missing quota data shows `--`. The tooltip summarizes both tools, with the selected source first. Left-click opens the same 16 quota themes available on macOS (Default plus the other fifteen) in WebView2. Right-click also provides Reset Panel Position and Quit; panel switching, refresh, launch at login, and update checks are in the panel menu.
163
+
164
+ Enable **Show Taskbar Quota** in either menu for a transparent `Codex: 92%` label inside the taskbar, immediately left of the notification area. It follows taskbar position, scaling, and light/dark theme, and hides during fullscreen use or taskbar auto-hide. Click the label to open the panel. If buttons leave insufficient space, it moves just outside the taskbar. The normal app icon remains as a menu entry point; the label follows the selected source and quota window.
163
165
 
164
166
  Windows differences: the panel opens at the bottom-right of the working area rather than next to the tray icon; update prompts use a system Yes/No dialog.
165
167
 
@@ -0,0 +1,28 @@
1
+ # Third-party notices
2
+
3
+ The Windows taskbar label anchoring in `wintray/taskbar_overlay.py` is adapted
4
+ from [Codex Usage Widget](https://github.com/ognjeeen/codex-usage-widget),
5
+ commit `940d722d4044242bbe73f8056014449c2ef827a7`, particularly
6
+ `Infrastructure/Windows/TaskbarWindowInterop.cs`.
7
+
8
+ MIT License
9
+
10
+ Copyright (c) 2026 Ognjen Marinković
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
@@ -20,6 +20,7 @@ from typing import Final
20
20
 
21
21
  from i18n import packaged_resource_path
22
22
  from installer import setup_hook
23
+ from installer.codex_hook_trust import codex_terse_hook_trust
23
24
 
24
25
  SEPARATOR = "-" * 29
25
26
  RATE_LIMIT_FRESH_SECONDS: Final = 15 * 60
@@ -54,6 +55,7 @@ CHECK_LABELS: Final = {
54
55
  CODEX_RATE_LIMITS: "codex rate limits",
55
56
  CODEX_HISTORY: "codex history",
56
57
  CLAUDE_COST: "claude cost",
58
+ "codex_terse_hook_trust": "codex terse hook trust",
57
59
  }
58
60
 
59
61
 
@@ -87,6 +89,7 @@ def collect() -> DoctorReport:
87
89
  ("optional", _field(CODEX_LOGS, _codex_logs)),
88
90
  ("optional", _field(CODEX_RATE_LIMITS, _codex_rate_limits)),
89
91
  ("optional", _field(CLAUDE_COST, _claude_cost)),
92
+ ("optional", _field("codex_terse_hook_trust", _codex_terse_trust)),
90
93
  ]
91
94
  return DoctorReport(
92
95
  version=_text_field(_current_version),
@@ -514,3 +517,8 @@ def _ago(mtime: float) -> str:
514
517
  return f"{hours}h"
515
518
  days = hours // 24
516
519
  return f"{days}d"
520
+
521
+
522
+ def _codex_terse_trust() -> CheckResult:
523
+ trust = codex_terse_hook_trust()
524
+ return CheckResult("codex_terse_hook_trust", "ok" if trust == "trusted" else "warn", trust)
@@ -22,6 +22,10 @@
22
22
  "launch_at_login": "開機自動啟動",
23
23
  "reset_panel_position": "重設面板位置",
24
24
  "hide_sections_menu": "隱藏區塊",
25
+ "tray_provider_menu": "系統匣顯示來源",
26
+ "quota_label_menu": "顯示工作列額度",
27
+ "quota_label_format": "{provider}: {value}%",
28
+ "quota_label_unknown": "{provider}: --",
25
29
  "quota_notifications_menu": "用量提醒通知",
26
30
  "window_keeper_menu": "自動啟動 5 小時計時",
27
31
  "window_keeper_tooltip": "5 小時額度重置後,自動各送一則極小訊息(Claude 用 Haiku、agy 用 Gemini 3.5 Flash Low、Codex 用最省成本模型),讓下一輪 5 小時立刻開始計時(會消耗極少量額度)",
@@ -493,7 +497,21 @@
493
497
  "claude_pane_elapsed": "{minutes}分{seconds}秒",
494
498
  "claude_pane_remaining": "剩",
495
499
  "claude_pane_reset_done": "已重置",
496
- "claude_pane_hours_ago": "{count}時前"
500
+ "claude_pane_hours_ago": "{count}時前",
501
+ "update_check_failed_offline": "連不上 GitHub,請確認網路後再試。",
502
+ "update_check_failed_rate_limited": "GitHub 暫時拒絕請求,可能太頻繁了,稍後再試。",
503
+ "update_check_failed_server": "GitHub 回應錯誤,稍後再試。",
504
+ "update_check_failed_bad_response": "GitHub 回傳的資料看不懂,稍後再試。",
505
+ "agy_signin_expired": "登入過期",
506
+ "agy_signin_expired_tooltip": "Antigravity 登入已過期,usage 拿不到新額度。在終端機執行一次 agy 重新登入,成功後會自動恢復。",
507
+ "keeper_failed_title": "自動開窗失敗",
508
+ "keeper_failed_body": "{tool}:{reason}。下一輪 5 小時沒有開始計時。",
509
+ "keeper_reason_not_found": "找不到指令",
510
+ "keeper_reason_timeout": "逾時",
511
+ "keeper_failed_line": "上次失敗 {time}:{tool}({reason})",
512
+ "terse_codex_hook_untrusted": "Codex 還沒批准 usage 的精簡模式 hook,Codex 那邊不會生效。在 Codex 裡打 /hooks 批准它。",
513
+ "terse_codex_hook_disabled": "你在 Codex 停用了 usage 的精簡模式 hook。在 Codex 裡打 /hooks 重新啟用。",
514
+ "claude_pane_stopped": "已停止"
497
515
  },
498
516
  "en": {
499
517
  "panel_default_name": "Default",
@@ -518,6 +536,10 @@
518
536
  "launch_at_login": "Launch at Login",
519
537
  "reset_panel_position": "Reset panel position",
520
538
  "hide_sections_menu": "Hide Sections",
539
+ "tray_provider_menu": "Tray Display Source",
540
+ "quota_label_menu": "Show Taskbar Quota",
541
+ "quota_label_format": "{provider}: {value}%",
542
+ "quota_label_unknown": "{provider}: --",
521
543
  "quota_notifications_menu": "Usage Alert Notifications",
522
544
  "window_keeper_menu": "Auto-start 5-hour Session",
523
545
  "window_keeper_tooltip": "After your 5-hour quotas reset, automatically send one tiny message to each tool (Claude with Haiku, agy with Gemini 3.5 Flash Low, Codex with its cheapest model) so the next 5-hour sessions start counting right away (uses a tiny bit of quota).",
@@ -989,7 +1011,21 @@
989
1011
  "claude_pane_elapsed": "{minutes}m{seconds}s",
990
1012
  "claude_pane_remaining": "left",
991
1013
  "claude_pane_reset_done": "Reset",
992
- "claude_pane_hours_ago": "{count}h ago"
1014
+ "claude_pane_hours_ago": "{count}h ago",
1015
+ "update_check_failed_offline": "Cannot reach GitHub. Check your connection and try again.",
1016
+ "update_check_failed_rate_limited": "GitHub is limiting requests. Try again later.",
1017
+ "update_check_failed_server": "GitHub returned an error. Try again later.",
1018
+ "update_check_failed_bad_response": "Could not read GitHub’s response. Try again later.",
1019
+ "agy_signin_expired": "Sign-in expired",
1020
+ "agy_signin_expired_tooltip": "Your Antigravity sign-in expired, so usage cannot fetch fresh quota. Run agy in a terminal to sign in again. Quota will recover automatically.",
1021
+ "keeper_failed_title": "Auto-start failed",
1022
+ "keeper_failed_body": "{tool}: {reason}. The next 5-hour window has not started.",
1023
+ "keeper_reason_not_found": "Command not found",
1024
+ "keeper_reason_timeout": "Timed out",
1025
+ "keeper_failed_line": "Last failure {time}: {tool} ({reason})",
1026
+ "terse_codex_hook_untrusted": "Codex has not approved usage’s terse mode hook, so terse mode will not work in Codex. Run /hooks in Codex to approve it.",
1027
+ "terse_codex_hook_disabled": "You disabled usage’s terse mode hook in Codex. Run /hooks in Codex to enable it again.",
1028
+ "claude_pane_stopped": "stopped"
993
1029
  },
994
1030
  "zh-CN": {
995
1031
  "panel_default_name": "默认",
@@ -1014,6 +1050,10 @@
1014
1050
  "launch_at_login": "开机自动启动",
1015
1051
  "reset_panel_position": "重设面板位置",
1016
1052
  "hide_sections_menu": "隐藏区块",
1053
+ "tray_provider_menu": "托盘显示来源",
1054
+ "quota_label_menu": "显示任务栏额度",
1055
+ "quota_label_format": "{provider}: {value}%",
1056
+ "quota_label_unknown": "{provider}: --",
1017
1057
  "quota_notifications_menu": "用量提醒通知",
1018
1058
  "window_keeper_menu": "自动启动 5 小时计时",
1019
1059
  "window_keeper_tooltip": "5 小时额度重置后,自动各发送一则极小消息(Claude 用 Haiku、agy 用 Gemini 3.5 Flash Low、Codex 用最省成本模型),让下一轮 5 小时立刻开始计时(会消耗极少量额度)",
@@ -1485,7 +1525,21 @@
1485
1525
  "claude_pane_elapsed": "{minutes}分{seconds}秒",
1486
1526
  "claude_pane_remaining": "剩",
1487
1527
  "claude_pane_reset_done": "已重置",
1488
- "claude_pane_hours_ago": "{count}时前"
1528
+ "claude_pane_hours_ago": "{count}时前",
1529
+ "update_check_failed_offline": "连不上 GitHub,请确认网络后再试。",
1530
+ "update_check_failed_rate_limited": "GitHub 暂时拒绝请求,可能太频繁了,稍后再试。",
1531
+ "update_check_failed_server": "GitHub 响应错误,稍后再试。",
1532
+ "update_check_failed_bad_response": "GitHub 返回的数据看不懂,稍后再试。",
1533
+ "agy_signin_expired": "登录过期",
1534
+ "agy_signin_expired_tooltip": "Antigravity 登录已过期,usage 无法获取新额度。在终端运行一次 agy 重新登录,成功后会自动恢复。",
1535
+ "keeper_failed_title": "自动开窗失败",
1536
+ "keeper_failed_body": "{tool}:{reason}。下一轮 5 小时没有开始计时。",
1537
+ "keeper_reason_not_found": "找不到命令",
1538
+ "keeper_reason_timeout": "超时",
1539
+ "keeper_failed_line": "上次失败 {time}:{tool}({reason})",
1540
+ "terse_codex_hook_untrusted": "Codex 还没批准 usage 的精简模式 hook,Codex 那边不会生效。在 Codex 里输入 /hooks 批准它。",
1541
+ "terse_codex_hook_disabled": "你在 Codex 停用了 usage 的精简模式 hook。在 Codex 里输入 /hooks 重新启用。",
1542
+ "claude_pane_stopped": "已停止"
1489
1543
  },
1490
1544
  "ja": {
1491
1545
  "panel_default_name": "デフォルト",
@@ -1510,6 +1564,10 @@
1510
1564
  "launch_at_login": "ログイン時に起動",
1511
1565
  "reset_panel_position": "パネルの位置をリセット",
1512
1566
  "hide_sections_menu": "セクションを非表示",
1567
+ "tray_provider_menu": "トレイの表示元",
1568
+ "quota_label_menu": "タスクバーにクォータを表示",
1569
+ "quota_label_format": "{provider}: {value}%",
1570
+ "quota_label_unknown": "{provider}: --",
1513
1571
  "quota_notifications_menu": "使用量アラート通知",
1514
1572
  "window_keeper_menu": "5 時間セッションを自動開始",
1515
1573
  "window_keeper_tooltip": "5 時間枠のリセット後、各ツールにごく小さなメッセージを自動送信します(Claude は Haiku、agy は Gemini 3.5 Flash Low、Codex は最も低コストなモデル)。次の 5 時間のカウントをすぐに開始します(ごくわずかな枠を消費します)。",
@@ -1981,7 +2039,21 @@
1981
2039
  "claude_pane_elapsed": "{minutes}分{seconds}秒",
1982
2040
  "claude_pane_remaining": "残り",
1983
2041
  "claude_pane_reset_done": "リセット済み",
1984
- "claude_pane_hours_ago": "{count}時間前"
2042
+ "claude_pane_hours_ago": "{count}時間前",
2043
+ "update_check_failed_offline": "GitHub に接続できません。ネット接続を確認して再試行してください。",
2044
+ "update_check_failed_rate_limited": "GitHub がリクエストを制限しています。後でもう一度お試しください。",
2045
+ "update_check_failed_server": "GitHub でエラーが発生しました。後でもう一度お試しください。",
2046
+ "update_check_failed_bad_response": "GitHub の応答を読み取れませんでした。後でもう一度お試しください。",
2047
+ "agy_signin_expired": "ログイン期限切れ",
2048
+ "agy_signin_expired_tooltip": "Antigravity のログイン期限が切れたため、usage は新しい利用枠を取得できません。ターミナルで agy を実行して再ログインすると、自動で復旧します。",
2049
+ "keeper_failed_title": "自動開始に失敗",
2050
+ "keeper_failed_body": "{tool}:{reason}。次の 5 時間枠は開始されていません。",
2051
+ "keeper_reason_not_found": "コマンドが見つかりません",
2052
+ "keeper_reason_timeout": "タイムアウト",
2053
+ "keeper_failed_line": "前回の失敗 {time}:{tool}({reason})",
2054
+ "terse_codex_hook_untrusted": "Codex が usage の簡潔モード hook をまだ承認していないため、Codex では機能しません。Codex で /hooks を入力して承認してください。",
2055
+ "terse_codex_hook_disabled": "Codex で usage の簡潔モード hook が無効になっています。Codex で /hooks を入力して再び有効にしてください。",
2056
+ "claude_pane_stopped": "停止済み"
1985
2057
  },
1986
2058
  "ko": {
1987
2059
  "panel_default_name": "기본",
@@ -2006,6 +2078,10 @@
2006
2078
  "launch_at_login": "로그인 시 시작",
2007
2079
  "reset_panel_position": "패널 위치 재설정",
2008
2080
  "hide_sections_menu": "섹션 숨기기",
2081
+ "tray_provider_menu": "트레이 표시 소스",
2082
+ "quota_label_menu": "작업 표시줄 할당량 표시",
2083
+ "quota_label_format": "{provider}: {value}%",
2084
+ "quota_label_unknown": "{provider}: --",
2009
2085
  "quota_notifications_menu": "사용량 알림",
2010
2086
  "window_keeper_menu": "5시간 세션 자동 시작",
2011
2087
  "window_keeper_tooltip": "5시간 할당량이 리셋되면 각 도구에 아주 작은 메시지를 자동 전송합니다 (Claude는 Haiku, agy는 Gemini 3.5 Flash Low, Codex는 가장 저렴한 모델). 다음 5시간 카운트를 바로 시작합니다 (극소량의 할당량을 소모합니다).",
@@ -2477,6 +2553,20 @@
2477
2553
  "claude_pane_elapsed": "{minutes}분{seconds}초",
2478
2554
  "claude_pane_remaining": "남음",
2479
2555
  "claude_pane_reset_done": "리셋됨",
2480
- "claude_pane_hours_ago": "{count}시간 전"
2556
+ "claude_pane_hours_ago": "{count}시간 전",
2557
+ "update_check_failed_offline": "GitHub에 연결할 수 없어요. 인터넷 연결을 확인하고 다시 시도하세요.",
2558
+ "update_check_failed_rate_limited": "GitHub가 요청을 제한하고 있어요. 잠시 후 다시 시도하세요.",
2559
+ "update_check_failed_server": "GitHub에서 오류가 발생했어요. 잠시 후 다시 시도하세요.",
2560
+ "update_check_failed_bad_response": "GitHub 응답을 읽을 수 없어요. 잠시 후 다시 시도하세요.",
2561
+ "agy_signin_expired": "로그인 만료",
2562
+ "agy_signin_expired_tooltip": "Antigravity 로그인이 만료되어 usage가 새 한도를 가져올 수 없어요. 터미널에서 agy를 실행해 다시 로그인하면 자동으로 복구돼요.",
2563
+ "keeper_failed_title": "자동 시작 실패",
2564
+ "keeper_failed_body": "{tool}: {reason}. 다음 5시간 구간이 시작되지 않았습니다.",
2565
+ "keeper_reason_not_found": "명령을 찾을 수 없음",
2566
+ "keeper_reason_timeout": "시간 초과",
2567
+ "keeper_failed_line": "마지막 실패 {time}: {tool} ({reason})",
2568
+ "terse_codex_hook_untrusted": "Codex가 usage의 간결 모드 hook을 아직 승인하지 않아 Codex에서 작동하지 않습니다. Codex에서 /hooks를 입력해 승인하세요.",
2569
+ "terse_codex_hook_disabled": "Codex에서 usage의 간결 모드 hook을 비활성화했습니다. Codex에서 /hooks를 입력해 다시 활성화하세요.",
2570
+ "claude_pane_stopped": "중지됨"
2481
2571
  }
2482
2572
  }
@@ -0,0 +1,66 @@
1
+ """Read Codex's approval record without granting hook trust."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import tomllib
8
+
9
+ from installer import session_hooks
10
+ from loaders.codex_paths import codex_home
11
+
12
+
13
+ def codex_terse_hook_trust() -> str:
14
+ """Return the usage SessionStart hook's trust state.
15
+
16
+ A trusted_hash alone cannot detect Modified (a hook edited after approval).
17
+ Codex remains responsible for verifying the hash and asking for approval.
18
+ """
19
+ path = session_hooks.CODEX_HOOKS_JSON
20
+ try:
21
+ data = json.loads(path.read_text(encoding="utf-8"))
22
+ if not isinstance(data, dict):
23
+ return "unknown"
24
+ groups = session_hooks._codex_session_start_list(data)
25
+ if groups is None:
26
+ return "not_installed"
27
+ index = None
28
+ for group_index, group in enumerate(groups):
29
+ handlers = group.get("hooks") if isinstance(group, dict) else None
30
+ if not isinstance(handlers, list):
31
+ continue
32
+ for handler_index, handler in enumerate(handlers):
33
+ command = handler.get("command") if isinstance(handler, dict) else None
34
+ if isinstance(command, str) and session_hooks._TERSE_MARKER in command:
35
+ index = (group_index, handler_index)
36
+ break
37
+ if index is not None:
38
+ break
39
+ if index is None:
40
+ return "not_installed"
41
+ with (codex_home() / "config.toml").open("rb") as handle:
42
+ config = tomllib.load(handle)
43
+ hooks = config.get("hooks", {})
44
+ states = hooks.get("state", {}) if isinstance(hooks, dict) else {}
45
+ if not isinstance(states, dict):
46
+ return "unknown"
47
+ suffix = f":session_start:{index[0]}:{index[1]}"
48
+ entry = states.get(f"{path}{suffix}")
49
+ if entry is None:
50
+ entry = next(
51
+ (
52
+ value
53
+ for key, value in states.items()
54
+ if key.endswith(suffix)
55
+ and os.path.realpath(key[: -len(suffix)]) == os.path.realpath(path)
56
+ ),
57
+ {},
58
+ )
59
+ if not isinstance(entry, dict):
60
+ return "untrusted"
61
+ if entry.get("enabled") is False:
62
+ return "disabled"
63
+ trusted_hash = entry.get("trusted_hash")
64
+ return "trusted" if isinstance(trusted_hash, str) and trusted_hash else "untrusted"
65
+ except (OSError, UnicodeError, ValueError):
66
+ return "unknown"
@@ -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.8"
89
+ HOOK_VERSION = "1.9"
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.
@@ -37,6 +37,19 @@ from urllib.request import Request, urlopen
37
37
  from usage_common.subprocess_utils import hidden_console_kwargs
38
38
 
39
39
  CACHE_PATH = Path(os.path.expanduser("~/.usage/agy_quota_cache.json"))
40
+ AUTH_EXPIRED_PATH = CACHE_PATH.parent / "agy_auth_expired"
41
+
42
+
43
+ def auth_expired() -> bool:
44
+ return AUTH_EXPIRED_PATH.exists()
45
+
46
+
47
+ def _mark_auth_expired() -> None:
48
+ with suppress(OSError):
49
+ AUTH_EXPIRED_PATH.parent.mkdir(parents=True, exist_ok=True)
50
+ AUTH_EXPIRED_PATH.touch()
51
+
52
+
40
53
  # Legacy OAuth token written by older Antigravity CLI versions. Read-only: we
41
54
  # never write back here (that is the CLI's home and could corrupt its login).
42
55
  _TOKEN_PATH = Path(os.path.expanduser("~/.gemini/antigravity-cli/antigravity-oauth-token"))
@@ -117,7 +130,11 @@ def probe_quota(timeout_seconds: float = 15) -> AgyQuotaResult | None:
117
130
  groups_raw = _extract_groups(raw)
118
131
  if not groups_raw:
119
132
  return None
120
- return _build_result(groups_raw)
133
+ result = _build_result(groups_raw)
134
+ if result is not None:
135
+ with suppress(OSError):
136
+ AUTH_EXPIRED_PATH.unlink(missing_ok=True)
137
+ return result
121
138
 
122
139
 
123
140
  def find_agy() -> str | None:
@@ -353,6 +370,20 @@ def _refresh_token(refresh_token: str, timeout: float) -> tuple[str, int] | None
353
370
  payload = json.loads(raw)
354
371
  except HTTPError as exc:
355
372
  _handle_http_error(exc)
373
+ if exc.code == 401:
374
+ _mark_auth_expired()
375
+ elif exc.code == 400:
376
+ try:
377
+ raw_error = exc.read(_MAX_RESPONSE_BYTES + 1)
378
+ if len(raw_error) <= _MAX_RESPONSE_BYTES:
379
+ error_payload = json.loads(raw_error)
380
+ if (
381
+ isinstance(error_payload, dict)
382
+ and error_payload.get("error") == "invalid_grant"
383
+ ):
384
+ _mark_auth_expired()
385
+ except (OSError, ValueError):
386
+ pass
356
387
  return None
357
388
  except (URLError, OSError, ValueError):
358
389
  return None
@@ -387,6 +418,8 @@ def _post_json(url: str, access_token: str, body: dict[str, object], timeout: fl
387
418
  return json.loads(raw)
388
419
  except HTTPError as exc:
389
420
  _handle_http_error(exc)
421
+ if exc.code == 401:
422
+ _mark_auth_expired()
390
423
  return None
391
424
  except (URLError, OSError, ValueError):
392
425
  return None
@@ -18,6 +18,7 @@ from loaders.agy_quota_probe import (
18
18
  AgyQuotaGroup,
19
19
  AgyQuotaResult,
20
20
  AgyQuotaWindow,
21
+ auth_expired,
21
22
  load_quota,
22
23
  )
23
24
  from loaders.agy_quota_probe import (
@@ -294,6 +295,11 @@ def _window_row(
294
295
 
295
296
 
296
297
  def _stale_state(fetched_at: str, now: float, language: str) -> AgyStaleState | None:
298
+ if auth_expired():
299
+ return {
300
+ "ageText": _t(language, "agy_signin_expired"),
301
+ "tooltip": _t(language, "agy_signin_expired_tooltip"),
302
+ }
297
303
  try:
298
304
  age_seconds = now - parse_iso8601_utc_or_raise(fetched_at).timestamp()
299
305
  except (TypeError, ValueError):
@@ -85,7 +85,6 @@ from menubar.chrome import (
85
85
  )
86
86
  from menubar.popover import PopoverViewController, _popover_size
87
87
  from menubar.prefs import (
88
- _auto_update_check_enabled,
89
88
  _hide_agy_enabled,
90
89
  _hide_claude_enabled,
91
90
  _hide_codex_enabled,
@@ -181,7 +180,6 @@ __all__ = [
181
180
  "_missing_row",
182
181
  "_quota_row",
183
182
  "format_human_time",
184
- "_auto_update_check_enabled",
185
183
  "_hide_agy_enabled",
186
184
  "_hide_claude_enabled",
187
185
  "_hide_codex_enabled",
@@ -306,6 +304,7 @@ class AppDelegate(NSObject):
306
304
  "agy_session": BurnRateTracker(),
307
305
  "agy_weekly": BurnRateTracker(),
308
306
  }
307
+ self._auto_check_schedule = update_gate.AutoCheckSchedule()
309
308
  self._quota_notifier = QuotaNotifier(_quota_notification_thresholds())
310
309
  self._refresh_in_flight = False
311
310
  self._refresh_queued = False
@@ -383,7 +382,7 @@ class AppDelegate(NSObject):
383
382
 
384
383
  def timerFired_(self, timer: Any) -> None:
385
384
  self._refresh()
386
- self._clear_stale_update_cache()
385
+ menubar_update.on_poll_tick(self)
387
386
 
388
387
  def _reschedule_poll_timer(self, interval: float) -> None:
389
388
  if self.timer is not None and self.timer_interval == interval:
@@ -611,19 +610,8 @@ class AppDelegate(NSObject):
611
610
  def _maybe_check_update_in_background(self) -> None:
612
611
  menubar_update.maybe_check_update_in_background(self)
613
612
 
614
- def _check_update_in_background(
615
- self,
616
- *,
617
- manual: bool,
618
- ignore_cooldown: bool,
619
- ignore_skipped: bool,
620
- ) -> None:
621
- menubar_update.check_update_in_background(
622
- self,
623
- manual=manual,
624
- ignore_cooldown=ignore_cooldown,
625
- ignore_skipped=ignore_skipped,
626
- )
613
+ def _check_update_in_background(self) -> bool:
614
+ return menubar_update.check_update_in_background(self)
627
615
 
628
616
  def _showUpdateAlert_(self, release: update_checker.ReleaseInfo) -> None:
629
617
  alert = _make_alert()
@@ -644,16 +632,6 @@ class AppDelegate(NSObject):
644
632
  prefs["update_dismissed_at"] = time.time()
645
633
  _save_preferences(prefs)
646
634
 
647
- def _showNoUpdateAvailable_(self, result: Any) -> None:
648
- alert = _make_alert()
649
- alert.setMessageText_(_t(self.language, "update_no_new_version"))
650
- alert.runModal()
651
-
652
- def _showUpdateCheckFailed_(self, result: Any) -> None:
653
- alert = _make_alert()
654
- alert.setMessageText_(_t(self.language, "update_check_failed"))
655
- alert.runModal()
656
-
657
635
  def _set_active_panel_id(self, panel_id: str) -> None:
658
636
  panel = panels.get_panel(panel_id)
659
637
  save_active_panel_id(panel.id)
@@ -905,16 +883,7 @@ class AppDelegate(NSObject):
905
883
  def _request_notification_authorization(self) -> None:
906
884
  if self.mock or not _quota_notifications_enabled():
907
885
  return
908
- try:
909
- center, constants = menubar_notify.user_notification_center()
910
- options = constants["badge"] | constants["sound"] | constants["alert"]
911
- center.requestAuthorizationWithOptions_completionHandler_(
912
- options,
913
- lambda granted, error: None,
914
- )
915
- except Exception:
916
- if os.environ.get("USAGE_DEBUG") == "1":
917
- logger.warning("notification authorization failed", exc_info=True)
886
+ menubar_notify.request_notification_authorization()
918
887
 
919
888
  def _process_quota_notifications(self, state: PopoverState) -> None:
920
889
  try:
@@ -51,6 +51,20 @@ def build_menu_item(
51
51
  item.setState_(1 if state else 0)
52
52
  if tooltip_key is not None:
53
53
  item.setToolTip_(_t(language, tooltip_key))
54
+ if title_key == "window_keeper_menu" and state:
55
+ from quota.keeper_outcome import failure_tooltip
56
+
57
+ failures = failure_tooltip(language)
58
+ if failures:
59
+ item.setTitle_(_t(language, title_key) + " ⚠")
60
+ item.setToolTip_(failures + "\n" + _t(language, "window_keeper_tooltip"))
61
+ if title_key == "terse_mode_menu" and state:
62
+ from installer.codex_hook_trust import codex_terse_hook_trust
63
+
64
+ trust = codex_terse_hook_trust()
65
+ if trust in ("untrusted", "disabled"):
66
+ item.setTitle_(_t(language, title_key) + " ⚠")
67
+ item.setToolTip_(_t(language, f"terse_codex_hook_{trust}"))
54
68
  return item
55
69
 
56
70