usage-cli 0.34.11__tar.gz → 0.34.13__tar.gz

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