usage-cli 0.32.2__tar.gz → 0.32.4__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 (245) hide show
  1. {usage_cli-0.32.2 → usage_cli-0.32.4}/PKG-INFO +5 -2
  2. {usage_cli-0.32.2 → usage_cli-0.32.4}/README.md +3 -1
  3. usage_cli-0.32.4/THIRD_PARTY_NOTICES.md +28 -0
  4. {usage_cli-0.32.2 → usage_cli-0.32.4}/doctor.py +8 -0
  5. {usage_cli-0.32.2 → usage_cli-0.32.4}/i18n.json +110 -5
  6. usage_cli-0.32.4/installer/codex_hook_trust.py +66 -0
  7. {usage_cli-0.32.2 → usage_cli-0.32.4}/loaders/agy_quota_probe.py +34 -1
  8. {usage_cli-0.32.2 → usage_cli-0.32.4}/menubar/agy.py +22 -7
  9. {usage_cli-0.32.2 → usage_cli-0.32.4}/menubar/app.py +10 -41
  10. {usage_cli-0.32.2 → usage_cli-0.32.4}/menubar/grok.py +8 -17
  11. usage_cli-0.32.4/menubar/manual_refresh.py +51 -0
  12. {usage_cli-0.32.2 → usage_cli-0.32.4}/menubar/menu.py +14 -0
  13. usage_cli-0.32.4/menubar/notify.py +219 -0
  14. {usage_cli-0.32.2 → usage_cli-0.32.4}/menubar/prefs.py +0 -5
  15. {usage_cli-0.32.2 → usage_cli-0.32.4}/menubar/refresh.py +7 -0
  16. {usage_cli-0.32.2 → usage_cli-0.32.4}/menubar/state.py +20 -4
  17. {usage_cli-0.32.2 → usage_cli-0.32.4}/menubar/title.py +19 -13
  18. {usage_cli-0.32.2 → usage_cli-0.32.4}/menubar/update.py +33 -50
  19. {usage_cli-0.32.2 → usage_cli-0.32.4}/panels/payload.py +1 -1
  20. {usage_cli-0.32.2 → usage_cli-0.32.4}/pyproject.toml +2 -2
  21. {usage_cli-0.32.2 → usage_cli-0.32.4}/quota/agy_window_keeper.py +37 -54
  22. {usage_cli-0.32.2 → usage_cli-0.32.4}/quota/codex_window_keeper.py +38 -64
  23. usage_cli-0.32.4/quota/keeper_outcome.py +195 -0
  24. {usage_cli-0.32.2 → usage_cli-0.32.4}/quota/window_keeper.py +34 -62
  25. {usage_cli-0.32.2 → usage_cli-0.32.4}/setup_app.py +1 -0
  26. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_agy_quota_probe.py +88 -0
  27. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_agy_window_keeper.py +1 -1
  28. usage_cli-0.32.4/tests/test_codex_hook_trust.py +89 -0
  29. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_doctor.py +2 -1
  30. usage_cli-0.32.4/tests/test_keeper_menu.py +83 -0
  31. usage_cli-0.32.4/tests/test_keeper_outcome.py +483 -0
  32. usage_cli-0.32.4/tests/test_manual_refresh.py +62 -0
  33. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_menubar.py +92 -79
  34. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_menubar_agy.py +31 -2
  35. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_menubar_grok.py +14 -0
  36. usage_cli-0.32.4/tests/test_menubar_notify.py +91 -0
  37. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_menubar_prefs.py +0 -5
  38. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_menubar_state.py +50 -0
  39. usage_cli-0.32.4/tests/test_menubar_update.py +35 -0
  40. usage_cli-0.32.4/tests/test_quota_label.py +425 -0
  41. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_update_checker.py +30 -0
  42. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_update_gate.py +21 -0
  43. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_usage_cli.py +13 -0
  44. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_web_panel_payload.py +10 -0
  45. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_wintray.py +363 -10
  46. {usage_cli-0.32.2 → usage_cli-0.32.4}/updates/checker.py +9 -9
  47. {usage_cli-0.32.2 → usage_cli-0.32.4}/updates/gate.py +28 -0
  48. {usage_cli-0.32.2 → usage_cli-0.32.4}/usage_cli.egg-info/PKG-INFO +5 -2
  49. {usage_cli-0.32.2 → usage_cli-0.32.4}/usage_cli.egg-info/SOURCES.txt +14 -0
  50. {usage_cli-0.32.2 → usage_cli-0.32.4}/usage_cli.py +2 -0
  51. {usage_cli-0.32.2 → usage_cli-0.32.4}/wintray/app.py +196 -33
  52. {usage_cli-0.32.2 → usage_cli-0.32.4}/wintray/menu.py +19 -0
  53. usage_cli-0.32.4/wintray/prefs.py +25 -0
  54. usage_cli-0.32.4/wintray/quota_label.py +184 -0
  55. usage_cli-0.32.4/wintray/taskbar_obstacles.py +71 -0
  56. usage_cli-0.32.4/wintray/taskbar_overlay.py +296 -0
  57. usage_cli-0.32.2/menubar/notify.py +0 -119
  58. usage_cli-0.32.2/tests/test_menubar_notify.py +0 -37
  59. {usage_cli-0.32.2 → usage_cli-0.32.4}/LICENSE +0 -0
  60. {usage_cli-0.32.2 → usage_cli-0.32.4}/adapters/__init__.py +0 -0
  61. {usage_cli-0.32.2 → usage_cli-0.32.4}/adapters/agy.py +0 -0
  62. {usage_cli-0.32.2 → usage_cli-0.32.4}/adapters/claude.py +0 -0
  63. {usage_cli-0.32.2 → usage_cli-0.32.4}/adapters/codex.py +0 -0
  64. {usage_cli-0.32.2 → usage_cli-0.32.4}/adapters/grok.py +0 -0
  65. {usage_cli-0.32.2 → usage_cli-0.32.4}/adapters/muse.py +0 -0
  66. {usage_cli-0.32.2 → usage_cli-0.32.4}/adapters/rate_limits.py +0 -0
  67. {usage_cli-0.32.2 → usage_cli-0.32.4}/adapters/registry.py +0 -0
  68. {usage_cli-0.32.2 → usage_cli-0.32.4}/adapters/types.py +0 -0
  69. {usage_cli-0.32.2 → usage_cli-0.32.4}/analyzer/__init__.py +0 -0
  70. {usage_cli-0.32.2 → usage_cli-0.32.4}/analyzer/aggregator.py +0 -0
  71. {usage_cli-0.32.2 → usage_cli-0.32.4}/analyzer/blocks.py +0 -0
  72. {usage_cli-0.32.2 → usage_cli-0.32.4}/analyzer/diagnoser.py +0 -0
  73. {usage_cli-0.32.2 → usage_cli-0.32.4}/analyzer/dispatch_ledger.py +0 -0
  74. {usage_cli-0.32.2 → usage_cli-0.32.4}/analyzer/insights.py +0 -0
  75. {usage_cli-0.32.2 → usage_cli-0.32.4}/analyzer/persona_loader.py +0 -0
  76. {usage_cli-0.32.2 → usage_cli-0.32.4}/analyzer/reporter.py +0 -0
  77. {usage_cli-0.32.2 → usage_cli-0.32.4}/analyzer/subscription.py +0 -0
  78. {usage_cli-0.32.2 → usage_cli-0.32.4}/analyzer/usage_snapshot.py +0 -0
  79. {usage_cli-0.32.2 → usage_cli-0.32.4}/fsevents_watch.py +0 -0
  80. {usage_cli-0.32.2 → usage_cli-0.32.4}/i18n.py +0 -0
  81. {usage_cli-0.32.2 → usage_cli-0.32.4}/installer/__init__.py +0 -0
  82. {usage_cli-0.32.2 → usage_cli-0.32.4}/installer/claude_pane.py +0 -0
  83. {usage_cli-0.32.2 → usage_cli-0.32.4}/installer/login_item.py +0 -0
  84. {usage_cli-0.32.2 → usage_cli-0.32.4}/installer/session_hooks.py +0 -0
  85. {usage_cli-0.32.2 → usage_cli-0.32.4}/installer/setup_hook.py +0 -0
  86. {usage_cli-0.32.2 → usage_cli-0.32.4}/installer/statusline_settings.py +0 -0
  87. {usage_cli-0.32.2 → usage_cli-0.32.4}/loaders/__init__.py +0 -0
  88. {usage_cli-0.32.2 → usage_cli-0.32.4}/loaders/agy_disk_cache.py +0 -0
  89. {usage_cli-0.32.2 → usage_cli-0.32.4}/loaders/agy_loader.py +0 -0
  90. {usage_cli-0.32.2 → usage_cli-0.32.4}/loaders/cache_quarantine.py +0 -0
  91. {usage_cli-0.32.2 → usage_cli-0.32.4}/loaders/claude_paths.py +0 -0
  92. {usage_cli-0.32.2 → usage_cli-0.32.4}/loaders/codex_disk_cache.py +0 -0
  93. {usage_cli-0.32.2 → usage_cli-0.32.4}/loaders/codex_events.py +0 -0
  94. {usage_cli-0.32.2 → usage_cli-0.32.4}/loaders/codex_fork_replay.py +0 -0
  95. {usage_cli-0.32.2 → usage_cli-0.32.4}/loaders/codex_loader.py +0 -0
  96. {usage_cli-0.32.2 → usage_cli-0.32.4}/loaders/codex_paths.py +0 -0
  97. {usage_cli-0.32.2 → usage_cli-0.32.4}/loaders/disk_cache_common.py +0 -0
  98. {usage_cli-0.32.2 → usage_cli-0.32.4}/loaders/disk_cache_lifecycle.py +0 -0
  99. {usage_cli-0.32.2 → usage_cli-0.32.4}/loaders/grok_loader.py +0 -0
  100. {usage_cli-0.32.2 → usage_cli-0.32.4}/loaders/grok_quota_probe.py +0 -0
  101. {usage_cli-0.32.2 → usage_cli-0.32.4}/loaders/history_disk_cache.py +0 -0
  102. {usage_cli-0.32.2 → usage_cli-0.32.4}/loaders/history_loader.py +0 -0
  103. {usage_cli-0.32.2 → usage_cli-0.32.4}/loaders/jsonl_limits.py +0 -0
  104. {usage_cli-0.32.2 → usage_cli-0.32.4}/loaders/jsonl_utils.py +0 -0
  105. {usage_cli-0.32.2 → usage_cli-0.32.4}/loaders/muse_loader.py +0 -0
  106. {usage_cli-0.32.2 → usage_cli-0.32.4}/main.py +0 -0
  107. {usage_cli-0.32.2 → usage_cli-0.32.4}/menubar/__init__.py +0 -0
  108. {usage_cli-0.32.2 → usage_cli-0.32.4}/menubar/actions.py +0 -0
  109. {usage_cli-0.32.2 → usage_cli-0.32.4}/menubar/chrome.py +0 -0
  110. {usage_cli-0.32.2 → usage_cli-0.32.4}/menubar/new_badge.py +0 -0
  111. {usage_cli-0.32.2 → usage_cli-0.32.4}/menubar/popover.py +0 -0
  112. {usage_cli-0.32.2 → usage_cli-0.32.4}/panels/__init__.py +0 -0
  113. {usage_cli-0.32.2 → usage_cli-0.32.4}/panels/base.py +0 -0
  114. {usage_cli-0.32.2 → usage_cli-0.32.4}/panels/dynamic_height.py +0 -0
  115. {usage_cli-0.32.2 → usage_cli-0.32.4}/panels/panel_scale.py +0 -0
  116. {usage_cli-0.32.2 → usage_cli-0.32.4}/panels/panel_window.py +0 -0
  117. {usage_cli-0.32.2 → usage_cli-0.32.4}/panels/panel_window_state.py +0 -0
  118. {usage_cli-0.32.2 → usage_cli-0.32.4}/panels/web_panel.py +0 -0
  119. {usage_cli-0.32.2 → usage_cli-0.32.4}/panels/window_drag.py +0 -0
  120. {usage_cli-0.32.2 → usage_cli-0.32.4}/prefs.py +0 -0
  121. {usage_cli-0.32.2 → usage_cli-0.32.4}/pricing.py +0 -0
  122. {usage_cli-0.32.2 → usage_cli-0.32.4}/project_resolver.py +0 -0
  123. {usage_cli-0.32.2 → usage_cli-0.32.4}/quota/__init__.py +0 -0
  124. {usage_cli-0.32.2 → usage_cli-0.32.4}/quota/burn_rate.py +0 -0
  125. {usage_cli-0.32.2 → usage_cli-0.32.4}/quota/usage_rate.py +0 -0
  126. {usage_cli-0.32.2 → usage_cli-0.32.4}/service_status.py +0 -0
  127. {usage_cli-0.32.2 → usage_cli-0.32.4}/setup.cfg +0 -0
  128. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_adapters_claude.py +0 -0
  129. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_adapters_codex.py +0 -0
  130. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_adapters_rate_limits.py +0 -0
  131. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_aggregator.py +0 -0
  132. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_agy_adapter.py +0 -0
  133. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_agy_disk_cache.py +0 -0
  134. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_agy_loader.py +0 -0
  135. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_analyzer_blocks.py +0 -0
  136. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_analyzer_pipeline.py +0 -0
  137. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_burn_rate.py +0 -0
  138. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_cache_quarantine.py +0 -0
  139. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_claude_pane.py +0 -0
  140. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_claude_paths.py +0 -0
  141. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_codex_consistency.py +0 -0
  142. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_codex_loader.py +0 -0
  143. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_codex_window_keeper.py +0 -0
  144. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_diagnoser.py +0 -0
  145. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_disk_cache_shards.py +0 -0
  146. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_dispatch_ledger.py +0 -0
  147. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_dynamic_height.py +0 -0
  148. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_fsevents_watch.py +0 -0
  149. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_grok_loader.py +0 -0
  150. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_grok_quota_probe.py +0 -0
  151. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_history_loader.py +0 -0
  152. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_hook_scripts_are_standalone.py +0 -0
  153. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_html_report_snapshot.py +0 -0
  154. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_i18n_format_fallback.py +0 -0
  155. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_i18n_key_parity.py +0 -0
  156. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_i18n_packaged_path.py +0 -0
  157. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_insights.py +0 -0
  158. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_jsonl_utils.py +0 -0
  159. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_login_item.py +0 -0
  160. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_main.py +0 -0
  161. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_muse_loader.py +0 -0
  162. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_new_badge.py +0 -0
  163. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_packaged_resources.py +0 -0
  164. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_packaging.py +0 -0
  165. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_panel_footer_layout.py +0 -0
  166. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_panel_scale.py +0 -0
  167. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_panel_window_state.py +0 -0
  168. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_panels.py +0 -0
  169. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_panels_lazy_import.py +0 -0
  170. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_persona_loader.py +0 -0
  171. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_pricing.py +0 -0
  172. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_project_resolver.py +0 -0
  173. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_report_daily_chart.py +0 -0
  174. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_report_filter_js.py +0 -0
  175. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_reporter_build_data.py +0 -0
  176. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_service_status.py +0 -0
  177. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_session_resume.py +0 -0
  178. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_setup_app.py +0 -0
  179. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_setup_hook.py +0 -0
  180. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_setup_hook_coexistence.py +0 -0
  181. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_setup_hook_resume.py +0 -0
  182. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_setup_hook_terse.py +0 -0
  183. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_subprocess_utils.py +0 -0
  184. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_subscription.py +0 -0
  185. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_time_utils.py +0 -0
  186. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_tui.py +0 -0
  187. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_tui_sprite.py +0 -0
  188. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_ui_tables.py +0 -0
  189. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_update_release_notes.py +0 -0
  190. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_usage_client.py +0 -0
  191. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_usage_diagnosis_snapshot.py +0 -0
  192. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_usage_dir_sweeper.py +0 -0
  193. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_usage_lang.py +0 -0
  194. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_usage_logging.py +0 -0
  195. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_usage_notifications.py +0 -0
  196. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_usage_rate.py +0 -0
  197. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_usage_snapshot.py +0 -0
  198. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_usage_snapshot_replay.py +0 -0
  199. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_usage_statusline.py +0 -0
  200. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_usage_statusline_agy.py +0 -0
  201. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_usage_statusline_forwarder.py +0 -0
  202. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_usage_statusline_grok.py +0 -0
  203. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_usage_terse_mode.py +0 -0
  204. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_usage_terse_reminder.py +0 -0
  205. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_wheel_packaging.py +0 -0
  206. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_win_login_item.py +0 -0
  207. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_window_keeper.py +0 -0
  208. {usage_cli-0.32.2 → usage_cli-0.32.4}/tests/test_windows_manifest.py +0 -0
  209. {usage_cli-0.32.2 → usage_cli-0.32.4}/tui/__init__.py +0 -0
  210. {usage_cli-0.32.2 → usage_cli-0.32.4}/tui/app.py +0 -0
  211. {usage_cli-0.32.2 → usage_cli-0.32.4}/tui/sprite.py +0 -0
  212. {usage_cli-0.32.2 → usage_cli-0.32.4}/ui/__init__.py +0 -0
  213. {usage_cli-0.32.2 → usage_cli-0.32.4}/ui/html_report.py +0 -0
  214. {usage_cli-0.32.2 → usage_cli-0.32.4}/ui/report_charts.py +0 -0
  215. {usage_cli-0.32.2 → usage_cli-0.32.4}/ui/report_daily_chart.py +0 -0
  216. {usage_cli-0.32.2 → usage_cli-0.32.4}/ui/report_filter.py +0 -0
  217. {usage_cli-0.32.2 → usage_cli-0.32.4}/ui/report_ledger.py +0 -0
  218. {usage_cli-0.32.2 → usage_cli-0.32.4}/ui/report_scripts.py +0 -0
  219. {usage_cli-0.32.2 → usage_cli-0.32.4}/ui/report_styles.py +0 -0
  220. {usage_cli-0.32.2 → usage_cli-0.32.4}/ui/tables.py +0 -0
  221. {usage_cli-0.32.2 → usage_cli-0.32.4}/updates/__init__.py +0 -0
  222. {usage_cli-0.32.2 → usage_cli-0.32.4}/updates/release_notes.py +0 -0
  223. {usage_cli-0.32.2 → usage_cli-0.32.4}/usage_cli.egg-info/dependency_links.txt +0 -0
  224. {usage_cli-0.32.2 → usage_cli-0.32.4}/usage_cli.egg-info/entry_points.txt +0 -0
  225. {usage_cli-0.32.2 → usage_cli-0.32.4}/usage_cli.egg-info/requires.txt +0 -0
  226. {usage_cli-0.32.2 → usage_cli-0.32.4}/usage_cli.egg-info/top_level.txt +0 -0
  227. {usage_cli-0.32.2 → usage_cli-0.32.4}/usage_client.py +0 -0
  228. {usage_cli-0.32.2 → usage_cli-0.32.4}/usage_common/__init__.py +0 -0
  229. {usage_cli-0.32.2 → usage_cli-0.32.4}/usage_common/subprocess_utils.py +0 -0
  230. {usage_cli-0.32.2 → usage_cli-0.32.4}/usage_common/time_utils.py +0 -0
  231. {usage_cli-0.32.2 → usage_cli-0.32.4}/usage_common/usage_dir_sweeper.py +0 -0
  232. {usage_cli-0.32.2 → usage_cli-0.32.4}/usage_common/usage_lang.py +0 -0
  233. {usage_cli-0.32.2 → usage_cli-0.32.4}/usage_common/usage_logging.py +0 -0
  234. {usage_cli-0.32.2 → usage_cli-0.32.4}/usage_diagnosis_snapshot.py +0 -0
  235. {usage_cli-0.32.2 → usage_cli-0.32.4}/usage_notifications.py +0 -0
  236. {usage_cli-0.32.2 → usage_cli-0.32.4}/usage_session_resume.py +0 -0
  237. {usage_cli-0.32.2 → usage_cli-0.32.4}/usage_statusline.py +0 -0
  238. {usage_cli-0.32.2 → usage_cli-0.32.4}/usage_statusline_agy.py +0 -0
  239. {usage_cli-0.32.2 → usage_cli-0.32.4}/usage_statusline_forwarder.py +0 -0
  240. {usage_cli-0.32.2 → usage_cli-0.32.4}/usage_statusline_grok.py +0 -0
  241. {usage_cli-0.32.2 → usage_cli-0.32.4}/usage_terse_mode.py +0 -0
  242. {usage_cli-0.32.2 → usage_cli-0.32.4}/usage_terse_reminder.py +0 -0
  243. {usage_cli-0.32.2 → usage_cli-0.32.4}/wintray/__init__.py +0 -0
  244. {usage_cli-0.32.2 → usage_cli-0.32.4}/wintray/login_item.py +0 -0
  245. {usage_cli-0.32.2 → usage_cli-0.32.4}/wintray/watch.py +0 -0
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: usage-cli
3
- Version: 0.32.2
3
+ Version: 0.32.4
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"
@@ -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
 
