usage-cli 0.32.6__tar.gz → 0.33.0__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 (251) hide show
  1. {usage_cli-0.32.6 → usage_cli-0.33.0}/PKG-INFO +6 -2
  2. {usage_cli-0.32.6 → usage_cli-0.33.0}/README.md +5 -1
  3. {usage_cli-0.32.6 → usage_cli-0.33.0}/i18n.json +140 -5
  4. {usage_cli-0.32.6 → usage_cli-0.33.0}/installer/claude_pane.py +72 -33
  5. {usage_cli-0.32.6 → usage_cli-0.33.0}/installer/session_hooks.py +116 -0
  6. {usage_cli-0.32.6 → usage_cli-0.33.0}/menubar/actions.py +89 -6
  7. {usage_cli-0.32.6 → usage_cli-0.33.0}/menubar/menu.py +44 -8
  8. {usage_cli-0.32.6 → usage_cli-0.33.0}/menubar/refresh.py +3 -0
  9. {usage_cli-0.32.6 → usage_cli-0.33.0}/pyproject.toml +3 -1
  10. usage_cli-0.33.0/quota/quota_snapshot.py +32 -0
  11. {usage_cli-0.32.6 → usage_cli-0.33.0}/quota/usage_rate.py +39 -3
  12. {usage_cli-0.32.6 → usage_cli-0.33.0}/setup_app.py +1 -0
  13. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_claude_pane.py +76 -0
  14. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_hook_scripts_are_standalone.py +1 -0
  15. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_menubar.py +90 -10
  16. usage_cli-0.33.0/tests/test_quota_aware.py +252 -0
  17. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_usage_rate.py +72 -1
  18. {usage_cli-0.32.6 → usage_cli-0.33.0}/usage_cli.egg-info/PKG-INFO +6 -2
  19. {usage_cli-0.32.6 → usage_cli-0.33.0}/usage_cli.egg-info/SOURCES.txt +3 -0
  20. {usage_cli-0.32.6 → usage_cli-0.33.0}/usage_cli.egg-info/top_level.txt +1 -0
  21. usage_cli-0.33.0/usage_quota_aware.py +150 -0
  22. {usage_cli-0.32.6 → usage_cli-0.33.0}/LICENSE +0 -0
  23. {usage_cli-0.32.6 → usage_cli-0.33.0}/THIRD_PARTY_NOTICES.md +0 -0
  24. {usage_cli-0.32.6 → usage_cli-0.33.0}/adapters/__init__.py +0 -0
  25. {usage_cli-0.32.6 → usage_cli-0.33.0}/adapters/agy.py +0 -0
  26. {usage_cli-0.32.6 → usage_cli-0.33.0}/adapters/claude.py +0 -0
  27. {usage_cli-0.32.6 → usage_cli-0.33.0}/adapters/codex.py +0 -0
  28. {usage_cli-0.32.6 → usage_cli-0.33.0}/adapters/grok.py +0 -0
  29. {usage_cli-0.32.6 → usage_cli-0.33.0}/adapters/muse.py +0 -0
  30. {usage_cli-0.32.6 → usage_cli-0.33.0}/adapters/rate_limits.py +0 -0
  31. {usage_cli-0.32.6 → usage_cli-0.33.0}/adapters/registry.py +0 -0
  32. {usage_cli-0.32.6 → usage_cli-0.33.0}/adapters/types.py +0 -0
  33. {usage_cli-0.32.6 → usage_cli-0.33.0}/analyzer/__init__.py +0 -0
  34. {usage_cli-0.32.6 → usage_cli-0.33.0}/analyzer/aggregator.py +0 -0
  35. {usage_cli-0.32.6 → usage_cli-0.33.0}/analyzer/blocks.py +0 -0
  36. {usage_cli-0.32.6 → usage_cli-0.33.0}/analyzer/diagnoser.py +0 -0
  37. {usage_cli-0.32.6 → usage_cli-0.33.0}/analyzer/dispatch_ledger.py +0 -0
  38. {usage_cli-0.32.6 → usage_cli-0.33.0}/analyzer/insights.py +0 -0
  39. {usage_cli-0.32.6 → usage_cli-0.33.0}/analyzer/persona_loader.py +0 -0
  40. {usage_cli-0.32.6 → usage_cli-0.33.0}/analyzer/reporter.py +0 -0
  41. {usage_cli-0.32.6 → usage_cli-0.33.0}/analyzer/subscription.py +0 -0
  42. {usage_cli-0.32.6 → usage_cli-0.33.0}/analyzer/usage_snapshot.py +0 -0
  43. {usage_cli-0.32.6 → usage_cli-0.33.0}/doctor.py +0 -0
  44. {usage_cli-0.32.6 → usage_cli-0.33.0}/fsevents_watch.py +0 -0
  45. {usage_cli-0.32.6 → usage_cli-0.33.0}/i18n.py +0 -0
  46. {usage_cli-0.32.6 → usage_cli-0.33.0}/installer/__init__.py +0 -0
  47. {usage_cli-0.32.6 → usage_cli-0.33.0}/installer/codex_hook_trust.py +0 -0
  48. {usage_cli-0.32.6 → usage_cli-0.33.0}/installer/login_item.py +0 -0
  49. {usage_cli-0.32.6 → usage_cli-0.33.0}/installer/setup_hook.py +0 -0
  50. {usage_cli-0.32.6 → usage_cli-0.33.0}/installer/statusline_settings.py +0 -0
  51. {usage_cli-0.32.6 → usage_cli-0.33.0}/loaders/__init__.py +0 -0
  52. {usage_cli-0.32.6 → usage_cli-0.33.0}/loaders/agy_disk_cache.py +0 -0
  53. {usage_cli-0.32.6 → usage_cli-0.33.0}/loaders/agy_loader.py +0 -0
  54. {usage_cli-0.32.6 → usage_cli-0.33.0}/loaders/agy_quota_probe.py +0 -0
  55. {usage_cli-0.32.6 → usage_cli-0.33.0}/loaders/cache_quarantine.py +0 -0
  56. {usage_cli-0.32.6 → usage_cli-0.33.0}/loaders/claude_paths.py +0 -0
  57. {usage_cli-0.32.6 → usage_cli-0.33.0}/loaders/codex_disk_cache.py +0 -0
  58. {usage_cli-0.32.6 → usage_cli-0.33.0}/loaders/codex_events.py +0 -0
  59. {usage_cli-0.32.6 → usage_cli-0.33.0}/loaders/codex_fork_replay.py +0 -0
  60. {usage_cli-0.32.6 → usage_cli-0.33.0}/loaders/codex_loader.py +0 -0
  61. {usage_cli-0.32.6 → usage_cli-0.33.0}/loaders/codex_paths.py +0 -0
  62. {usage_cli-0.32.6 → usage_cli-0.33.0}/loaders/disk_cache_common.py +0 -0
  63. {usage_cli-0.32.6 → usage_cli-0.33.0}/loaders/disk_cache_lifecycle.py +0 -0
  64. {usage_cli-0.32.6 → usage_cli-0.33.0}/loaders/grok_loader.py +0 -0
  65. {usage_cli-0.32.6 → usage_cli-0.33.0}/loaders/grok_quota_probe.py +0 -0
  66. {usage_cli-0.32.6 → usage_cli-0.33.0}/loaders/history_disk_cache.py +0 -0
  67. {usage_cli-0.32.6 → usage_cli-0.33.0}/loaders/history_loader.py +0 -0
  68. {usage_cli-0.32.6 → usage_cli-0.33.0}/loaders/jsonl_limits.py +0 -0
  69. {usage_cli-0.32.6 → usage_cli-0.33.0}/loaders/jsonl_utils.py +0 -0
  70. {usage_cli-0.32.6 → usage_cli-0.33.0}/loaders/muse_loader.py +0 -0
  71. {usage_cli-0.32.6 → usage_cli-0.33.0}/main.py +0 -0
  72. {usage_cli-0.32.6 → usage_cli-0.33.0}/menubar/__init__.py +0 -0
  73. {usage_cli-0.32.6 → usage_cli-0.33.0}/menubar/agy.py +0 -0
  74. {usage_cli-0.32.6 → usage_cli-0.33.0}/menubar/app.py +0 -0
  75. {usage_cli-0.32.6 → usage_cli-0.33.0}/menubar/chrome.py +0 -0
  76. {usage_cli-0.32.6 → usage_cli-0.33.0}/menubar/grok.py +0 -0
  77. {usage_cli-0.32.6 → usage_cli-0.33.0}/menubar/manual_refresh.py +0 -0
  78. {usage_cli-0.32.6 → usage_cli-0.33.0}/menubar/new_badge.py +0 -0
  79. {usage_cli-0.32.6 → usage_cli-0.33.0}/menubar/notify.py +0 -0
  80. {usage_cli-0.32.6 → usage_cli-0.33.0}/menubar/popover.py +0 -0
  81. {usage_cli-0.32.6 → usage_cli-0.33.0}/menubar/prefs.py +0 -0
  82. {usage_cli-0.32.6 → usage_cli-0.33.0}/menubar/state.py +0 -0
  83. {usage_cli-0.32.6 → usage_cli-0.33.0}/menubar/title.py +0 -0
  84. {usage_cli-0.32.6 → usage_cli-0.33.0}/menubar/update.py +0 -0
  85. {usage_cli-0.32.6 → usage_cli-0.33.0}/menubar/whats_new.py +0 -0
  86. {usage_cli-0.32.6 → usage_cli-0.33.0}/panels/__init__.py +0 -0
  87. {usage_cli-0.32.6 → usage_cli-0.33.0}/panels/base.py +0 -0
  88. {usage_cli-0.32.6 → usage_cli-0.33.0}/panels/dynamic_height.py +0 -0
  89. {usage_cli-0.32.6 → usage_cli-0.33.0}/panels/panel_scale.py +0 -0
  90. {usage_cli-0.32.6 → usage_cli-0.33.0}/panels/panel_window.py +0 -0
  91. {usage_cli-0.32.6 → usage_cli-0.33.0}/panels/panel_window_state.py +0 -0
  92. {usage_cli-0.32.6 → usage_cli-0.33.0}/panels/payload.py +0 -0
  93. {usage_cli-0.32.6 → usage_cli-0.33.0}/panels/web_panel.py +0 -0
  94. {usage_cli-0.32.6 → usage_cli-0.33.0}/panels/window_drag.py +0 -0
  95. {usage_cli-0.32.6 → usage_cli-0.33.0}/prefs.py +0 -0
  96. {usage_cli-0.32.6 → usage_cli-0.33.0}/pricing.py +0 -0
  97. {usage_cli-0.32.6 → usage_cli-0.33.0}/project_resolver.py +0 -0
  98. {usage_cli-0.32.6 → usage_cli-0.33.0}/quota/__init__.py +0 -0
  99. {usage_cli-0.32.6 → usage_cli-0.33.0}/quota/agy_window_keeper.py +0 -0
  100. {usage_cli-0.32.6 → usage_cli-0.33.0}/quota/burn_rate.py +0 -0
  101. {usage_cli-0.32.6 → usage_cli-0.33.0}/quota/codex_window_keeper.py +0 -0
  102. {usage_cli-0.32.6 → usage_cli-0.33.0}/quota/keeper_outcome.py +0 -0
  103. {usage_cli-0.32.6 → usage_cli-0.33.0}/quota/window_keeper.py +0 -0
  104. {usage_cli-0.32.6 → usage_cli-0.33.0}/service_status.py +0 -0
  105. {usage_cli-0.32.6 → usage_cli-0.33.0}/setup.cfg +0 -0
  106. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_adapters_claude.py +0 -0
  107. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_adapters_codex.py +0 -0
  108. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_adapters_rate_limits.py +0 -0
  109. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_aggregator.py +0 -0
  110. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_agy_adapter.py +0 -0
  111. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_agy_disk_cache.py +0 -0
  112. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_agy_loader.py +0 -0
  113. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_agy_quota_probe.py +0 -0
  114. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_agy_window_keeper.py +0 -0
  115. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_analyzer_blocks.py +0 -0
  116. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_analyzer_pipeline.py +0 -0
  117. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_burn_rate.py +0 -0
  118. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_cache_quarantine.py +0 -0
  119. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_check_upstream.py +0 -0
  120. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_claude_paths.py +0 -0
  121. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_codex_consistency.py +0 -0
  122. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_codex_hook_trust.py +0 -0
  123. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_codex_loader.py +0 -0
  124. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_codex_window_keeper.py +0 -0
  125. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_diagnoser.py +0 -0
  126. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_disk_cache_shards.py +0 -0
  127. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_dispatch_ledger.py +0 -0
  128. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_doctor.py +0 -0
  129. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_dynamic_height.py +0 -0
  130. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_fsevents_watch.py +0 -0
  131. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_grok_loader.py +0 -0
  132. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_grok_quota_probe.py +0 -0
  133. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_history_loader.py +0 -0
  134. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_html_report_snapshot.py +0 -0
  135. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_i18n_format_fallback.py +0 -0
  136. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_i18n_key_parity.py +0 -0
  137. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_i18n_packaged_path.py +0 -0
  138. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_insights.py +0 -0
  139. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_jsonl_utils.py +0 -0
  140. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_keeper_menu.py +0 -0
  141. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_keeper_outcome.py +0 -0
  142. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_login_item.py +0 -0
  143. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_main.py +0 -0
  144. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_manual_refresh.py +0 -0
  145. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_menubar_agy.py +0 -0
  146. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_menubar_grok.py +0 -0
  147. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_menubar_notify.py +0 -0
  148. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_menubar_prefs.py +0 -0
  149. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_menubar_state.py +0 -0
  150. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_menubar_update.py +0 -0
  151. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_muse_loader.py +0 -0
  152. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_new_badge.py +0 -0
  153. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_packaged_resources.py +0 -0
  154. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_packaging.py +0 -0
  155. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_panel_footer_layout.py +0 -0
  156. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_panel_scale.py +0 -0
  157. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_panel_window_state.py +0 -0
  158. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_panels.py +0 -0
  159. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_panels_lazy_import.py +0 -0
  160. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_persona_loader.py +0 -0
  161. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_pricing.py +0 -0
  162. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_project_resolver.py +0 -0
  163. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_quota_label.py +0 -0
  164. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_report_daily_chart.py +0 -0
  165. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_report_filter_js.py +0 -0
  166. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_reporter_build_data.py +0 -0
  167. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_service_status.py +0 -0
  168. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_session_resume.py +0 -0
  169. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_setup_app.py +0 -0
  170. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_setup_hook.py +0 -0
  171. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_setup_hook_coexistence.py +0 -0
  172. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_setup_hook_resume.py +0 -0
  173. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_setup_hook_terse.py +0 -0
  174. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_subprocess_utils.py +0 -0
  175. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_subscription.py +0 -0
  176. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_time_utils.py +0 -0
  177. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_tui.py +0 -0
  178. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_tui_sprite.py +0 -0
  179. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_ui_tables.py +0 -0
  180. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_update_checker.py +0 -0
  181. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_update_gate.py +0 -0
  182. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_update_release_notes.py +0 -0
  183. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_usage_cli.py +0 -0
  184. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_usage_client.py +0 -0
  185. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_usage_diagnosis_snapshot.py +0 -0
  186. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_usage_dir_sweeper.py +0 -0
  187. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_usage_lang.py +0 -0
  188. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_usage_logging.py +0 -0
  189. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_usage_notifications.py +0 -0
  190. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_usage_snapshot.py +0 -0
  191. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_usage_snapshot_replay.py +0 -0
  192. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_usage_statusline.py +0 -0
  193. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_usage_statusline_agy.py +0 -0
  194. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_usage_statusline_forwarder.py +0 -0
  195. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_usage_statusline_grok.py +0 -0
  196. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_usage_terse_mode.py +0 -0
  197. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_usage_terse_reminder.py +0 -0
  198. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_web_panel_payload.py +0 -0
  199. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_whats_new.py +0 -0
  200. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_wheel_packaging.py +0 -0
  201. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_win_login_item.py +0 -0
  202. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_window_keeper.py +0 -0
  203. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_windows_manifest.py +0 -0
  204. {usage_cli-0.32.6 → usage_cli-0.33.0}/tests/test_wintray.py +0 -0
  205. {usage_cli-0.32.6 → usage_cli-0.33.0}/tui/__init__.py +0 -0
  206. {usage_cli-0.32.6 → usage_cli-0.33.0}/tui/app.py +0 -0
  207. {usage_cli-0.32.6 → usage_cli-0.33.0}/tui/sprite.py +0 -0
  208. {usage_cli-0.32.6 → usage_cli-0.33.0}/ui/__init__.py +0 -0
  209. {usage_cli-0.32.6 → usage_cli-0.33.0}/ui/html_report.py +0 -0
  210. {usage_cli-0.32.6 → usage_cli-0.33.0}/ui/report_charts.py +0 -0
  211. {usage_cli-0.32.6 → usage_cli-0.33.0}/ui/report_daily_chart.py +0 -0
  212. {usage_cli-0.32.6 → usage_cli-0.33.0}/ui/report_filter.py +0 -0
  213. {usage_cli-0.32.6 → usage_cli-0.33.0}/ui/report_ledger.py +0 -0
  214. {usage_cli-0.32.6 → usage_cli-0.33.0}/ui/report_scripts.py +0 -0
  215. {usage_cli-0.32.6 → usage_cli-0.33.0}/ui/report_styles.py +0 -0
  216. {usage_cli-0.32.6 → usage_cli-0.33.0}/ui/tables.py +0 -0
  217. {usage_cli-0.32.6 → usage_cli-0.33.0}/updates/__init__.py +0 -0
  218. {usage_cli-0.32.6 → usage_cli-0.33.0}/updates/checker.py +0 -0
  219. {usage_cli-0.32.6 → usage_cli-0.33.0}/updates/gate.py +0 -0
  220. {usage_cli-0.32.6 → usage_cli-0.33.0}/updates/release_notes.py +0 -0
  221. {usage_cli-0.32.6 → usage_cli-0.33.0}/updates/whats_new.py +0 -0
  222. {usage_cli-0.32.6 → usage_cli-0.33.0}/usage_cli.egg-info/dependency_links.txt +0 -0
  223. {usage_cli-0.32.6 → usage_cli-0.33.0}/usage_cli.egg-info/entry_points.txt +0 -0
  224. {usage_cli-0.32.6 → usage_cli-0.33.0}/usage_cli.egg-info/requires.txt +0 -0
  225. {usage_cli-0.32.6 → usage_cli-0.33.0}/usage_cli.py +0 -0
  226. {usage_cli-0.32.6 → usage_cli-0.33.0}/usage_client.py +0 -0
  227. {usage_cli-0.32.6 → usage_cli-0.33.0}/usage_common/__init__.py +0 -0
  228. {usage_cli-0.32.6 → usage_cli-0.33.0}/usage_common/subprocess_utils.py +0 -0
  229. {usage_cli-0.32.6 → usage_cli-0.33.0}/usage_common/time_utils.py +0 -0
  230. {usage_cli-0.32.6 → usage_cli-0.33.0}/usage_common/usage_dir_sweeper.py +0 -0
  231. {usage_cli-0.32.6 → usage_cli-0.33.0}/usage_common/usage_lang.py +0 -0
  232. {usage_cli-0.32.6 → usage_cli-0.33.0}/usage_common/usage_logging.py +0 -0
  233. {usage_cli-0.32.6 → usage_cli-0.33.0}/usage_diagnosis_snapshot.py +0 -0
  234. {usage_cli-0.32.6 → usage_cli-0.33.0}/usage_notifications.py +0 -0
  235. {usage_cli-0.32.6 → usage_cli-0.33.0}/usage_session_resume.py +0 -0
  236. {usage_cli-0.32.6 → usage_cli-0.33.0}/usage_statusline.py +0 -0
  237. {usage_cli-0.32.6 → usage_cli-0.33.0}/usage_statusline_agy.py +0 -0
  238. {usage_cli-0.32.6 → usage_cli-0.33.0}/usage_statusline_forwarder.py +0 -0
  239. {usage_cli-0.32.6 → usage_cli-0.33.0}/usage_statusline_grok.py +0 -0
  240. {usage_cli-0.32.6 → usage_cli-0.33.0}/usage_terse_mode.py +0 -0
  241. {usage_cli-0.32.6 → usage_cli-0.33.0}/usage_terse_reminder.py +0 -0
  242. {usage_cli-0.32.6 → usage_cli-0.33.0}/wintray/__init__.py +0 -0
  243. {usage_cli-0.32.6 → usage_cli-0.33.0}/wintray/app.py +0 -0
  244. {usage_cli-0.32.6 → usage_cli-0.33.0}/wintray/login_item.py +0 -0
  245. {usage_cli-0.32.6 → usage_cli-0.33.0}/wintray/menu.py +0 -0
  246. {usage_cli-0.32.6 → usage_cli-0.33.0}/wintray/prefs.py +0 -0
  247. {usage_cli-0.32.6 → usage_cli-0.33.0}/wintray/quota_label.py +0 -0
  248. {usage_cli-0.32.6 → usage_cli-0.33.0}/wintray/taskbar_obstacles.py +0 -0
  249. {usage_cli-0.32.6 → usage_cli-0.33.0}/wintray/taskbar_overlay.py +0 -0
  250. {usage_cli-0.32.6 → usage_cli-0.33.0}/wintray/watch.py +0 -0
  251. {usage_cli-0.32.6 → usage_cli-0.33.0}/wintray/whats_new.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: usage-cli