@@ -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 小時立刻開始計時(會消耗極少量額度)",
@@ -56,6 +60,7 @@
56
60
  "data_stale_hint": "資料來自備援來源,建議檢查設定",
57
61
  "status_loading": "載入中",
58
62
  "status_synced": "✓ 已同步",
63
+ "status_refreshing": "↻ 更新中…",
59
64
  "status_no_data": "無資料",
60
65
  "awaiting_rate_limits": "請對 Claude Code 發送一句訊息以同步配額",
61
66
  "hook_broken_not_installed": "⚠ 狀態列沒在更新,請按「設定狀態列」重新安裝",
@@ -75,6 +80,7 @@
75
80
  "percent_used": "{value}% 已用",
76
81
  "reset_in": "重置 {time}",
77
82
  "reset_imminent": "即將重置",
83
+ "reset_done": "已重置",
78
84
  "burn_warning": "⚠ 照目前速度 {empty}後用完 · 重置 {reset}",
79
85
  "burn_warning_compact": "⚠ {empty}後用完",
80
86
  "reset_placeholder": "重置 --",
@@ -490,7 +496,22 @@
490
496
  "claude_pane_invalid_sidecar": "usage-pane.json 格式錯誤",
491
497
  "claude_pane_elapsed": "{minutes}分{seconds}秒",
492
498
  "claude_pane_remaining": "剩",
493
- "claude_pane_hours_ago": "{count}時前"
499
+ "claude_pane_reset_done": "已重置",
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": "已停止"
494
515
  },
495
516
  "en": {
496
517
  "panel_default_name": "Default",
@@ -515,6 +536,10 @@
515
536
  "launch_at_login": "Launch at Login",
516
537
  "reset_panel_position": "Reset panel position",
517
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}: --",
518
543
  "quota_notifications_menu": "Usage Alert Notifications",
519
544
  "window_keeper_menu": "Auto-start 5-hour Session",
520
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).",
@@ -549,6 +574,7 @@
549
574
  "data_stale_hint": "Data is from a fallback source. Check settings.",
550
575
  "status_loading": "Loading",
551
576
  "status_synced": "✓ Synced",
577
+ "status_refreshing": "↻ Refreshing…",
552
578
  "status_no_data": "No data",
553
579
  "awaiting_rate_limits": "Send a message in Claude Code to sync your quota",
554
580
  "hook_broken_not_installed": "⚠ Status line is not updating. Click \"Set Up Status Line\" to reinstall it.",
@@ -568,6 +594,7 @@
568
594
  "percent_used": "{value}% used",
569
595
  "reset_in": "Resets in {time}",