3
- Version: 0.32.6
3
+ Version: 0.33.0
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
@@ -78,6 +78,8 @@ It lands in your Applications folder automatically. Open it once; if macOS 15 or
78
78
  - **Progress Concierge:** Open a new Claude Code session and `usage` hands your last progress straight to the AI, including your last request, uncommitted changes, and unfinished todos. No `/resume`, no recap. When you do `/resume` a conversation that sat long enough for its cache to expire, it warns you how many tokens the next message will re-send and suggests `/compact` first. Off by default.
79
79
  - **Token Saver:** A menu-bar toggle asks Claude Code and Codex to answer more tersely and in plainer language, saving output tokens while keeping code and error messages byte-exact. A light reminder keeps long conversations from drifting back to verbose — in an A/B test on real sessions, late replies stayed ~40% shorter instead of drifting 84% longer.
80
80
  - **Claude Code side pane (macOS and Windows):** Quotas, conversations, and background jobs inside Claude Code. [See the side pane](#claude-code-side-pane).
81
+ - **Beginner Mode (macOS):** Off by default. After Claude Code answers, up to three technical terms from that answer appear above the prompt, each with a one-line plain explanation in your UI language. Press 9 to mark them understood; terms you skip come back after 1, 3, then 7 days. Understood terms return as a one-question multiple-choice quiz 7, 21, then 60 days later, at most one a day; a wrong answer puts the term back in the hints. `/terms` opens your term history. Picking terms asks Claude Haiku through your Claude Code, so it uses a little Claude quota, and it pauses while your 5-hour quota is at 90% or more.
82
+ - **Quota-Aware Mode (macOS):** Off by default. When a 5-hour quota passes 80%, 90%, or 95%, or a weekly quota passes 95%, Claude Code gets one line with what is left and when it resets. Before a big task, Claude then tells you and lets you pick a smaller part or wait for the reset. It covers Claude Code, Codex, and Antigravity quota, says each level once per conversation, and makes no extra model calls.
81
83
  - **Auto-start 5-hour Session:** Off by default. Turn it on and, right after a 5-hour quota resets, `usage` sends one tiny message to each tool (Claude with Haiku, Antigravity with Gemini 3.5 Flash Low, Codex with its cheapest model) so the next 5-hour window starts counting right away. Those messages do use a little quota, but the amount is negligible. Checking your quota never sends a message; only this switch does.
82
84
  - **Terminal Integration:** `usage status --json` hands your Claude Code, Codex, Antigravity, and Grok quota to any tool that can run a command — Starship, tmux, or your own scripts. Reads the same local files as the menu bar. [Ready-made snippets](docs/DEVELOPMENT.md#quota-status-for-other-tools-usage-status).
83
85
  - **Token-waste Health Check:** A daily background diagnosis scans your logs for waste, including repeated file reads, polluter directories, and noisy Bash output. If it finds issues, a one-line heads-up appears; say "show me" and the AI walks you through fixes.
@@ -102,6 +104,7 @@ It lands in your Applications folder automatically. Open it once; if macOS 15 or
102
104
  - Claude Code and Codex numbers are read from local log files on your machine.
103
105
  - Antigravity quota requires network access, and only if you use it: quota is fetched from Google's official quota endpoint using the OAuth credential the Antigravity CLI already stored after sign-in — read from macOS Keychain, Windows Credential Manager, or a local token file depending on CLI version. `usage` reads that credential without writing it back and keeps any refreshed access token in memory only; the call itself reads quota metadata.
104
106
  - Background network activity: the Antigravity quota/token endpoints above, public Claude and Codex status pages to flag outages, a public model-pricing table to estimate cost (falls back to built-in prices offline), and occasionally checking GitHub for a new version. Claude Code and Codex log contents are never uploaded.
107
+ - Beginner Mode, only if you turn it on, sends Claude Code's latest answer to Claude Haiku through your own Claude Code sign-in to pick terms. Your term list stays in `~/.usage/glossary.json`.
105
108
 
106
109
  ## Requirements
107
110
 
@@ -162,7 +165,7 @@ See your quota, other conversations, and background jobs without leaving Claude
162
165
 
163
166
  **How to enable**
164
167
 
165
- 1. In the usage menu bar menu (macOS) or the system-tray panel menu (Windows), check **Claude Code side pane**.
168
+ 1. In the usage menu bar menu (macOS) or the system-tray panel menu (Windows), check **Claude Code side pane**. On macOS it sits under the **Claude Code** submenu as **Side pane**.
166
169
  2. Open a new conversation or run `/reload-plugins`.
167
170
  3. At terminal widths ≥144 columns, the pane opens on the right automatically. In narrower terminals, enter `/usage-dash`.
168
171
 
@@ -251,6 +254,7 @@ If the menu bar shows `--`, it's usually not broken — there's just no local da
251
254
  | AI Update Daily | ✅ | — | — |
252
255
  | Progress Concierge & Token Saver | ✅ | — | — |
253
256
  | Token-waste Health Check | ✅ | — | — |
257
+ | Claude Code Beginner Mode & Quota-Aware Mode | ✅ | — | — |
254
258
  | Open-source license | AGPL-3.0 | MIT | — |
255
259
 
256
260
  ## When usage Isn't the Right Fit
@@ -57,6 +57,8 @@ It lands in your Applications folder automatically. Open it once; if macOS 15 or
57
57
  - **Progress Concierge:** Open a new Claude Code session and `usage` hands your last progress straight to the AI, including your last request, uncommitted changes, and unfinished todos. No `/resume`, no recap. When you do `/resume` a conversation that sat long enough for its cache to expire, it warns you how many tokens the next message will re-send and suggests `/compact` first. Off by default.
58
58
  - **Token Saver:** A menu-bar toggle asks Claude Code and Codex to answer more tersely and in plainer language, saving output tokens while keeping code and error messages byte-exact. A light reminder keeps long conversations from drifting back to verbose — in an A/B test on real sessions, late replies stayed ~40% shorter instead of drifting 84% longer.
59
59
  - **Claude Code side pane (macOS and Windows):** Quotas, conversations, and background jobs inside Claude Code. [See the side pane](#claude-code-side-pane).
60
+ - **Beginner Mode (macOS):** Off by default. After Claude Code answers, up to three technical terms from that answer appear above the prompt, each with a one-line plain explanation in your UI language. Press 9 to mark them understood; terms you skip come back after 1, 3, then 7 days. Understood terms return as a one-question multiple-choice quiz 7, 21, then 60 days later, at most one a day; a wrong answer puts the term back in the hints. `/terms` opens your term history. Picking terms asks Claude Haiku through your Claude Code, so it uses a little Claude quota, and it pauses while your 5-hour quota is at 90% or more.
61
+ - **Quota-Aware Mode (macOS):** Off by default. When a 5-hour quota passes 80%, 90%, or 95%, or a weekly quota passes 95%, Claude Code gets one line with what is left and when it resets. Before a big task, Claude then tells you and lets you pick a smaller part or wait for the reset. It covers Claude Code, Codex, and Antigravity quota, says each level once per conversation, and makes no extra model calls.
60
62
  - **Auto-start 5-hour Session:** Off by default. Turn it on and, right after a 5-hour quota resets, `usage` sends one tiny message to each tool (Claude with Haiku, Antigravity with Gemini 3.5 Flash Low, Codex with its cheapest model) so the next 5-hour window starts counting right away. Those messages do use a little quota, but the amount is negligible. Checking your quota never sends a message; only this switch does.
61
63
  - **Terminal Integration:** `usage status --json` hands your Claude Code, Codex, Antigravity, and Grok quota to any tool that can run a command — Starship, tmux, or your own scripts. Reads the same local files as the menu bar. [Ready-made snippets](docs/DEVELOPMENT.md#quota-status-for-other-tools-usage-status).
62
64
  - **Token-waste Health Check:** A daily background diagnosis scans your logs for waste, including repeated file reads, polluter directories, and noisy Bash output. If it finds issues, a one-line heads-up appears; say "show me" and the AI walks you through fixes.
@@ -81,6 +83,7 @@ It lands in your Applications folder automatically. Open it once; if macOS 15 or
81
83
  - Claude Code and Codex numbers are read from local log files on your machine.
82
84
  - Antigravity quota requires network access, and only if you use it: quota is fetched from Google's official quota endpoint using the OAuth credential the Antigravity CLI already stored after sign-in — read from macOS Keychain, Windows Credential Manager, or a local token file depending on CLI version. `usage` reads that credential without writing it back and keeps any refreshed access token in memory only; the call itself reads quota metadata.
83
85
  - Background network activity: the Antigravity quota/token endpoints above, public Claude and Codex status pages to flag outages, a public model-pricing table to estimate cost (falls back to built-in prices offline), and occasionally checking GitHub for a new version. Claude Code and Codex log contents are never uploaded.
86
+ - Beginner Mode, only if you turn it on, sends Claude Code's latest answer to Claude Haiku through your own Claude Code sign-in to pick terms. Your term list stays in `~/.usage/glossary.json`.
84
87
 
85
88
  ## Requirements
86
89
 
@@ -141,7 +144,7 @@ See your quota, other conversations, and background jobs without leaving Claude
141
144
 
142
145
  **How to enable**
143
146
 
144
- 1. In the usage menu bar menu (macOS) or the system-tray panel menu (Windows), check **Claude Code side pane**.
147
+ 1. In the usage menu bar menu (macOS) or the system-tray panel menu (Windows), check **Claude Code side pane**. On macOS it sits under the **Claude Code** submenu as **Side pane**.
145
148
  2. Open a new conversation or run `/reload-plugins`.
146
149
  3. At terminal widths ≥144 columns, the pane opens on the right automatically. In narrower terminals, enter `/usage-dash`.
147
150
 
@@ -230,6 +233,7 @@ If the menu bar shows `--`, it's usually not broken — there's just no local da
230
233
  | AI Update Daily | ✅ | — | — |
231
234
  | Progress Concierge & Token Saver | ✅ | — | — |
232
235
  | Token-waste Health Check | ✅ | — | — |
236
+ | Claude Code Beginner Mode & Quota-Aware Mode | ✅ | — | — |
233
237
  | Open-source license | AGPL-3.0 | MIT | — |
234
238
 
235
239
  ## When usage Isn't the Right Fit
@@ -490,6 +490,8 @@
490
490
  "claude_pane_sidecar_error": "面板設定讀取失敗:{error}",
491
491
  "menu_badge_new": "新",
492
492
  "claude_pane_menu": "Claude Code 側邊面板",
493
+ "claude_code_section": "Claude Code",
494
+ "claude_pane_section_menu": "側邊面板",
493
495
  "claude_pane_tooltip": "在 Claude Code 顯示額度、對話與背景工作",
494
496
  "claude_pane_enabled_msg": "已開啟側邊面板。開新的 Claude Code 對話(或在對話裡打 /reload-plugins)後生效;終端機寬度不夠時打 /usage-dash 叫出面板。",
495
497
  "claude_pane_fullscreen_enabled_note": "已一併開啟 Claude Code 的全螢幕介面,讓面板顯示在右側。取消勾選側邊面板時會改回。",
@@ -515,7 +517,32 @@
515
517
  "keeper_failed_line": "上次失敗 {time}:{tool}({reason})",
516
518
  "terse_codex_hook_untrusted": "Codex 還沒批准 usage 的精簡模式 hook,Codex 那邊不會生效。在 Codex 裡打 /hooks 批准它。",
517
519
  "terse_codex_hook_disabled": "你在 Codex 停用了 usage 的精簡模式 hook。在 Codex 裡打 /hooks 重新啟用。",
518
- "claude_pane_stopped": "已停止"
520
+ "claude_pane_stopped": "已停止",
521
+ "claude_beginner_menu": "新手模式",
522
+ "claude_beginner_tooltip": "幫你看懂 Claude 的回答:回答完,在輸入框上方用白話提示最多 3 個專有名詞。按 9 標成「懂了」就不再提示,之後開新對話時會出選擇題小考。會用到少量 Claude 額度。",
523
+ "claude_beginner_title": "名詞提示",
524
+ "claude_beginner_history_title": "名詞提示紀錄",
525
+ "claude_beginner_description": "開啟名詞提示紀錄",
526
+ "claude_beginner_all_known": "都懂了",
527
+ "claude_beginner_dismiss": "收起",
528
+ "claude_beginner_known": "懂了",
529
+ "claude_beginner_unknown": "還不懂",
530
+ "claude_beginner_empty": "還沒有名詞紀錄。",
531
+ "claude_beginner_opened": "已開啟名詞提示紀錄。",
532
+ "claude_beginner_quiz_title": "小考",
533
+ "claude_beginner_quiz_question": "{term} 是什麼意思?",
534
+ "claude_beginner_quiz_right": "答對了!{days} 天後再考一次。",
535
+ "claude_beginner_quiz_done": "答對了!這個詞你已經學會了。",
536
+ "claude_beginner_quiz_wrong": "答錯了。正確答案:{answer}。這個詞之後會再出現在名詞提示裡。",
537
+ "claude_beginner_enabled_msg": "已開啟新手模式。重開 Claude Code 後生效。",
538
+ "claude_beginner_disabled_msg": "已關閉新手模式。重開 Claude Code 後生效。",
539
+ "claude_beginner_action_failed": "無法切換新手模式",
540
+ "quota_aware_menu": "額度感知模式",
541
+ "quota_aware_tooltip": "額度快用完時,Claude 開大任務前會先告訴你剩多少、幾點重置,讓你選先做小的還是等重置。",
542
+ "quota_aware_enabled_msg": "已開啟額度感知模式。請重啟 Claude Code 套用。",
543
+ "quota_aware_disabled_msg": "已關閉額度感知模式。請重啟 Claude Code 套用。",
544
+ "quota_aware_action_failed": "無法切換額度感知模式。",
545
+ "claude_beginner_error": "新手模式:{error}"
519
546
  },
520
547
  "en": {
521
548
  "panel_default_name": "Default",
@@ -1008,6 +1035,8 @@
1008
1035
  "claude_pane_sidecar_error": "Cannot read pane settings: {error}",
1009
1036
  "menu_badge_new": "New",
1010
1037
  "claude_pane_menu": "Claude Code side pane",
1038
+ "claude_code_section": "Claude Code",
1039
+ "claude_pane_section_menu": "Side pane",
1011
1040
  "claude_pane_tooltip": "Show quotas, Claude sessions and background jobs in Claude Code",
1012
1041
  "claude_pane_enabled_msg": "Pane enabled. Open a new Claude Code conversation (or run /reload-plugins in a conversation). If the terminal is too narrow, run /usage-dash to open the pane.",
1013
1042
  "claude_pane_fullscreen_enabled_note": "Claude Code's fullscreen layout is now on so the pane can sit on the right. Turning the side pane off switches it back.",
@@ -1033,7 +1062,32 @@
1033
1062
  "keeper_failed_line": "Last failure {time}: {tool} ({reason})",
1034
1063
  "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.",
1035
1064
  "terse_codex_hook_disabled": "You disabled usage’s terse mode hook in Codex. Run /hooks in Codex to enable it again.",
1036
- "claude_pane_stopped": "stopped"
1065
+ "claude_pane_stopped": "stopped",
1066
+ "claude_beginner_menu": "Beginner Mode",
1067
+ "claude_beginner_tooltip": "Understand Claude’s answers: after each answer, see up to 3 technical terms explained above the prompt. Press 9 to mark them as understood so they stop appearing; a new conversation will later quiz you on them with a multiple-choice question. Uses a small amount of Claude quota.",
1068
+ "claude_beginner_title": "Terms",
1069
+ "claude_beginner_history_title": "Term history",
1070
+ "claude_beginner_description": "Open your term history",
1071
+ "claude_beginner_all_known": "All understood",
1072
+ "claude_beginner_dismiss": "Dismiss",
1073
+ "claude_beginner_known": "Understood",
1074
+ "claude_beginner_unknown": "Not yet understood",
1075
+ "claude_beginner_empty": "No terms yet.",
1076
+ "claude_beginner_opened": "Term history opened.",
1077
+ "claude_beginner_quiz_title": "Quick quiz",
1078
+ "claude_beginner_quiz_question": "What does {term} mean?",
1079
+ "claude_beginner_quiz_right": "Correct! Next quiz in {days} days.",
1080
+ "claude_beginner_quiz_done": "Correct! You have learned this term.",
1081
+ "claude_beginner_quiz_wrong": "Not quite. The answer is: {answer}. This term will show up in the hints again.",
1082
+ "claude_beginner_enabled_msg": "Beginner Mode enabled. Restart Claude Code to apply.",
1083
+ "claude_beginner_disabled_msg": "Beginner Mode disabled. Restart Claude Code to apply.",
1084
+ "claude_beginner_action_failed": "Could not change Beginner Mode",
1085
+ "quota_aware_menu": "Quota-Aware Mode",
1086
+ "quota_aware_tooltip": "When quota is running low, Claude tells you how much remains and when it resets before starting a large task, so you can choose a smaller part now or wait for the reset.",
1087
+ "quota_aware_enabled_msg": "Quota-Aware Mode enabled. Restart Claude Code to apply.",
1088
+ "quota_aware_disabled_msg": "Quota-Aware Mode disabled. Restart Claude Code to apply.",
1089
+ "quota_aware_action_failed": "Could not change Quota-Aware Mode.",
1090
+ "claude_beginner_error": "Beginner Mode: {error}"
1037
1091
  },
1038
1092
  "zh-CN": {
1039
1093
  "panel_default_name": "默认",
@@ -1526,6 +1580,8 @@
1526
1580
  "claude_pane_sidecar_error": "面板设置读取失败:{error}",
1527
1581
  "menu_badge_new": "新",
1528
1582
  "claude_pane_menu": "Claude Code 侧边面板",
1583
+ "claude_code_section": "Claude Code",
1584
+ "claude_pane_section_menu": "侧边面板",
1529
1585
  "claude_pane_tooltip": "在 Claude Code 显示额度、对话与后台任务",
1530
1586
  "claude_pane_enabled_msg": "已开启侧边面板。打开新的 Claude Code 对话(或在对话里输入 /reload-plugins)后生效;终端宽度不够时输入 /usage-dash 打开面板。",
1531
1587
  "claude_pane_fullscreen_enabled_note": "已一并开启 Claude Code 的全屏界面,让面板显示在右侧。取消勾选侧边面板时会改回。",
@@ -1551,7 +1607,32 @@
1551
1607
  "keeper_failed_line": "上次失败 {time}:{tool}({reason})",
1552
1608
  "terse_codex_hook_untrusted": "Codex 还没批准 usage 的精简模式 hook,Codex 那边不会生效。在 Codex 里输入 /hooks 批准它。",
1553
1609
  "terse_codex_hook_disabled": "你在 Codex 停用了 usage 的精简模式 hook。在 Codex 里输入 /hooks 重新启用。",
1554
- "claude_pane_stopped": "已停止"
1610
+ "claude_pane_stopped": "已停止",
1611
+ "claude_beginner_menu": "新手模式",
1612
+ "claude_beginner_tooltip": "帮你看懂 Claude 的回答:回答完,在输入框上方用白话提示最多 3 个专业术语。按 9 标成“懂了”就不再提示,之后开新对话时会出选择题小测验。会用到少量 Claude 额度。",
1613
+ "claude_beginner_title": "术语提示",
1614
+ "claude_beginner_history_title": "术语提示记录",
1615
+ "claude_beginner_description": "打开术语提示记录",
1616
+ "claude_beginner_all_known": "都懂了",
1617
+ "claude_beginner_dismiss": "收起",
1618
+ "claude_beginner_known": "懂了",
1619
+ "claude_beginner_unknown": "还不懂",
1620
+ "claude_beginner_empty": "还没有术语记录。",
1621
+ "claude_beginner_opened": "已打开术语提示记录。",
1622
+ "claude_beginner_quiz_title": "小测验",
1623
+ "claude_beginner_quiz_question": "{term} 是什么意思?",
1624
+ "claude_beginner_quiz_right": "答对了!{days} 天后再考一次。",
1625
+ "claude_beginner_quiz_done": "答对了!这个词你已经学会了。",
1626
+ "claude_beginner_quiz_wrong": "答错了。正确答案:{answer}。这个词之后会再出现在术语提示里。",
1627
+ "claude_beginner_enabled_msg": "已开启新手模式。重启 Claude Code 后生效。",
1628
+ "claude_beginner_disabled_msg": "已关闭新手模式。重启 Claude Code 后生效。",
1629
+ "claude_beginner_action_failed": "无法切换新手模式",
1630
+ "quota_aware_menu": "额度感知模式",
1631
+ "quota_aware_tooltip": "额度快用完时,Claude 开大任务前会先告诉你还剩多少、几点重置,让你选先做小的还是等重置。",
1632
+ "quota_aware_enabled_msg": "已开启额度感知模式。请重启 Claude Code 应用。",
1633
+ "quota_aware_disabled_msg": "已关闭额度感知模式。请重启 Claude Code 应用。",
1634
+ "quota_aware_action_failed": "无法切换额度感知模式。",
1635
+ "claude_beginner_error": "新手模式:{error}"
1555
1636
  },
1556
1637
  "ja": {
1557
1638
  "panel_default_name": "デフォルト",
@@ -2044,6 +2125,8 @@
2044
2125
  "claude_pane_sidecar_error": "パネル設定を読み込めません: {error}",
2045
2126
  "menu_badge_new": "新規",
2046
2127
  "claude_pane_menu": "Claude Code サイドパネル",
2128
+ "claude_code_section": "Claude Code",
2129
+ "claude_pane_section_menu": "サイドペイン",
2047
2130
  "claude_pane_tooltip": "Claude Code に使用量、セッション、バックグラウンド作業を表示",
2048
2131
  "claude_pane_enabled_msg": "サイドパネルを有効にしました。新しい Claude Code セッションを開くか、会話で /reload-plugins を実行してください。ターミナルの幅が足りない場合は /usage-dash で開けます。",
2049
2132
  "claude_pane_fullscreen_enabled_note": "Claude Code のフルスクリーンレイアウトを有効にし、パネルを右側に表示できるようにしました。サイドパネルをオフにすると元に戻ります。",
@@ -2069,7 +2152,32 @@
2069
2152
  "keeper_failed_line": "前回の失敗 {time}:{tool}({reason})",
2070
2153
  "terse_codex_hook_untrusted": "Codex が usage の簡潔モード hook をまだ承認していないため、Codex では機能しません。Codex で /hooks を入力して承認してください。",
2071
2154
  "terse_codex_hook_disabled": "Codex で usage の簡潔モード hook が無効になっています。Codex で /hooks を入力して再び有効にしてください。",
2072
- "claude_pane_stopped": "停止済み"
2155
+ "claude_pane_stopped": "停止済み",
2156
+ "claude_beginner_menu": "初心者モード",
2157
+ "claude_beginner_tooltip": "Claude の回答をわかりやすく:回答後、入力欄の上に最大3つの専門用語をやさしく説明します。9を押すと「理解した」と記録して表示しなくなり、後日、新しい会話で選択式のミニテストが出ます。Claude の使用枠を少量使います。",
2158
+ "claude_beginner_title": "用語のヒント",
2159
+ "claude_beginner_history_title": "用語の履歴",
2160
+ "claude_beginner_description": "用語の履歴を開く",
2161
+ "claude_beginner_all_known": "すべて理解した",
2162
+ "claude_beginner_dismiss": "閉じる",
2163
+ "claude_beginner_known": "理解した",
2164
+ "claude_beginner_unknown": "まだわからない",
2165
+ "claude_beginner_empty": "用語の記録はまだありません。",
2166
+ "claude_beginner_opened": "用語の履歴を開きました。",
2167
+ "claude_beginner_quiz_title": "ミニテスト",
2168
+ "claude_beginner_quiz_question": "{term} の意味は?",
2169
+ "claude_beginner_quiz_right": "正解!{days} 日後にもう一度出題します。",
2170
+ "claude_beginner_quiz_done": "正解!この用語は覚えました。",
2171
+ "claude_beginner_quiz_wrong": "不正解。正解は「{answer}」です。この用語はまた用語ヒントに表示されます。",
2172
+ "claude_beginner_enabled_msg": "初心者モードを有効にしました。Claude Code を再起動してください。",
2173
+ "claude_beginner_disabled_msg": "初心者モードを無効にしました。Claude Code を再起動してください。",
2174
+ "claude_beginner_action_failed": "初心者モードを切り替えられません",
2175
+ "quota_aware_menu": "クォータ認識モード",
2176
+ "quota_aware_tooltip": "クォータが少なくなると、大きなタスクを始める前に Claude が残量とリセット時刻を伝えます。小さな部分を先に進めるか、リセットを待つか選べます。",
2177
+ "quota_aware_enabled_msg": "クォータ認識モードを有効にしました。Claude Code を再起動してください。",
2178
+ "quota_aware_disabled_msg": "クォータ認識モードを無効にしました。Claude Code を再起動してください。",
2179
+ "quota_aware_action_failed": "クォータ認識モードを切り替えられませんでした。",
2180
+ "claude_beginner_error": "初心者モード:{error}"
2073
2181
  },
2074
2182
  "ko": {
2075
2183
  "panel_default_name": "기본",
@@ -2562,6 +2670,8 @@
2562
2670
  "claude_pane_sidecar_error": "패널 설정을 읽을 수 없습니다: {error}",
2563
2671
  "menu_badge_new": "신규",
2564
2672
  "claude_pane_menu": "Claude Code 사이드 패널",
2673
+ "claude_code_section": "Claude Code",
2674
+ "claude_pane_section_menu": "사이드 패널",
2565
2675
  "claude_pane_tooltip": "Claude Code에 사용량, 대화 및 백그라운드 작업 표시",
2566
2676
  "claude_pane_enabled_msg": "사이드 패널을 켰습니다. 새 Claude Code 대화를 열거나 대화에서 /reload-plugins를 실행하세요. 터미널이 좁으면 /usage-dash로 패널을 여세요.",
2567
2677
  "claude_pane_fullscreen_enabled_note": "Claude Code의 전체 화면 레이아웃도 켜서 패널이 오른쪽에 표시됩니다. 사이드 패널을 끄면 원래대로 돌아갑니다.",
@@ -2587,6 +2697,31 @@
2587
2697
  "keeper_failed_line": "마지막 실패 {time}: {tool} ({reason})",
2588
2698
  "terse_codex_hook_untrusted": "Codex가 usage의 간결 모드 hook을 아직 승인하지 않아 Codex에서 작동하지 않습니다. Codex에서 /hooks를 입력해 승인하세요.",
2589
2699
  "terse_codex_hook_disabled": "Codex에서 usage의 간결 모드 hook을 비활성화했습니다. Codex에서 /hooks를 입력해 다시 활성화하세요.",
2590
- "claude_pane_stopped": "중지됨"
2700
+ "claude_pane_stopped": "중지됨",
2701
+ "claude_beginner_menu": "초보자 모드",
2702
+ "claude_beginner_tooltip": "Claude의 답변을 쉽게 이해하세요. 답변 후 입력창 위에 전문 용어를 최대 3개까지 쉽게 설명합니다. 9를 누르면 이해한 것으로 기록하여 더 이상 표시하지 않고, 나중에 새 대화에서 객관식 퀴즈를 냅니다. Claude 사용량을 조금 씁니다.",
2703
+ "claude_beginner_title": "용어 안내",
2704
+ "claude_beginner_history_title": "용어 안내 기록",
2705
+ "claude_beginner_description": "용어 안내 기록 열기",
2706
+ "claude_beginner_all_known": "모두 이해했어요",
2707
+ "claude_beginner_dismiss": "닫기",
2708
+ "claude_beginner_known": "이해했어요",
2709
+ "claude_beginner_unknown": "아직 모르겠어요",
2710
+ "claude_beginner_empty": "아직 용어 기록이 없습니다.",
2711
+ "claude_beginner_opened": "용어 안내 기록을 열었습니다.",
2712
+ "claude_beginner_quiz_title": "퀴즈",
2713
+ "claude_beginner_quiz_question": "{term}의 뜻은?",
2714
+ "claude_beginner_quiz_right": "정답! {days}일 후에 다시 출제합니다.",
2715
+ "claude_beginner_quiz_done": "정답! 이 용어를 익혔어요.",
2716
+ "claude_beginner_quiz_wrong": "오답이에요. 정답: {answer}. 이 용어는 용어 힌트에 다시 나타납니다.",
2717
+ "claude_beginner_enabled_msg": "초보자 모드를 켰습니다. Claude Code를 다시 시작하세요.",
2718
+ "claude_beginner_disabled_msg": "초보자 모드를 껐습니다. Claude Code를 다시 시작하세요.",
2719
+ "claude_beginner_action_failed": "초보자 모드를 전환할 수 없습니다",
2720
+ "quota_aware_menu": "할당량 인식 모드",
2721
+ "quota_aware_tooltip": "할당량이 부족해지면 Claude가 큰 작업을 시작하기 전에 남은 양과 초기화 시간을 알려 줍니다. 작은 부분을 먼저 진행하거나 초기화를 기다릴 수 있습니다.",
2722
+ "quota_aware_enabled_msg": "할당량 인식 모드를 켰습니다. Claude Code를 다시 시작해 주세요.",
2723
+ "quota_aware_disabled_msg": "할당량 인식 모드를 껐습니다. Claude Code를 다시 시작해 주세요.",
2724
+ "quota_aware_action_failed": "할당량 인식 모드를 변경할 수 없습니다.",
2725
+ "claude_beginner_error": "초보자 모드: {error}"
2591
2726
  }
2592
2727
  }
@@ -33,10 +33,18 @@ def _remove_path(value: str, path: str) -> str:
33
33
  return os.pathsep.join(parts) if any(parts) else ""
34
34
 
35
35
 
36
- def _resolve_source() -> Path:
36
+ def _install_dir(mod: str) -> Path:
37
+ return INSTALL_DIR.with_name("usage-beginner") if mod == "usage-beginner" else INSTALL_DIR
38
+
39
+
40
+ def _sidecar_name(mod: str) -> str:
41
+ return "usage-beginner.json" if mod == "usage-beginner" else "usage-pane.json"
42
+
43
+
44
+ def _resolve_source(mod: str = "usage-dash") -> Path:
37
45
  paths = [
38
- Path(__file__).resolve().parent.parent / "claude_pane" / "usage-dash",
39
- Path(sys.executable).resolve().parent.parent / "Resources" / "claude_pane" / "usage-dash",
46
+ Path(__file__).resolve().parent.parent / "claude_pane" / mod,
47
+ Path(sys.executable).resolve().parent.parent / "Resources" / "claude_pane" / mod,
40
48
  ]
41
49
  for path in paths:
42
50
  if path.is_dir():
@@ -72,7 +80,7 @@ def _status_argv() -> list[str]:
72
80
  ]
73
81
 
74
82
 
75
- def _sidecar_text() -> str:
83
+ def _sidecar_text(mod: str = "usage-dash") -> str:
76
84
  from i18n import I18N_PATH
77
85
 
78
86
  bundle = json.loads(I18N_PATH.read_text(encoding="utf-8"))
@@ -81,30 +89,36 @@ def _sidecar_text() -> str:
81
89
  strings = {
82
90
  key: table.get(key) or value
83
91
  for key, value in english.items()
84
- if key.startswith("claude_pane_")
92
+ if key.startswith("claude_beginner_" if mod == "usage-beginner" else "claude_pane_")
85
93
  }
86
94
  return (
87
95
  json.dumps(
88
- {"strings": strings, "status_argv": _status_argv()}, ensure_ascii=False, indent=2
96
+ (
97
+ {"strings": strings, "lang": detect_lang()}
98
+ if mod == "usage-beginner"
99
+ else {"strings": strings, "status_argv": _status_argv()}
100
+ ),
101
+ ensure_ascii=False,
102
+ indent=2,
89
103
  )
90
104
  + "\n"
91
105
  )
92
106
 
93
107
 
94
- def _write_sidecar() -> None:
95
- setup_hook._atomic_write_text(INSTALL_DIR / "usage-pane.json", _sidecar_text())
108
+ def _write_sidecar(mod: str = "usage-dash") -> None:
109
+ setup_hook._atomic_write_text(_install_dir(mod) / _sidecar_name(mod), _sidecar_text(mod))
96
110
 
97
111
 
98
- def _copy_pane() -> None:
99
- source = _resolve_source()
100
- if INSTALL_DIR.exists():
101
- shutil.rmtree(INSTALL_DIR)
102
- shutil.copytree(source, INSTALL_DIR, ignore=shutil.ignore_patterns("*.test.ts"))
103
- _write_sidecar()
112
+ def _copy_pane(mod: str = "usage-dash") -> None:
113
+ source = _resolve_source(mod)
114
+ if _install_dir(mod).exists():
115
+ shutil.rmtree(_install_dir(mod))
116
+ shutil.copytree(source, _install_dir(mod), ignore=shutil.ignore_patterns("*.test.ts"))
117
+ _write_sidecar() if mod == "usage-dash" else _write_sidecar(mod)
104
118
 
105
119
 
106
- def _installed_is_current() -> bool:
107
- source = _resolve_source()
120
+ def _installed_is_current(mod: str = "usage-dash") -> bool:
121
+ source = _resolve_source(mod)
108
122
  shipped = {
109
123
  path.relative_to(source)
110
124
  for path in source.rglob("*")
@@ -113,16 +127,20 @@ def _installed_is_current() -> bool:
113
127
  # Claude Code writes its own type stubs under .claude-plugin/types; they are not ours.
114
128
  generated = Path(".claude-plugin", "types")
115
129
  installed = {
116
- path.relative_to(INSTALL_DIR)
117
- for path in INSTALL_DIR.rglob("*")
118
- if path.is_file() and generated not in path.relative_to(INSTALL_DIR).parents
119
- } - {Path("usage-pane.json")}
130
+ path.relative_to(_install_dir(mod))
131
+ for path in _install_dir(mod).rglob("*")
132
+ if path.is_file() and generated not in path.relative_to(_install_dir(mod)).parents
133
+ } - {Path(_sidecar_name(mod))}
120
134
  if shipped != installed:
121
135
  return False
122
- if not all(filecmp.cmp(source / path, INSTALL_DIR / path, shallow=False) for path in shipped):
136
+ if not all(
137
+ filecmp.cmp(source / path, _install_dir(mod) / path, shallow=False) for path in shipped
138
+ ):
123
139
  return False
124
140
  try:
125
- return (INSTALL_DIR / "usage-pane.json").read_text(encoding="utf-8") == _sidecar_text()
141
+ return (_install_dir(mod) / _sidecar_name(mod)).read_text(
142
+ encoding="utf-8"
143
+ ) == _sidecar_text(mod)
126
144
  except OSError:
127
145
  return False
128
146
 
@@ -134,11 +152,13 @@ def _env(settings: dict[str, object]) -> dict[str, str]:
134
152
  return env
135
153
 
136
154
 
137
- def enable_claude_pane() -> int:
138
- _copy_pane()
155
+ def enable_claude_pane(mod: str = "usage-dash") -> int:
156
+ _copy_pane() if mod == "usage-dash" else _copy_pane(mod)
139
157
  settings = setup_hook._load_settings()
140
158
  env = _env(settings)
141
- env[PLUGIN_DIRS_KEY] = _add_path(env.get(PLUGIN_DIRS_KEY, ""), str(INSTALL_DIR.absolute()))
159
+ env[PLUGIN_DIRS_KEY] = _add_path(
160
+ env.get(PLUGIN_DIRS_KEY, ""), str(_install_dir(mod).absolute())
161
+ )
142
162
  settings["env"] = env
143
163
  setup_hook._save_settings(settings)
144
164
  return 0
@@ -159,12 +179,12 @@ def enable_fullscreen_layout() -> None:
159
179
  setup_hook._save_settings(settings)
160
180
 
161
181
 
162
- def disable_claude_pane() -> int:
182
+ def disable_claude_pane(mod: str = "usage-dash") -> int:
163
183
  settings = setup_hook._load_settings()
164
184
  env = _env(settings)
165
185
  changed = False
166
186
  if PLUGIN_DIRS_KEY in env:
167
- value = _remove_path(env[PLUGIN_DIRS_KEY], str(INSTALL_DIR.absolute()))
187
+ value = _remove_path(env[PLUGIN_DIRS_KEY], str(_install_dir(mod).absolute()))
168
188
  if value:
169
189
  env[PLUGIN_DIRS_KEY] = value
170
190
  else:
@@ -173,7 +193,11 @@ def disable_claude_pane() -> int:
173
193
  settings.pop("env", None)
174
194
  changed = True
175
195
  usage = settings.get("usage")
176
- if isinstance(usage, dict) and usage.get("claudePaneSetFullscreen") is True:
196
+ if (
197
+ mod == "usage-dash"
198
+ and isinstance(usage, dict)
199
+ and usage.get("claudePaneSetFullscreen") is True
200
+ ):
177
201
  if settings.get("tui") == "fullscreen":
178
202
  settings.pop("tui")
179
203
  usage.pop("claudePaneSetFullscreen")
@@ -182,18 +206,19 @@ def disable_claude_pane() -> int:
182
206
  changed = True
183
207
  if changed:
184
208
  setup_hook._save_settings(settings)
185
- if INSTALL_DIR.exists():
186
- shutil.rmtree(INSTALL_DIR)
209
+ if _install_dir(mod).exists():
210
+ shutil.rmtree(_install_dir(mod))
187
211
  return 0
188
212
 
189
213
 
190
- def is_claude_pane_enabled() -> bool:
214
+ def is_claude_pane_enabled(mod: str = "usage-dash") -> bool:
191
215
  try:
192
216
  value = _env(setup_hook._load_settings()).get(PLUGIN_DIRS_KEY, "")
193
217
  except SystemExit:
194
218
  return False
195
- return INSTALL_DIR.is_dir() and any(
196
- part and _same_path(part, str(INSTALL_DIR.absolute())) for part in value.split(os.pathsep)
219
+ return _install_dir(mod).is_dir() and any(
220
+ part and _same_path(part, str(_install_dir(mod).absolute()))
221
+ for part in value.split(os.pathsep)
197
222
  )
198
223
 
199
224
 
@@ -201,3 +226,17 @@ def refresh_claude_pane() -> None:
201
226
  """Re-copy an enabled pane whose files differ from this build's, e.g. after an app upgrade."""
202
227
  if is_claude_pane_enabled() and not _installed_is_current():
203
228
  _copy_pane()
229
+ if is_claude_beginner_enabled() and not _installed_is_current("usage-beginner"):
230
+ _copy_pane("usage-beginner")
231
+
232
+
233
+ def enable_claude_beginner() -> int:
234
+ return enable_claude_pane("usage-beginner")
235
+
236
+
237
+ def disable_claude_beginner() -> int:
238
+ return disable_claude_pane("usage-beginner")
239
+
240
+
241
+ def is_claude_beginner_enabled() -> bool:
242
+ return is_claude_pane_enabled("usage-beginner")
@@ -92,6 +92,12 @@ TERSE_REMINDER_HOOK_VERSION = "1.2"
92
92
  TERSE_REMINDER_MATCHER = ""
93
93
  _TERSE_REMINDER_MARKER = "usage-terse-reminder"
94
94
  _TERSE_REMINDER_MARKERS = (_TERSE_REMINDER_MARKER, "usage_terse_reminder")
95
+ # Independent, opt-in Claude Code UserPromptSubmit hook.
96
+ QUOTA_AWARE_HOOK_TARGET = Path(os.path.expanduser("~/.claude/usage-quota-aware.py"))
97
+ QUOTA_AWARE_HOOK_VERSION = "1.0"
98
+ QUOTA_AWARE_MATCHER = ""
99
+ _QUOTA_AWARE_MARKER = "usage-quota-aware"
100
+ _QUOTA_AWARE_MARKERS = (_QUOTA_AWARE_MARKER, "usage_quota_aware")
95
101
 
96
102
 
97
103
  def _migrate_bundled_python_commands_if_needed(
@@ -1252,9 +1258,119 @@ def self_heal() -> None:
1252
1258
  raise
1253
1259
  _debug_self_heal_failure("resume_hook", exc)
1254
1260
 
1261
+ try:
1262
+ _self_heal_quota_aware()
1263
+ except BaseException as exc:
1264
+ if isinstance(exc, KeyboardInterrupt):
1265
+ raise
1266
+ _debug_self_heal_failure("quota_aware_hook", exc)
1267
+
1255
1268
  try:
1256
1269
  _self_heal_terse_mode()
1257
1270
  except BaseException as exc:
1258
1271
  if isinstance(exc, KeyboardInterrupt):
1259
1272
  raise
1260
1273
  _debug_self_heal_failure("terse_hook", exc)
1274
+
1275
+
1276
+ def _resolve_quota_aware_source() -> Path:
1277
+ paths = [
1278
+ Path(__file__).resolve().parent.parent / "usage_quota_aware.py",
1279
+ Path(sys.executable).resolve().parent.parent / "Resources" / "usage_quota_aware.py",
1280
+ ]
1281
+ for path in paths:
1282
+ if path.exists():
1283
+ return path
1284
+ raise SystemExit(_t("setup_hook_source_missing", tried=", ".join(map(str, paths))))
1285
+
1286
+
1287
+ def _quota_aware_command() -> str:
1288
+ return f"{_shell_arg(_find_system_python())} {_shell_arg(str(QUOTA_AWARE_HOOK_TARGET))}"
1289
+
1290
+
1291
+ def _copy_quota_aware_script() -> None:
1292
+ QUOTA_AWARE_HOOK_TARGET.parent.mkdir(parents=True, exist_ok=True)
1293
+ shutil.copyfile(_resolve_quota_aware_source(), QUOTA_AWARE_HOOK_TARGET)
1294
+ QUOTA_AWARE_HOOK_TARGET.chmod(
1295
+ QUOTA_AWARE_HOOK_TARGET.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
1296
+ )
1297
+
1298
+
1299
+ def is_quota_aware_enabled() -> bool:
1300
+ try:
1301
+ entries = _user_prompt_submit_list(_load_settings())
1302
+ except SystemExit:
1303
+ return False
1304
+ return any(_is_hook_entry(entry, _QUOTA_AWARE_MARKERS) for entry in entries or [])
1305
+
1306
+
1307
+ def enable_quota_aware() -> int:
1308
+ if not setup_hook._claude_settings_dir_exists():
1309
+ print(_t("setup_no_agents"), file=sys.stderr)
1310
+ return 1
1311
+ _copy_quota_aware_script()
1312
+ settings = _load_settings()
1313
+ hooks = settings.get("hooks")
1314
+ if not isinstance(hooks, dict):
1315
+ hooks = {}
1316
+ settings["hooks"] = hooks
1317
+ entries = _user_prompt_submit_list(settings) or []
1318
+ hooks["UserPromptSubmit"] = [
1319
+ kept
1320
+ for entry in entries
1321
+ if (kept := _strip_hook_entries(entry, _QUOTA_AWARE_MARKERS)) is not None
1322
+ ] + [
1323
+ {
1324
+ "matcher": QUOTA_AWARE_MATCHER,
1325
+ "hooks": [{"type": "command", "command": _quota_aware_command()}],
1326
+ }
1327
+ ]
1328
+ _save_settings(settings)
1329
+ print(_t("quota_aware_enabled_msg"))
1330
+ print(_t("setup_claude_restart_required"))
1331
+ return 0
1332
+
1333
+
1334
+ def disable_quota_aware() -> int:
1335
+ settings = _load_settings()
1336
+ entries = _user_prompt_submit_list(settings)
1337
+ if entries is not None:
1338
+ kept = [
1339
+ item
1340
+ for entry in entries
1341
+ if (item := _strip_hook_entries(entry, _QUOTA_AWARE_MARKERS)) is not None
1342
+ ]
1343
+ if kept != entries:
1344
+ hooks = settings["hooks"]
1345
+ if kept:
1346
+ hooks["UserPromptSubmit"] = kept
1347
+ else:
1348
+ hooks.pop("UserPromptSubmit", None)
1349
+ if not hooks:
1350
+ settings.pop("hooks", None)
1351
+ _save_settings(settings)
1352
+ if QUOTA_AWARE_HOOK_TARGET.exists():
1353
+ QUOTA_AWARE_HOOK_TARGET.unlink()
1354
+ print(_t("quota_aware_disabled_msg"))
1355
+ return 0
1356
+
1357
+
1358
+ def _installed_quota_aware_version() -> str | None:
1359
+ try:
1360
+ for line in QUOTA_AWARE_HOOK_TARGET.read_text(encoding="utf-8").splitlines():
1361
+ if line.startswith("__version__"):
1362
+ return line.partition("=")[2].strip().strip("\"'")
1363
+ except (OSError, UnicodeError):
1364
+ pass
1365
+ return None
1366
+
1367
+
1368
+ def _self_heal_quota_aware() -> None:
1369
+ if not is_quota_aware_enabled():
1370
+ return
1371
+ old = _installed_quota_aware_version()
1372
+ if old != QUOTA_AWARE_HOOK_VERSION:
1373
+ _copy_quota_aware_script()
1374
+ _append_self_heal_log(
1375
+ "update_quota_aware_hook", f"{old or 'unknown'} -> {QUOTA_AWARE_HOOK_VERSION}"
1376
+ )