570
596
  "reset_imminent": "Reset imminent",
597
+ "reset_done": "Reset",
571
598
  "burn_warning": "⚠ At this pace, empty in {empty} · resets in {reset}",
572
599
  "burn_warning_compact": "⚠ Empty in {empty}",
573
600
  "reset_placeholder": "Resets in --",
@@ -983,7 +1010,22 @@
983
1010
  "claude_pane_invalid_sidecar": "Invalid usage-pane.json",
984
1011
  "claude_pane_elapsed": "{minutes}m{seconds}s",
985
1012
  "claude_pane_remaining": "left",
986
- "claude_pane_hours_ago": "{count}h ago"
1013
+ "claude_pane_reset_done": "Reset",
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"
987
1029
  },
988
1030
  "zh-CN": {
989
1031
  "panel_default_name": "默认",
@@ -1008,6 +1050,10 @@
1008
1050
  "launch_at_login": "开机自动启动",
1009
1051
  "reset_panel_position": "重设面板位置",
1010
1052
  "hide_sections_menu": "隐藏区块",
1053
+ "tray_provider_menu": "托盘显示来源",
1054
+ "quota_label_menu": "显示任务栏额度",
1055
+ "quota_label_format": "{provider}: {value}%",
1056
+ "quota_label_unknown": "{provider}: --",
1011
1057
  "quota_notifications_menu": "用量提醒通知",
1012
1058
  "window_keeper_menu": "自动启动 5 小时计时",
1013
1059
  "window_keeper_tooltip": "5 小时额度重置后,自动各发送一则极小消息(Claude 用 Haiku、agy 用 Gemini 3.5 Flash Low、Codex 用最省成本模型),让下一轮 5 小时立刻开始计时(会消耗极少量额度)",
@@ -1042,6 +1088,7 @@
1042
1088
  "data_stale_hint": "数据来自备用来源,建议检查设置",
1043
1089
  "status_loading": "加载中",
1044
1090
  "status_synced": "✓ 已同步",
1091
+ "status_refreshing": "↻ 更新中…",
1045
1092
  "status_no_data": "无数据",
1046
1093
  "awaiting_rate_limits": "请对 Claude Code 发送一句消息以同步配额",
1047
1094
  "hook_broken_not_installed": "⚠ 状态栏没有更新,请点「设置状态栏」重新安装",
@@ -1061,6 +1108,7 @@
1061
1108
  "percent_used": "{value}% 已用",
1062
1109
  "reset_in": "重置 {time}",
1063
1110
  "reset_imminent": "即将重置",
1111
+ "reset_done": "已重置",
1064
1112
  "burn_warning": "⚠ 按当前速度 {empty}后用完 · 重置 {reset}",
1065
1113
  "burn_warning_compact": "⚠ {empty}后用完",
1066
1114
  "reset_placeholder": "重置 --",
@@ -1476,7 +1524,22 @@
1476
1524
  "claude_pane_invalid_sidecar": "usage-pane.json 格式错误",
1477
1525
  "claude_pane_elapsed": "{minutes}分{seconds}秒",
1478
1526
  "claude_pane_remaining": "剩",
1479
- "claude_pane_hours_ago": "{count}时前"
1527
+ "claude_pane_reset_done": "已重置",
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": "已停止"
1480
1543
  },
1481
1544
  "ja": {
1482
1545
  "panel_default_name": "デフォルト",
@@ -1501,6 +1564,10 @@
1501
1564
  "launch_at_login": "ログイン時に起動",
1502
1565
  "reset_panel_position": "パネルの位置をリセット",
1503
1566
  "hide_sections_menu": "セクションを非表示",
1567
+ "tray_provider_menu": "トレイの表示元",
1568
+ "quota_label_menu": "タスクバーにクォータを表示",
1569
+ "quota_label_format": "{provider}: {value}%",
1570
+ "quota_label_unknown": "{provider}: --",
1504
1571
  "quota_notifications_menu": "使用量アラート通知",
1505
1572
  "window_keeper_menu": "5 時間セッションを自動開始",
1506
1573
  "window_keeper_tooltip": "5 時間枠のリセット後、各ツールにごく小さなメッセージを自動送信します(Claude は Haiku、agy は Gemini 3.5 Flash Low、Codex は最も低コストなモデル)。次の 5 時間のカウントをすぐに開始します(ごくわずかな枠を消費します)。",
@@ -1535,6 +1602,7 @@
1535
1602
  "data_stale_hint": "データはフォールバック元から取得しています。設定を確認してください。",
1536
1603
  "status_loading": "読み込み中",
1537
1604
  "status_synced": "✓ 同期済み",
1605
+ "status_refreshing": "↻ 更新中…",
1538
1606
  "status_no_data": "データなし",
1539
1607
  "awaiting_rate_limits": "Claude Code にメッセージを送信してクォータを同期してください",
1540
1608
  "hook_broken_not_installed": "⚠ ステータスラインが更新されていません。「ステータスラインを設定」を押して再インストールしてください。",
@@ -1554,6 +1622,7 @@
1554
1622
  "percent_used": "{value}% 使用済み",
1555
1623
  "reset_in": "{time}後にリセット",
1556
1624
  "reset_imminent": "まもなくリセット",
1625
+ "reset_done": "リセット済み",
1557
1626
  "burn_warning": "⚠ このペースで{empty}で使い切り·リセット{reset}",
1558
1627
  "burn_warning_compact": "⚠ {empty}で使い切り",
1559
1628
  "reset_placeholder": "リセット --",
@@ -1969,7 +2038,22 @@
1969
2038
  "claude_pane_invalid_sidecar": "usage-pane.json の形式が不正です",
1970
2039
  "claude_pane_elapsed": "{minutes}分{seconds}秒",
1971
2040
  "claude_pane_remaining": "残り",
1972
- "claude_pane_hours_ago": "{count}時間前"
2041
+ "claude_pane_reset_done": "リセット済み",
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": "停止済み"
1973
2057
  },
1974
2058
  "ko": {
1975
2059
  "panel_default_name": "기본",
@@ -1994,6 +2078,10 @@
1994
2078
  "launch_at_login": "로그인 시 시작",
1995
2079
  "reset_panel_position": "패널 위치 재설정",
1996
2080
  "hide_sections_menu": "섹션 숨기기",
2081
+ "tray_provider_menu": "트레이 표시 소스",
2082
+ "quota_label_menu": "작업 표시줄 할당량 표시",
2083
+ "quota_label_format": "{provider}: {value}%",
2084
+ "quota_label_unknown": "{provider}: --",
1997
2085
  "quota_notifications_menu": "사용량 알림",
1998
2086
  "window_keeper_menu": "5시간 세션 자동 시작",
1999
2087
  "window_keeper_tooltip": "5시간 할당량이 리셋되면 각 도구에 아주 작은 메시지를 자동 전송합니다 (Claude는 Haiku, agy는 Gemini 3.5 Flash Low, Codex는 가장 저렴한 모델). 다음 5시간 카운트를 바로 시작합니다 (극소량의 할당량을 소모합니다).",
@@ -2028,6 +2116,7 @@
2028
2116
  "data_stale_hint": "데이터가 대체 소스에서 왔습니다. 설정을 확인하세요.",
2029
2117
  "status_loading": "불러오는 중",
2030
2118
  "status_synced": "✓ 동기화됨",
2119
+ "status_refreshing": "↻ 새로 고치는 중…",
2031
2120
  "status_no_data": "데이터 없음",
2032
2121
  "awaiting_rate_limits": "Claude Code에 메시지를 보내 할당량을 동기화하세요",
2033
2122
  "hook_broken_not_installed": "⚠ 상태 표시줄이 업데이트되지 않습니다. \"상태 표시줄 설정\"을 눌러 다시 설치하세요.",
@@ -2047,6 +2136,7 @@
2047
2136
  "percent_used": "{value}% 사용됨",
2048
2137
  "reset_in": "{time} 후 초기화",
2049
2138
  "reset_imminent": "곧 초기화",
2139
+ "reset_done": "리셋됨",
2050
2140
  "burn_warning": "⚠ 현재 속도면 {empty} 후 소진 · 초기화까지 {reset}",
2051
2141
  "burn_warning_compact": "⚠ {empty} 후 소진",
2052
2142
  "reset_placeholder": "초기화까지 --",
@@ -2462,6 +2552,21 @@
2462
2552
  "claude_pane_invalid_sidecar": "usage-pane.json 형식이 잘못되었습니다",
2463
2553
  "claude_pane_elapsed": "{minutes}분{seconds}초",
2464
2554
  "claude_pane_remaining": "남음",
2465
- "claude_pane_hours_ago": "{count}시간 전"
2555
+ "claude_pane_reset_done": "리셋됨",
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": "중지됨"
2466
2571
  }
2467
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"
@@ -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 (
@@ -213,20 +214,20 @@ def _remaining_percent(window: AgyQuotaWindow) -> float:
213
214
  return max(0.0, min(100.0, float(window.remaining_percent)))
214
215
 
215
216
 
216
- def _cache_age_minutes(fetched_at: str, now: float) -> int:
217
- """Whole minutes since the cached snapshot was taken (never negative)."""
217
+ def _cache_age_minutes(fetched_at: str, now: float) -> float:
218
+ """Minutes since the cached snapshot was taken (never negative)."""
218
219
  try:
219
220
  age_seconds = now - parse_iso8601_utc_or_raise(fetched_at).timestamp()
220
221
  except (TypeError, ValueError):
221
222
  return 0
222
- return max(0, int(age_seconds // 60))
223
+ return max(0.0, age_seconds / 60)
223
224
 
224
225
 
225
226
  def _window_row(
226
227
  title: str,
227
228
  window: AgyQuotaWindow,
228
229
  language: str,
229
- age_minutes: int = 0,
230
+ age_minutes: float = 0,
230
231
  forecast_seconds: float | None = None,
231
232
  warning_max_seconds: float | None = None,
232
233
  *,
@@ -236,12 +237,20 @@ def _window_row(
236
237
  used = 100.0 - remaining
237
238
  warning = False
238
239
  warning_seconds: float | None = None
239
- if remaining == 100.0:
240
+ reset_done = window.resets_in_minutes is not None and window.resets_in_minutes <= max(
241
+ 0, age_minutes
242
+ )
243
+ if reset_done:
244
+ used = 0.0
245
+ reset_text = _t(language, "reset_done")
246
+ elif remaining == 100.0:
240
247
  reset_text = _t(language, "agy_quota_full")
241
248
  elif window.resets_in_minutes is None:
242
249
  reset_text = _t(language, "reset_placeholder")
250
+ elif window.resets_in_minutes - max(0, age_minutes) < 1:
251
+ reset_text = _t(language, "reset_imminent")
243
252
  else:
244
- minutes_left = max(1, window.resets_in_minutes - max(0, age_minutes))
253
+ minutes_left = window.resets_in_minutes - int(max(0, age_minutes))
245
254
  time_to_reset = minutes_left * 60
246
255
  if window_seconds is None:
247
256
  if (
@@ -275,16 +284,22 @@ def _window_row(
275
284
  )
276
285
  return QuotaRowState(
277
286
  title=title,
278
- percent=used,
287
+ percent=100.0 - remaining,
279
288
  percent_text=_t(language, "percent_used", value=_format_percent(used)),
280
289
  reset_text=reset_text,
281
290
  color=_bar_color(used, AGY_COLOR),
282
291
  warning=warning,
283
292
  available=True,
293
+ reset_done=reset_done,
284
294
  )
285
295
 
286
296
 
287
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
+ }
288
303
  try:
289
304
  age_seconds = now - parse_iso8601_utc_or_raise(fetched_at).timestamp()
290
305
  except (TypeError, ValueError):