usage-cli 0.32.5__tar.gz → 0.32.6__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 (248) hide show
  1. {usage_cli-0.32.5 → usage_cli-0.32.6}/PKG-INFO +1 -1
  2. {usage_cli-0.32.5 → usage_cli-0.32.6}/i18n.json +25 -5
  3. {usage_cli-0.32.5 → usage_cli-0.32.6}/installer/session_hooks.py +1 -1
  4. {usage_cli-0.32.5 → usage_cli-0.32.6}/installer/setup_hook.py +1 -1
  5. {usage_cli-0.32.5 → usage_cli-0.32.6}/main.py +9 -2
  6. {usage_cli-0.32.5 → usage_cli-0.32.6}/menubar/app.py +9 -20
  7. {usage_cli-0.32.5 → usage_cli-0.32.6}/menubar/update.py +26 -0
  8. usage_cli-0.32.6/menubar/whats_new.py +39 -0
  9. {usage_cli-0.32.5 → usage_cli-0.32.6}/pyproject.toml +2 -2
  10. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_main.py +1 -0
  11. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_update_release_notes.py +30 -1
  12. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_usage_statusline.py +285 -1
  13. usage_cli-0.32.6/tests/test_whats_new.py +345 -0
  14. {usage_cli-0.32.5 → usage_cli-0.32.6}/updates/release_notes.py +40 -0
  15. usage_cli-0.32.6/updates/whats_new.py +121 -0
  16. {usage_cli-0.32.5 → usage_cli-0.32.6}/usage_cli.egg-info/PKG-INFO +1 -1
  17. {usage_cli-0.32.5 → usage_cli-0.32.6}/usage_cli.egg-info/SOURCES.txt +5 -1
  18. {usage_cli-0.32.5 → usage_cli-0.32.6}/usage_statusline.py +204 -3
  19. {usage_cli-0.32.5 → usage_cli-0.32.6}/usage_terse_mode.py +24 -6
  20. {usage_cli-0.32.5 → usage_cli-0.32.6}/wintray/app.py +11 -3
  21. usage_cli-0.32.6/wintray/whats_new.py +48 -0
  22. {usage_cli-0.32.5 → usage_cli-0.32.6}/LICENSE +0 -0
  23. {usage_cli-0.32.5 → usage_cli-0.32.6}/README.md +0 -0
  24. {usage_cli-0.32.5 → usage_cli-0.32.6}/THIRD_PARTY_NOTICES.md +0 -0
  25. {usage_cli-0.32.5 → usage_cli-0.32.6}/adapters/__init__.py +0 -0
  26. {usage_cli-0.32.5 → usage_cli-0.32.6}/adapters/agy.py +0 -0
  27. {usage_cli-0.32.5 → usage_cli-0.32.6}/adapters/claude.py +0 -0
  28. {usage_cli-0.32.5 → usage_cli-0.32.6}/adapters/codex.py +0 -0
  29. {usage_cli-0.32.5 → usage_cli-0.32.6}/adapters/grok.py +0 -0
  30. {usage_cli-0.32.5 → usage_cli-0.32.6}/adapters/muse.py +0 -0
  31. {usage_cli-0.32.5 → usage_cli-0.32.6}/adapters/rate_limits.py +0 -0
  32. {usage_cli-0.32.5 → usage_cli-0.32.6}/adapters/registry.py +0 -0
  33. {usage_cli-0.32.5 → usage_cli-0.32.6}/adapters/types.py +0 -0
  34. {usage_cli-0.32.5 → usage_cli-0.32.6}/analyzer/__init__.py +0 -0
  35. {usage_cli-0.32.5 → usage_cli-0.32.6}/analyzer/aggregator.py +0 -0
  36. {usage_cli-0.32.5 → usage_cli-0.32.6}/analyzer/blocks.py +0 -0
  37. {usage_cli-0.32.5 → usage_cli-0.32.6}/analyzer/diagnoser.py +0 -0
  38. {usage_cli-0.32.5 → usage_cli-0.32.6}/analyzer/dispatch_ledger.py +0 -0
  39. {usage_cli-0.32.5 → usage_cli-0.32.6}/analyzer/insights.py +0 -0
  40. {usage_cli-0.32.5 → usage_cli-0.32.6}/analyzer/persona_loader.py +0 -0
  41. {usage_cli-0.32.5 → usage_cli-0.32.6}/analyzer/reporter.py +0 -0
  42. {usage_cli-0.32.5 → usage_cli-0.32.6}/analyzer/subscription.py +0 -0
  43. {usage_cli-0.32.5 → usage_cli-0.32.6}/analyzer/usage_snapshot.py +0 -0
  44. {usage_cli-0.32.5 → usage_cli-0.32.6}/doctor.py +0 -0
  45. {usage_cli-0.32.5 → usage_cli-0.32.6}/fsevents_watch.py +0 -0
  46. {usage_cli-0.32.5 → usage_cli-0.32.6}/i18n.py +0 -0
  47. {usage_cli-0.32.5 → usage_cli-0.32.6}/installer/__init__.py +0 -0
  48. {usage_cli-0.32.5 → usage_cli-0.32.6}/installer/claude_pane.py +0 -0
  49. {usage_cli-0.32.5 → usage_cli-0.32.6}/installer/codex_hook_trust.py +0 -0
  50. {usage_cli-0.32.5 → usage_cli-0.32.6}/installer/login_item.py +0 -0
  51. {usage_cli-0.32.5 → usage_cli-0.32.6}/installer/statusline_settings.py +0 -0
  52. {usage_cli-0.32.5 → usage_cli-0.32.6}/loaders/__init__.py +0 -0
  53. {usage_cli-0.32.5 → usage_cli-0.32.6}/loaders/agy_disk_cache.py +0 -0
  54. {usage_cli-0.32.5 → usage_cli-0.32.6}/loaders/agy_loader.py +0 -0
  55. {usage_cli-0.32.5 → usage_cli-0.32.6}/loaders/agy_quota_probe.py +0 -0
  56. {usage_cli-0.32.5 → usage_cli-0.32.6}/loaders/cache_quarantine.py +0 -0
  57. {usage_cli-0.32.5 → usage_cli-0.32.6}/loaders/claude_paths.py +0 -0
  58. {usage_cli-0.32.5 → usage_cli-0.32.6}/loaders/codex_disk_cache.py +0 -0
  59. {usage_cli-0.32.5 → usage_cli-0.32.6}/loaders/codex_events.py +0 -0
  60. {usage_cli-0.32.5 → usage_cli-0.32.6}/loaders/codex_fork_replay.py +0 -0
  61. {usage_cli-0.32.5 → usage_cli-0.32.6}/loaders/codex_loader.py +0 -0
  62. {usage_cli-0.32.5 → usage_cli-0.32.6}/loaders/codex_paths.py +0 -0
  63. {usage_cli-0.32.5 → usage_cli-0.32.6}/loaders/disk_cache_common.py +0 -0
  64. {usage_cli-0.32.5 → usage_cli-0.32.6}/loaders/disk_cache_lifecycle.py +0 -0
  65. {usage_cli-0.32.5 → usage_cli-0.32.6}/loaders/grok_loader.py +0 -0
  66. {usage_cli-0.32.5 → usage_cli-0.32.6}/loaders/grok_quota_probe.py +0 -0
  67. {usage_cli-0.32.5 → usage_cli-0.32.6}/loaders/history_disk_cache.py +0 -0
  68. {usage_cli-0.32.5 → usage_cli-0.32.6}/loaders/history_loader.py +0 -0
  69. {usage_cli-0.32.5 → usage_cli-0.32.6}/loaders/jsonl_limits.py +0 -0
  70. {usage_cli-0.32.5 → usage_cli-0.32.6}/loaders/jsonl_utils.py +0 -0
  71. {usage_cli-0.32.5 → usage_cli-0.32.6}/loaders/muse_loader.py +0 -0
  72. {usage_cli-0.32.5 → usage_cli-0.32.6}/menubar/__init__.py +0 -0
  73. {usage_cli-0.32.5 → usage_cli-0.32.6}/menubar/actions.py +0 -0
  74. {usage_cli-0.32.5 → usage_cli-0.32.6}/menubar/agy.py +0 -0
  75. {usage_cli-0.32.5 → usage_cli-0.32.6}/menubar/chrome.py +0 -0
  76. {usage_cli-0.32.5 → usage_cli-0.32.6}/menubar/grok.py +0 -0
  77. {usage_cli-0.32.5 → usage_cli-0.32.6}/menubar/manual_refresh.py +0 -0
  78. {usage_cli-0.32.5 → usage_cli-0.32.6}/menubar/menu.py +0 -0
  79. {usage_cli-0.32.5 → usage_cli-0.32.6}/menubar/new_badge.py +0 -0
  80. {usage_cli-0.32.5 → usage_cli-0.32.6}/menubar/notify.py +0 -0
  81. {usage_cli-0.32.5 → usage_cli-0.32.6}/menubar/popover.py +0 -0
  82. {usage_cli-0.32.5 → usage_cli-0.32.6}/menubar/prefs.py +0 -0
  83. {usage_cli-0.32.5 → usage_cli-0.32.6}/menubar/refresh.py +0 -0
  84. {usage_cli-0.32.5 → usage_cli-0.32.6}/menubar/state.py +0 -0
  85. {usage_cli-0.32.5 → usage_cli-0.32.6}/menubar/title.py +0 -0
  86. {usage_cli-0.32.5 → usage_cli-0.32.6}/panels/__init__.py +0 -0
  87. {usage_cli-0.32.5 → usage_cli-0.32.6}/panels/base.py +0 -0
  88. {usage_cli-0.32.5 → usage_cli-0.32.6}/panels/dynamic_height.py +0 -0
  89. {usage_cli-0.32.5 → usage_cli-0.32.6}/panels/panel_scale.py +0 -0
  90. {usage_cli-0.32.5 → usage_cli-0.32.6}/panels/panel_window.py +0 -0
  91. {usage_cli-0.32.5 → usage_cli-0.32.6}/panels/panel_window_state.py +0 -0
  92. {usage_cli-0.32.5 → usage_cli-0.32.6}/panels/payload.py +0 -0
  93. {usage_cli-0.32.5 → usage_cli-0.32.6}/panels/web_panel.py +0 -0
  94. {usage_cli-0.32.5 → usage_cli-0.32.6}/panels/window_drag.py +0 -0
  95. {usage_cli-0.32.5 → usage_cli-0.32.6}/prefs.py +0 -0
  96. {usage_cli-0.32.5 → usage_cli-0.32.6}/pricing.py +0 -0
  97. {usage_cli-0.32.5 → usage_cli-0.32.6}/project_resolver.py +0 -0
  98. {usage_cli-0.32.5 → usage_cli-0.32.6}/quota/__init__.py +0 -0
  99. {usage_cli-0.32.5 → usage_cli-0.32.6}/quota/agy_window_keeper.py +0 -0
  100. {usage_cli-0.32.5 → usage_cli-0.32.6}/quota/burn_rate.py +0 -0
  101. {usage_cli-0.32.5 → usage_cli-0.32.6}/quota/codex_window_keeper.py +0 -0
  102. {usage_cli-0.32.5 → usage_cli-0.32.6}/quota/keeper_outcome.py +0 -0
  103. {usage_cli-0.32.5 → usage_cli-0.32.6}/quota/usage_rate.py +0 -0
  104. {usage_cli-0.32.5 → usage_cli-0.32.6}/quota/window_keeper.py +0 -0
  105. {usage_cli-0.32.5 → usage_cli-0.32.6}/service_status.py +0 -0
  106. {usage_cli-0.32.5 → usage_cli-0.32.6}/setup.cfg +0 -0
  107. {usage_cli-0.32.5 → usage_cli-0.32.6}/setup_app.py +0 -0
  108. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_adapters_claude.py +0 -0
  109. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_adapters_codex.py +0 -0
  110. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_adapters_rate_limits.py +0 -0
  111. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_aggregator.py +0 -0
  112. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_agy_adapter.py +0 -0
  113. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_agy_disk_cache.py +0 -0
  114. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_agy_loader.py +0 -0
  115. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_agy_quota_probe.py +0 -0
  116. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_agy_window_keeper.py +0 -0
  117. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_analyzer_blocks.py +0 -0
  118. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_analyzer_pipeline.py +0 -0
  119. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_burn_rate.py +0 -0
  120. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_cache_quarantine.py +0 -0
  121. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_check_upstream.py +0 -0
  122. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_claude_pane.py +0 -0
  123. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_claude_paths.py +0 -0
  124. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_codex_consistency.py +0 -0
  125. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_codex_hook_trust.py +0 -0
  126. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_codex_loader.py +0 -0
  127. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_codex_window_keeper.py +0 -0
  128. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_diagnoser.py +0 -0
  129. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_disk_cache_shards.py +0 -0
  130. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_dispatch_ledger.py +0 -0
  131. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_doctor.py +0 -0
  132. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_dynamic_height.py +0 -0
  133. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_fsevents_watch.py +0 -0
  134. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_grok_loader.py +0 -0
  135. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_grok_quota_probe.py +0 -0
  136. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_history_loader.py +0 -0
  137. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_hook_scripts_are_standalone.py +0 -0
  138. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_html_report_snapshot.py +0 -0
  139. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_i18n_format_fallback.py +0 -0
  140. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_i18n_key_parity.py +0 -0
  141. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_i18n_packaged_path.py +0 -0
  142. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_insights.py +0 -0
  143. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_jsonl_utils.py +0 -0
  144. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_keeper_menu.py +0 -0
  145. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_keeper_outcome.py +0 -0
  146. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_login_item.py +0 -0
  147. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_manual_refresh.py +0 -0
  148. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_menubar.py +0 -0
  149. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_menubar_agy.py +0 -0
  150. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_menubar_grok.py +0 -0
  151. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_menubar_notify.py +0 -0
  152. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_menubar_prefs.py +0 -0
  153. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_menubar_state.py +0 -0
  154. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_menubar_update.py +0 -0
  155. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_muse_loader.py +0 -0
  156. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_new_badge.py +0 -0
  157. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_packaged_resources.py +0 -0
  158. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_packaging.py +0 -0
  159. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_panel_footer_layout.py +0 -0
  160. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_panel_scale.py +0 -0
  161. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_panel_window_state.py +0 -0
  162. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_panels.py +0 -0
  163. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_panels_lazy_import.py +0 -0
  164. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_persona_loader.py +0 -0
  165. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_pricing.py +0 -0
  166. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_project_resolver.py +0 -0
  167. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_quota_label.py +0 -0
  168. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_report_daily_chart.py +0 -0
  169. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_report_filter_js.py +0 -0
  170. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_reporter_build_data.py +0 -0
  171. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_service_status.py +0 -0
  172. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_session_resume.py +0 -0
  173. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_setup_app.py +0 -0
  174. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_setup_hook.py +0 -0
  175. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_setup_hook_coexistence.py +0 -0
  176. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_setup_hook_resume.py +0 -0
  177. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_setup_hook_terse.py +0 -0
  178. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_subprocess_utils.py +0 -0
  179. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_subscription.py +0 -0
  180. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_time_utils.py +0 -0
  181. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_tui.py +0 -0
  182. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_tui_sprite.py +0 -0
  183. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_ui_tables.py +0 -0
  184. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_update_checker.py +0 -0
  185. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_update_gate.py +0 -0
  186. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_usage_cli.py +0 -0
  187. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_usage_client.py +0 -0
  188. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_usage_diagnosis_snapshot.py +0 -0
  189. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_usage_dir_sweeper.py +0 -0
  190. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_usage_lang.py +0 -0
  191. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_usage_logging.py +0 -0
  192. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_usage_notifications.py +0 -0
  193. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_usage_rate.py +0 -0
  194. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_usage_snapshot.py +0 -0
  195. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_usage_snapshot_replay.py +0 -0
  196. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_usage_statusline_agy.py +0 -0
  197. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_usage_statusline_forwarder.py +0 -0
  198. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_usage_statusline_grok.py +0 -0
  199. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_usage_terse_mode.py +0 -0
  200. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_usage_terse_reminder.py +0 -0
  201. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_web_panel_payload.py +0 -0
  202. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_wheel_packaging.py +0 -0
  203. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_win_login_item.py +0 -0
  204. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_window_keeper.py +0 -0
  205. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_windows_manifest.py +0 -0
  206. {usage_cli-0.32.5 → usage_cli-0.32.6}/tests/test_wintray.py +0 -0
  207. {usage_cli-0.32.5 → usage_cli-0.32.6}/tui/__init__.py +0 -0
  208. {usage_cli-0.32.5 → usage_cli-0.32.6}/tui/app.py +0 -0
  209. {usage_cli-0.32.5 → usage_cli-0.32.6}/tui/sprite.py +0 -0
  210. {usage_cli-0.32.5 → usage_cli-0.32.6}/ui/__init__.py +0 -0
  211. {usage_cli-0.32.5 → usage_cli-0.32.6}/ui/html_report.py +0 -0
  212. {usage_cli-0.32.5 → usage_cli-0.32.6}/ui/report_charts.py +0 -0
  213. {usage_cli-0.32.5 → usage_cli-0.32.6}/ui/report_daily_chart.py +0 -0
  214. {usage_cli-0.32.5 → usage_cli-0.32.6}/ui/report_filter.py +0 -0
  215. {usage_cli-0.32.5 → usage_cli-0.32.6}/ui/report_ledger.py +0 -0
  216. {usage_cli-0.32.5 → usage_cli-0.32.6}/ui/report_scripts.py +0 -0
  217. {usage_cli-0.32.5 → usage_cli-0.32.6}/ui/report_styles.py +0 -0
  218. {usage_cli-0.32.5 → usage_cli-0.32.6}/ui/tables.py +0 -0
  219. {usage_cli-0.32.5 → usage_cli-0.32.6}/updates/__init__.py +0 -0
  220. {usage_cli-0.32.5 → usage_cli-0.32.6}/updates/checker.py +0 -0
  221. {usage_cli-0.32.5 → usage_cli-0.32.6}/updates/gate.py +0 -0
  222. {usage_cli-0.32.5 → usage_cli-0.32.6}/usage_cli.egg-info/dependency_links.txt +0 -0
  223. {usage_cli-0.32.5 → usage_cli-0.32.6}/usage_cli.egg-info/entry_points.txt +0 -0
  224. {usage_cli-0.32.5 → usage_cli-0.32.6}/usage_cli.egg-info/requires.txt +0 -0
  225. {usage_cli-0.32.5 → usage_cli-0.32.6}/usage_cli.egg-info/top_level.txt +0 -0
  226. {usage_cli-0.32.5 → usage_cli-0.32.6}/usage_cli.py +0 -0
  227. {usage_cli-0.32.5 → usage_cli-0.32.6}/usage_client.py +0 -0
  228. {usage_cli-0.32.5 → usage_cli-0.32.6}/usage_common/__init__.py +0 -0
  229. {usage_cli-0.32.5 → usage_cli-0.32.6}/usage_common/subprocess_utils.py +0 -0
  230. {usage_cli-0.32.5 → usage_cli-0.32.6}/usage_common/time_utils.py +0 -0
  231. {usage_cli-0.32.5 → usage_cli-0.32.6}/usage_common/usage_dir_sweeper.py +0 -0
  232. {usage_cli-0.32.5 → usage_cli-0.32.6}/usage_common/usage_lang.py +0 -0
  233. {usage_cli-0.32.5 → usage_cli-0.32.6}/usage_common/usage_logging.py +0 -0
  234. {usage_cli-0.32.5 → usage_cli-0.32.6}/usage_diagnosis_snapshot.py +0 -0
  235. {usage_cli-0.32.5 → usage_cli-0.32.6}/usage_notifications.py +0 -0
  236. {usage_cli-0.32.5 → usage_cli-0.32.6}/usage_session_resume.py +0 -0
  237. {usage_cli-0.32.5 → usage_cli-0.32.6}/usage_statusline_agy.py +0 -0
  238. {usage_cli-0.32.5 → usage_cli-0.32.6}/usage_statusline_forwarder.py +0 -0
  239. {usage_cli-0.32.5 → usage_cli-0.32.6}/usage_statusline_grok.py +0 -0
  240. {usage_cli-0.32.5 → usage_cli-0.32.6}/usage_terse_reminder.py +0 -0
  241. {usage_cli-0.32.5 → usage_cli-0.32.6}/wintray/__init__.py +0 -0
  242. {usage_cli-0.32.5 → usage_cli-0.32.6}/wintray/login_item.py +0 -0
  243. {usage_cli-0.32.5 → usage_cli-0.32.6}/wintray/menu.py +0 -0
  244. {usage_cli-0.32.5 → usage_cli-0.32.6}/wintray/prefs.py +0 -0
  245. {usage_cli-0.32.5 → usage_cli-0.32.6}/wintray/quota_label.py +0 -0
  246. {usage_cli-0.32.5 → usage_cli-0.32.6}/wintray/taskbar_obstacles.py +0 -0
  247. {usage_cli-0.32.5 → usage_cli-0.32.6}/wintray/taskbar_overlay.py +0 -0
  248. {usage_cli-0.32.5 → usage_cli-0.32.6}/wintray/watch.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: usage-cli
3
- Version: 0.32.5
3
+ Version: 0.32.6
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
@@ -42,6 +42,10 @@
42
42
  "update_btn_download": "前往下載",
43
43
  "update_btn_later": "稍後再說",
44
44
  "update_btn_skip": "跳過此版本",
45
+ "whats_new_title": "usage {version} 更新內容",
46
+ "whats_new_ok": "好",
47
+ "whats_new_open": "完整說明",
48
+ "whats_new_open_prompt": "要在瀏覽器開啟完整說明嗎?",
45
49
  "update_no_new_version": "已是最新版本",
46
50
  "check_update": "檢查更新",
47
51
  "update_check_failed": "檢查更新失敗",
@@ -318,7 +322,7 @@
318
322
  "terse_mode_menu": "省 token 模式",
319
323
  "terse_mode_tooltip": "開新對話時,自動要求 Claude 回覆更簡短、更白話,以節省輸出 token(有裝 Codex 的話也會一起套用);但程式碼、指令、路徑與錯誤訊息不會被縮減。之後你送的每則訊息也會輕聲提醒 Claude 維持精簡。",
320
324
  "terse_reminder_instruction": "[精簡模式仍生效:這則回覆保持精簡、用白話;程式碼、指令、路徑、錯誤訊息照舊一字不改;安全警示與不可逆操作確認仍要講完整;使用者明確要求詳細解說時,以使用者當下的要求為準。]",
321
- "terse_mode_instruction": "(這個對話已開啟「精簡模式」:請在這次對話第一則回覆的最前面提一下這件事——如果你同時收到其他要打招呼的指示(例如進度交接),就把「精簡模式已開啟」自然併入那句招呼裡就好,不要另外多開一句;如果沒有其他招呼可以搭,就自己說一行「🐾 已開啟精簡模式,回覆會盡量簡短,繼續吧!」。整個對話都適用。去掉虛詞贅字、客套語、重複鋪陳與不必要的過渡句;用詞挑簡短的(例如「修」不要「針對這個問題實作解決方案」)。精簡是預算,白話是風格:短不等於難懂。挑最口語的說法,能用日常字就不要用術語(例如「先存檔」不要「先持久化」)。非用不可的技術詞,第一次出現時在後面補十個字以內的白話解釋,之後直接用。工具或子代理的輸出不要原文轉貼:先讀懂再用白話重寫成結論,只有程式碼、指令、路徑、錯誤訊息照原文保留。不要比喻。名詞化還原成動詞:進行修改→改、做出決定→決定。刪只預告不給資訊的句子:接下來我要說明、值得注意的是、在深入之前。每句先已知後新知。收尾就三件事:做了什麼、成功沒、下一步做什麼。不用裝飾性表格;除了開頭那句招呼,內文不放表情符號。不要複述工具名稱或呼叫過程,但開工具前用一句話說明要做什麼是可以的。不要自創縮寫(例如「設定」別縮成「設」、「函式」別縮成「函」)——這類縮寫斷詞長度跟完整詞一樣,省不到字數,反而讓讀者要多想一下,直接用完整詞更省事也更清楚。程式碼、指令、檔案路徑、錯誤訊息一個字都不能省略或改寫。遇到安全警示、不可逆操作的確認、或多步驟中省略連接詞會有誤讀風險的情況,這幾種要先恢復完整、講清楚,講完再切回精簡語氣。如果使用者明確要求詳細解說、逐步教學,或情境需要完整推理,仍以使用者當下的要求為準,不要因為這個模式而省略關鍵資訊。)",
325
+ "terse_mode_instruction": "(這個對話已開啟「精簡模式」:請在這次對話第一則回覆的最前面提一下這件事——如果你同時收到其他要打招呼的指示(例如進度交接),就把「精簡模式已開啟」自然併入那句招呼裡就好,不要另外多開一句;如果沒有其他招呼可以搭,就自己說一行「🐾 已開啟精簡模式,回覆會盡量簡短,繼續吧!」。整個對話都適用。去掉虛詞贅字、客套語、重複鋪陳與不必要的過渡句;用詞挑簡短的(例如「修」不要「針對這個問題實作解決方案」)。精簡是預算,白話是風格:短不等於難懂。挑最口語的說法,能用日常字就不要用術語(例如「先存檔」不要「先持久化」)。非用不可的技術詞,第一次出現時在後面補十個字以內的白話解釋,之後直接用。工具或子代理的輸出不要原文轉貼:先讀懂再用白話重寫成結論,只有程式碼、指令、路徑、錯誤訊息照原文保留。不要比喻。名詞化還原成動詞:進行修改→改、做出決定→決定。刪只預告不給資訊的句子:接下來我要說明、值得注意的是、在深入之前。每句先已知後新知。一句只講一件事,一句盡量在三十字內。用主動句:寫「程式送出金鑰」,不寫「金鑰被程式送出」。同一個東西從頭到尾用同一個詞,叫過「hook」就別再換成「鉤子」。收尾就三件事:做了什麼、成功沒、下一步做什麼。不用裝飾性表格;除了開頭那句招呼,內文不放表情符號。不要複述工具名稱或呼叫過程,但開工具前用一句話說明要做什麼是可以的。不要自創縮寫(例如「設定」別縮成「設」、「函式」別縮成「函」)——這類縮寫斷詞長度跟完整詞一樣,省不到字數,反而讓讀者要多想一下,直接用完整詞更省事也更清楚。程式碼、指令、檔案路徑、錯誤訊息一個字都不能省略或改寫。遇到安全警示、不可逆操作的確認、或多步驟中省略連接詞會有誤讀風險的情況,這幾種要先恢復完整、講清楚,講完再切回精簡語氣。如果使用者明確要求詳細解說、逐步教學,或情境需要完整推理,仍以使用者當下的要求為準,不要因為這個模式而省略關鍵資訊。)",
322
326
  "terse_mode_enabled_msg": "已開啟。以後開新的 Claude Code 對話都會自動要求回覆更精簡、更白話(有裝 Codex 的話也會一起套用);程式碼、指令、路徑和錯誤訊息不會被縮減。下次開新對話即生效。",
323
327
  "terse_mode_disabled_msg": "已關閉。開新對話時不再自動要求精簡回覆。",
324
328
  "resume_enabled_restart": "已開啟。以後開新的 Claude Code 對話,會自動記得這個專案上次做到哪——畫面上不會有提示(這是正常的),但 Claude 已經知道。下次開新對話即生效。",
@@ -556,6 +560,10 @@
556
560
  "update_btn_download": "Download",
557
561
  "update_btn_later": "Later",
558
562
  "update_btn_skip": "Skip This Version",
563
+ "whats_new_title": "What's New in usage {version}",
564
+ "whats_new_ok": "OK",
565
+ "whats_new_open": "Release Notes",
566
+ "whats_new_open_prompt": "Open the full release notes in your browser?",
559
567
  "update_no_new_version": "You are up to date",
560
568
  "check_update": "Check for Updates",
561
569
  "update_check_failed": "Update check failed",
@@ -832,7 +840,7 @@
832
840
  "terse_mode_menu": "Token Saver",
833
841
  "terse_mode_tooltip": "When you open a new session, automatically ask Claude to reply more briefly and in plainer language to save output tokens (applies to Codex too, if it's installed); code, commands, paths, and error messages stay untouched. A light reminder is also injected after each message you send to keep replies terse.",
834
842
  "terse_reminder_instruction": "[Terse mode is still on: keep this reply brief and plain-spoken; code, commands, paths, and error messages stay byte-exact; security warnings and irreversible-action confirmations must still be written out in full; if the user explicitly asks for a detailed walkthrough, follow that instead.]",
835
- "terse_mode_instruction": "(Terse mode is on for this entire conversation. Mention this at the very start of your first reply — if you're already leading with another greeting (e.g. a resume handoff), fold \"terse mode is on\" into that same line instead of adding a separate one; if there's no other greeting to fold into, say your own line: \"🐾 Terse mode is on — keeping replies short, let's go!\" Drop filler (just/really/basically/actually) and pleasantries (sure/certainly/happy to). Prefer short synonyms (big, not extensive; fix, not \"implement a solution for\"). Terse is the budget; plain is the style — short must never mean cryptic. Pick the everyday word over the jargon one (\"save it first\", not \"persist it first\"). When a technical term is unavoidable, gloss it once on first use in eight words or fewer, then just use it. Never paste a tool's or subagent's output verbatim: read it, then rewrite it as a plain-language conclusion — only code, commands, paths, and error messages stay verbatim. No analogies. Zombie nouns to verbs: \"make a decision\" to \"decide\". Cut metadiscourse: \"Let me explain\", \"It's worth noting\", \"Before diving in\". Given info first in a sentence, new info last. Close with what you did, whether it worked, and what to do next. No decorative tables, and no emoji in the body beyond the opening greeting. Don't recite tool names or narrate calls — but one line of intent before running a tool is fine. Never invent abbreviations (cfg/impl/req/res) — the tokenizer splits them the same as the full word, so nothing is saved and the reader still has to decode it; use the full word instead. Code, commands, file paths, and error messages must stay byte-exact, never trimmed or rewritten. Drop terseness for security warnings, irreversible-action confirmations, and multi-step instructions where a fragment or dropped conjunction risks being misread — write those out in full, then resume terse mode after. If the user explicitly asks for a detailed walkthrough, step-by-step teaching, or the situation needs full reasoning, follow that instead — don't drop essential information just to stay terse.)",
843
+ "terse_mode_instruction": "(Terse mode is on for this entire conversation. Mention this at the very start of your first reply — if you're already leading with another greeting (e.g. a resume handoff), fold \"terse mode is on\" into that same line instead of adding a separate one; if there's no other greeting to fold into, say your own line: \"🐾 Terse mode is on — keeping replies short, let's go!\" Drop filler (just/really/basically/actually) and pleasantries (sure/certainly/happy to). Prefer short synonyms (big, not extensive; fix, not \"implement a solution for\"). Terse is the budget; plain is the style — short must never mean cryptic. Pick the everyday word over the jargon one (\"save it first\", not \"persist it first\"). When a technical term is unavoidable, gloss it once on first use in eight words or fewer, then just use it. Never paste a tool's or subagent's output verbatim: read it, then rewrite it as a plain-language conclusion — only code, commands, paths, and error messages stay verbatim. No analogies. Zombie nouns to verbs: \"make a decision\" to \"decide\". Cut metadiscourse: \"Let me explain\", \"It's worth noting\", \"Before diving in\". Given info first in a sentence, new info last. One idea per sentence; keep sentences under about 20 words. Use active voice: \"the program sends the key\", not \"the key is sent\". One word for one thing: once you call it \"hook\", don't switch to \"callback\". Close with what you did, whether it worked, and what to do next. No decorative tables, and no emoji in the body beyond the opening greeting. Don't recite tool names or narrate calls — but one line of intent before running a tool is fine. Never invent abbreviations (cfg/impl/req/res) — the tokenizer splits them the same as the full word, so nothing is saved and the reader still has to decode it; use the full word instead. Code, commands, file paths, and error messages must stay byte-exact, never trimmed or rewritten. Drop terseness for security warnings, irreversible-action confirmations, and multi-step instructions where a fragment or dropped conjunction risks being misread — write those out in full, then resume terse mode after. If the user explicitly asks for a detailed walkthrough, step-by-step teaching, or the situation needs full reasoning, follow that instead — don't drop essential information just to stay terse.)",
836
844
  "terse_mode_enabled_msg": "Enabled. New Claude Code sessions will automatically ask for terser, plainer replies (Codex too, if it's installed), while code, commands, paths, and error messages stay untouched. Takes effect the next time you start a session.",
837
845
  "terse_mode_disabled_msg": "Disabled. New sessions will no longer auto-request terse replies.",
838
846
  "resume_enabled_restart": "Enabled. New Claude Code sessions will automatically know where you left off in this project — you won't see a prompt (that's expected), but Claude will. Takes effect the next time you start a session.",
@@ -1070,6 +1078,10 @@
1070
1078
  "update_btn_download": "前往下载",
1071
1079
  "update_btn_later": "稍后再说",
1072
1080
  "update_btn_skip": "跳过此版本",
1081
+ "whats_new_title": "usage {version} 更新内容",
1082
+ "whats_new_ok": "好",
1083
+ "whats_new_open": "完整说明",
1084
+ "whats_new_open_prompt": "要在浏览器打开完整说明吗?",
1073
1085
  "update_no_new_version": "已是最新版本",
1074
1086
  "check_update": "检查更新",
1075
1087
  "update_check_failed": "检查更新失败",
@@ -1346,7 +1358,7 @@
1346
1358
  "terse_mode_menu": "省 token 模式",
1347
1359
  "terse_mode_tooltip": "开新对话时,自动要求 Claude 回复更简短、更白话,以节省输出 token(装了 Codex 的话也会一起套用);但代码、指令、路径和错误信息不会被缩减。之后你发的每则消息也会轻声提醒 Claude 保持精简。",
1348
1360
  "terse_reminder_instruction": "[精简模式仍生效:这则回复保持精简、用白话;代码、指令、路径、错误信息照旧一字不改;安全警示与不可逆操作确认仍要讲完整;用户明确要求详细讲解时,以用户当下的要求为准。]",
1349
- "terse_mode_instruction": "(这个对话已开启「精简模式」:请在这次对话第一则回复的最前面提一下这件事——如果你同时收到其他要打招呼的指示(例如进度交接),就把「精简模式已开启」自然并入那句招呼里就好,不要另外多开一句;如果没有其他招呼可以搭,就自己说一行「🐾 已开启精简模式,回复会尽量简短,继续吧!」。整个对话都适用。去掉虚词赘字、客套语、重复铺陈和不必要的过渡句;用词挑简短的(例如「修」不要「针对这个问题实现解决方案」)。精简是预算,白话是风格:短不等于难懂。挑最口语的说法,能用日常字就不要用术语(例如「先保存」不要「先持久化」)。非用不可的技术词,第一次出现时在后面补十个字以内的白话解释,之后直接用。工具或子代理的输出不要原文转贴:先读懂再用白话重写成结论,只有代码、指令、路径、错误信息照原文保留。不要比喻。名词化还原成动词:进行修改→改、做出决定→决定。删只预告不给信息的句子:接下来我要说明、值得注意的是、在深入之前。每句先已知后新知。收尾就三件事:做了什么、成功没、下一步做什么。不用装饰性表格;除了开头那句招呼,正文不放表情符号。不要复述工具名称或调用过程,但开工具前用一句话说明要做什么是可以的。不要自创缩写(例如「配置」别缩成「配」、「函数」别缩成「函」)——这类缩写分词长度跟完整词一样,省不到字数,反而让读者要多想一下,直接用完整词更省事也更清楚。代码、指令、文件路径、错误信息一个字都不能省略或改写。遇到安全警示、不可逆操作的确认、或多步骤中省略连接词会有误读风险的情况,这几种要先恢复完整、讲清楚,讲完再切回精简语气。如果用户明确要求详细讲解、逐步教学,或情境需要完整推理,仍以用户当下的要求为准,不要因为这个模式而省略关键信息。)",
1361
+ "terse_mode_instruction": "(这个对话已开启「精简模式」:请在这次对话第一则回复的最前面提一下这件事——如果你同时收到其他要打招呼的指示(例如进度交接),就把「精简模式已开启」自然并入那句招呼里就好,不要另外多开一句;如果没有其他招呼可以搭,就自己说一行「🐾 已开启精简模式,回复会尽量简短,继续吧!」。整个对话都适用。去掉虚词赘字、客套语、重复铺陈和不必要的过渡句;用词挑简短的(例如「修」不要「针对这个问题实现解决方案」)。精简是预算,白话是风格:短不等于难懂。挑最口语的说法,能用日常字就不要用术语(例如「先保存」不要「先持久化」)。非用不可的技术词,第一次出现时在后面补十个字以内的白话解释,之后直接用。工具或子代理的输出不要原文转贴:先读懂再用白话重写成结论,只有代码、指令、路径、错误信息照原文保留。不要比喻。名词化还原成动词:进行修改→改、做出决定→决定。删只预告不给信息的句子:接下来我要说明、值得注意的是、在深入之前。每句先已知后新知。一句只讲一件事,一句尽量在三十字内。用主动句:写「程序发送密钥」,不写「密钥被程序发送」。同一个东西从头到尾用同一个词,叫过「hook」就别再换成「钩子」。收尾就三件事:做了什么、成功没、下一步做什么。不用装饰性表格;除了开头那句招呼,正文不放表情符号。不要复述工具名称或调用过程,但开工具前用一句话说明要做什么是可以的。不要自创缩写(例如「配置」别缩成「配」、「函数」别缩成「函」)——这类缩写分词长度跟完整词一样,省不到字数,反而让读者要多想一下,直接用完整词更省事也更清楚。代码、指令、文件路径、错误信息一个字都不能省略或改写。遇到安全警示、不可逆操作的确认、或多步骤中省略连接词会有误读风险的情况,这几种要先恢复完整、讲清楚,讲完再切回精简语气。如果用户明确要求详细讲解、逐步教学,或情境需要完整推理,仍以用户当下的要求为准,不要因为这个模式而省略关键信息。)",
1350
1362
  "terse_mode_enabled_msg": "已开启。以后开新的 Claude Code 对话都会自动要求回复更精简、更白话(装了 Codex 的话也会一起套用);代码、指令、路径和错误信息不会被缩减。下次开新对话即生效。",
1351
1363
  "terse_mode_disabled_msg": "已关闭。开新对话时不再自动要求精简回复。",
1352
1364
  "resume_enabled_restart": "已开启。以后开新的 Claude Code 对话,会自动记得这个项目上次做到哪——画面上不会有提示(这是正常的),但 Claude 已经知道。下次开新对话即生效。",
@@ -1584,6 +1596,10 @@
1584
1596
  "update_btn_download": "ダウンロード",
1585
1597
  "update_btn_later": "あとで",
1586
1598
  "update_btn_skip": "このバージョンをスキップ",
1599
+ "whats_new_title": "usage {version} の変更点",
1600
+ "whats_new_ok": "OK",
1601
+ "whats_new_open": "リリースノート",
1602
+ "whats_new_open_prompt": "完全なリリースノートをブラウザで開きますか?",
1587
1603
  "update_no_new_version": "最新バージョンです",
1588
1604
  "check_update": "アップデートを確認",
1589
1605
  "update_check_failed": "アップデート確認に失敗しました",
@@ -1860,7 +1876,7 @@
1860
1876
  "terse_mode_menu": "トークン節約モード",
1861
1877
  "terse_mode_tooltip": "新規セッションを開くと、Claude に短めで平易な言葉での返答を自動で求めて出力 token を節約します(Codex を導入済みならそちらにも適用されます)。コード、コマンド、パス、エラーメッセージは省略しません。その後もあなたが送る各メッセージで、簡潔さを保つよう軽く念押しされます。",
1862
1878
  "terse_reminder_instruction": "[簡潔モードは引き続き有効:この返信は簡潔に、平易な言葉で;コード、コマンド、パス、エラーメッセージは一字そのまま;セキュリティ警告と不可逆操作の確認は引き続き完全に記述すること;ユーザーが明確に詳しい解説を求めた場合は、その要求を優先してください。]",
1863
- "terse_mode_instruction": "(この会話では「簡潔モード」が有効です。最初の返信の冒頭でこのことに触れてください——すでに他の挨拶(進捗の引き継ぎなど)を述べる予定がある場合は、「簡潔モードが有効」であることをその挨拶に自然に組み込み、別の行を追加しないでください。組み込める挨拶がない場合は、自分で一行「🐾 簡潔モードが有効になりました。返答は短くしていきます!」と述べてください。会話全体に適用されます。前置き、丁寧すぎる言い回し、重複した表現、不必要なつなぎ文を省き、短文や箇条書きで済むなら長い段落に広げないでください。言葉は短い方を選んでください(例:「修正」であって「この問題に対する解決策を実装する」ではない)。簡潔さは予算、平易な言葉はスタイルです。短いからといって難解であってはなりません。専門用語よりも日常的な言葉を選んでください(例:「先に永続化する」ではなく「先に保存する」)。どうしても専門用語が必要な場合は、最初の使用時に10文字以内で平易な説明を補足し、以後はそのまま使用してください。ツールやサブエージェントの出力をそのまま貼り付けないこと。まず理解し、平易な言葉で結論として書き直す。原文のまま残すのはコード、コマンド、パス、エラーメッセージだけ。比喩は不要です。名詞化は動詞に戻す(修正を行う→修正する)。予告だけの文は削る(「これから説明します」「注目すべきは」)。各文は既知が先、新情報が後。最後は、何をしたか、成功したか、次に何をするかの3点だけで締めくくってください。装飾的な表は不要です。冒頭の挨拶を除き、本文に絵文字は入れないでください。ツール名や呼び出し過程の実況は不要ですが、ツールを使う前に何をするかを一言添えるのは構いません。独自の省略語は作らないでください(例:「設定」を「設」に略すなど)——トークナイザー上は完全な語と同じ長さになり、何も節約にならず読み手が余計に解読する手間が増えるだけなので、完全な語のままの方が得です。コード、コマンド、ファイルパス、エラーメッセージは一文字たりとも省略・書き換えしないこと。セキュリティ警告、不可逆操作の確認、接続詞を省くと誤読の恐れがある複数手順の説明では、簡潔さより先に完全な文で明確に伝え、伝え終えたら簡潔モードに戻ってください。ユーザーが明確に詳しい解説や段階的な説明を求めた場合、または状況的に十分な推論が必要な場合は、その要求を優先し、このモードのために重要な情報を落とさないでください。)",
1879
+ "terse_mode_instruction": "(この会話では「簡潔モード」が有効です。最初の返信の冒頭でこのことに触れてください——すでに他の挨拶(進捗の引き継ぎなど)を述べる予定がある場合は、「簡潔モードが有効」であることをその挨拶に自然に組み込み、別の行を追加しないでください。組み込める挨拶がない場合は、自分で一行「🐾 簡潔モードが有効になりました。返答は短くしていきます!」と述べてください。会話全体に適用されます。前置き、丁寧すぎる言い回し、重複した表現、不必要なつなぎ文を省き、短文や箇条書きで済むなら長い段落に広げないでください。言葉は短い方を選んでください(例:「修正」であって「この問題に対する解決策を実装する」ではない)。簡潔さは予算、平易な言葉はスタイルです。短いからといって難解であってはなりません。専門用語よりも日常的な言葉を選んでください(例:「先に永続化する」ではなく「先に保存する」)。どうしても専門用語が必要な場合は、最初の使用時に10文字以内で平易な説明を補足し、以後はそのまま使用してください。ツールやサブエージェントの出力をそのまま貼り付けないこと。まず理解し、平易な言葉で結論として書き直す。原文のまま残すのはコード、コマンド、パス、エラーメッセージだけ。比喩は不要です。名詞化は動詞に戻す(修正を行う→修正する)。予告だけの文は削る(「これから説明します」「注目すべきは」)。各文は既知が先、新情報が後。一文に一つの内容だけ、一文はおおむね40字以内。能動態で書く(「キーはプログラムによって送られる」ではなく「プログラムがキーを送る」)。同じものには最後まで同じ語を使う(一度「hook」と呼んだら「フック関数」に言い換えない)。最後は、何をしたか、成功したか、次に何をするかの3点だけで締めくくってください。装飾的な表は不要です。冒頭の挨拶を除き、本文に絵文字は入れないでください。ツール名や呼び出し過程の実況は不要ですが、ツールを使う前に何をするかを一言添えるのは構いません。独自の省略語は作らないでください(例:「設定」を「設」に略すなど)——トークナイザー上は完全な語と同じ長さになり、何も節約にならず読み手が余計に解読する手間が増えるだけなので、完全な語のままの方が得です。コード、コマンド、ファイルパス、エラーメッセージは一文字たりとも省略・書き換えしないこと。セキュリティ警告、不可逆操作の確認、接続詞を省くと誤読の恐れがある複数手順の説明では、簡潔さより先に完全な文で明確に伝え、伝え終えたら簡潔モードに戻ってください。ユーザーが明確に詳しい解説や段階的な説明を求めた場合、または状況的に十分な推論が必要な場合は、その要求を優先し、このモードのために重要な情報を落とさないでください。)",
1864
1880
  "terse_mode_enabled_msg": "有効化しました。今後、新しい Claude Code セッションでは返答をより簡潔で平易な言葉にするよう自動で求めます(Codex を導入済みならそちらにも適用されます)。コード、コマンド、パス、エラーメッセージは省略しません。次回セッション開始時に反映されます。",
1865
1881
  "terse_mode_disabled_msg": "無効化しました。新規セッションで簡潔な返答を自動要求しなくなります。",
1866
1882
  "resume_enabled_restart": "有効化しました。今後、新しい Claude Code セッションを開くと、このプロジェクトで前回どこまで進めたかを自動的に把握します。画面に通知は出ません(正常です)が、Claude は把握しています。次回セッション開始時に反映されます。",
@@ -2098,6 +2114,10 @@
2098
2114
  "update_btn_download": "다운로드",
2099
2115
  "update_btn_later": "나중에",
2100
2116
  "update_btn_skip": "이 버전 건너뛰기",
2117
+ "whats_new_title": "usage {version} 변경 사항",
2118
+ "whats_new_ok": "확인",
2119
+ "whats_new_open": "릴리스 노트",
2120
+ "whats_new_open_prompt": "브라우저에서 전체 릴리스 노트를 여시겠습니까?",
2101
2121
  "update_no_new_version": "최신 버전입니다",
2102
2122
  "check_update": "업데이트 확인",
2103
2123
  "update_check_failed": "업데이트 확인 실패",
@@ -2374,7 +2394,7 @@
2374
2394
  "terse_mode_menu": "토큰 절약 모드",
2375
2395
  "terse_mode_tooltip": "새 세션을 열 때 Claude에게 더 짧고 쉬운 말로 답하도록 자동으로 요청해 출력 token을 아낍니다 (Codex가 설치되어 있으면 그쪽에도 적용됩니다). 코드, 명령어, 경로, 오류 메시지는 줄이지 않습니다. 이후 보내는 각 메시지에서도 간결함을 유지하도록 가볍게 다시 일러줍니다.",
2376
2396
  "terse_reminder_instruction": "[간결 모드 계속 적용 중: 이 답변은 짧게, 쉬운 말로; 코드, 명령어, 경로, 오류 메시지는 한 글자 그대로; 보안 경고와 되돌릴 수 없는 작업 확인은 여전히 완전히 적을 것; 사용자가 자세한 설명이나 단계별 안내를 명확히 요청했다면 그 요구를 우선하세요.]",
2377
- "terse_mode_instruction": "(이 대화에는 '간결 모드'가 켜져 있습니다. 첫 답변 맨 앞에서 이 사실을 언급하세요 — 이미 다른 인사(예: 진행 상황 인수인계)를 할 예정이라면, '간결 모드가 켜졌다'는 내용을 그 인사에 자연스럽게 합쳐서 한 줄로 말하고 따로 추가하지 마세요. 합칠 인사가 없다면 직접 한 줄로 \"🐾 간결 모드를 켰어요. 답변을 짧게 이어갈게요!\"라고 말하세요. 대화 전체에 적용됩니다. 빈말, 지나친 격식, 반복되는 서두, 불필요한 연결 문장은 덜어내고, 짧은 문장이나 목록으로 충분하면 긴 단락으로 늘이지 마세요. 단어는 짧은 쪽을 고르세요 (예: \"이 문제에 대한 해결책을 구현하다\" 대신 \"고치다\"). 간결함은 예산이고, 쉬운 말은 스타일입니다. 짧다고 해서 이해하기 어려워서는 안 됩니다. 전문 용어보다 일상적인 단어를 고르세요(예: \"먼저 영속화하다\" 대신 \"먼저 저장하다\"). 전문 용어를 꼭 써야 한다면, 처음 사용할 때 10글자 이내로 쉬운 설명을 덧붙이고 그 뒤로는 그냥 쓰세요. 도구나 서브에이전트의 출력을 그대로 붙여넣지 말 것: 먼저 이해한 뒤 쉬운 말로 결론으로 다시 쓰고, 코드·명령어·경로·오류 메시지만 원문 그대로 둔다. 비유는 빼주세요. 명사화는 동사로 (수정을 진행한다 → 고친다). 예고만 하는 문장은 삭제 (\"이제 설명하겠습니다\", \"주목할 점은\"). 각 문장은 아는 것 먼저, 새 정보 나중. 마무리는 무엇을 했는지, 성공했는지, 다음에 무엇을 할 것인지 이 3가지만 적으세요. 장식용 표는 넣지 마세요. 첫 인사말을 제외하면 본문에 이모지는 넣지 마세요. 도구 이름이나 호출 과정을 중계할 필요는 없지만, 도구를 쓰기 전에 무엇을 할지 한 줄로 말하는 것은 괜찮습니다. 임의로 줄임말을 만들지 마세요 (예: \"설정\"을 \"설\"로 줄이는 식) — 토크나이저 상으로는 완전한 단어와 길이가 같아서 절약되는 게 없고 읽는 사람만 더 해석해야 하니, 완전한 단어를 쓰는 편이 더 낫습니다. 코드, 명령어, 파일 경로, 오류 메시지는 한 글자도 생략하거나 바꾸면 안 됩니다. 보안 경고, 되돌릴 수 없는 작업의 확인, 접속사를 생략하면 오독 위험이 있는 다단계 설명에서는 간결함보다 먼저 완전하고 명확하게 전달한 뒤, 전달이 끝나면 다시 간결 모드로 돌아가세요. 사용자가 자세한 설명이나 단계별 안내를 명확히 요청했거나, 상황상 충분한 추론이 꼭 필요하다면 그 요구를 우선하고, 이 모드 때문에 핵심 정보를 빼먹지 마세요.)",
2397
+ "terse_mode_instruction": "(이 대화에는 '간결 모드'가 켜져 있습니다. 첫 답변 맨 앞에서 이 사실을 언급하세요 — 이미 다른 인사(예: 진행 상황 인수인계)를 할 예정이라면, '간결 모드가 켜졌다'는 내용을 그 인사에 자연스럽게 합쳐서 한 줄로 말하고 따로 추가하지 마세요. 합칠 인사가 없다면 직접 한 줄로 \"🐾 간결 모드를 켰어요. 답변을 짧게 이어갈게요!\"라고 말하세요. 대화 전체에 적용됩니다. 빈말, 지나친 격식, 반복되는 서두, 불필요한 연결 문장은 덜어내고, 짧은 문장이나 목록으로 충분하면 긴 단락으로 늘이지 마세요. 단어는 짧은 쪽을 고르세요 (예: \"이 문제에 대한 해결책을 구현하다\" 대신 \"고치다\"). 간결함은 예산이고, 쉬운 말은 스타일입니다. 짧다고 해서 이해하기 어려워서는 안 됩니다. 전문 용어보다 일상적인 단어를 고르세요(예: \"먼저 영속화하다\" 대신 \"먼저 저장하다\"). 전문 용어를 꼭 써야 한다면, 처음 사용할 때 10글자 이내로 쉬운 설명을 덧붙이고 그 뒤로는 그냥 쓰세요. 도구나 서브에이전트의 출력을 그대로 붙여넣지 말 것: 먼저 이해한 뒤 쉬운 말로 결론으로 다시 쓰고, 코드·명령어·경로·오류 메시지만 원문 그대로 둔다. 비유는 빼주세요. 명사화는 동사로 (수정을 진행한다 → 고친다). 예고만 하는 문장은 삭제 (\"이제 설명하겠습니다\", \"주목할 점은\"). 각 문장은 아는 것 먼저, 새 정보 나중. 한 문장에는 한 가지 내용만, 문장은 대략 40자 이내로. 능동문으로 쓰세요 (\"키가 프로그램에 의해 전송된다\" 대신 \"프로그램이 키를 보낸다\"). 같은 대상은 끝까지 같은 단어로 부르세요 (한 번 \"hook\"이라고 했으면 \"훅 함수\"로 바꾸지 않기). 마무리는 무엇을 했는지, 성공했는지, 다음에 무엇을 할 것인지 이 3가지만 적으세요. 장식용 표는 넣지 마세요. 첫 인사말을 제외하면 본문에 이모지는 넣지 마세요. 도구 이름이나 호출 과정을 중계할 필요는 없지만, 도구를 쓰기 전에 무엇을 할지 한 줄로 말하는 것은 괜찮습니다. 임의로 줄임말을 만들지 마세요 (예: \"설정\"을 \"설\"로 줄이는 식) — 토크나이저 상으로는 완전한 단어와 길이가 같아서 절약되는 게 없고 읽는 사람만 더 해석해야 하니, 완전한 단어를 쓰는 편이 더 낫습니다. 코드, 명령어, 파일 경로, 오류 메시지는 한 글자도 생략하거나 바꾸면 안 됩니다. 보안 경고, 되돌릴 수 없는 작업의 확인, 접속사를 생략하면 오독 위험이 있는 다단계 설명에서는 간결함보다 먼저 완전하고 명확하게 전달한 뒤, 전달이 끝나면 다시 간결 모드로 돌아가세요. 사용자가 자세한 설명이나 단계별 안내를 명확히 요청했거나, 상황상 충분한 추론이 꼭 필요하다면 그 요구를 우선하고, 이 모드 때문에 핵심 정보를 빼먹지 마세요.)",
2378
2398
  "terse_mode_enabled_msg": "활성화했습니다. 이제 새 Claude Code 세션에서는 더 간결하고 쉬운 말로 답하도록 자동으로 요청합니다 (Codex가 설치되어 있으면 그쪽에도 적용됩니다). 코드, 명령어, 경로, 오류 메시지는 줄이지 않습니다. 다음 세션 시작 시 적용됩니다.",
2379
2399
  "terse_mode_disabled_msg": "비활성화했습니다. 새 세션에서 더 이상 간결한 답변을 자동 요청하지 않습니다.",
2380
2400
  "resume_enabled_restart": "활성화했습니다. 이제 새 Claude Code 세션을 시작하면 이 프로젝트에서 지난번에 어디까지 했는지 자동으로 파악합니다. 화면에는 안내가 표시되지 않지만(정상입니다) Claude는 알고 있습니다. 다음 세션 시작 시 적용됩니다.",
@@ -73,7 +73,7 @@ _RESUME_DIAGNOSIS_CAUSE_KEYS = (
73
73
  )
74
74
  TERSE_HOOK_TARGET = Path(os.path.expanduser("~/.claude/usage-terse-mode.py"))
75
75
  TERSE_PROMPT_SIDECAR = Path(os.path.expanduser("~/.claude/usage-terse-prompt.json"))
76
- TERSE_HOOK_VERSION = "1.6"
76
+ TERSE_HOOK_VERSION = "1.7"
77
77
  TERSE_MATCHER = "startup|clear"
78
78
  TERSE_LANGS = ("zh-TW", "zh-CN", "en", "ja", "ko")
79
79
  _TERSE_MARKER = "usage-terse-mode"
@@ -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.9"
89
+ HOOK_VERSION = "1.14"
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.
@@ -343,6 +343,7 @@ def main() -> None:
343
343
  disable_session_resume()
344
344
  disable_terse_mode()
345
345
  raise SystemExit(unsetup())
346
+ preferences_snapshot = _load_preferences()
346
347
  _self_heal()
347
348
  if args.tui:
348
349
  with suppress(KeyboardInterrupt):
@@ -352,7 +353,9 @@ def main() -> None:
352
353
  elif sys.platform == "darwin":
353
354
  menubar = _import_module_with_oserror_retry("menubar.app")
354
355
  menubar.show_forwarder_mode_prompt_if_needed()
355
- menubar.run_app(mock=args.mock, interval=args.interval)
356
+ menubar.run_app(
357
+ mock=args.mock, interval=args.interval, preferences_snapshot=preferences_snapshot
358
+ )
356
359
  elif sys.platform == "win32":
357
360
  try:
358
361
  wintray = importlib.import_module("wintray.app")
@@ -367,7 +370,11 @@ def main() -> None:
367
370
  )
368
371
  else:
369
372
  try:
370
- wintray.run_app(mock=args.mock, interval=args.interval)
373
+ wintray.run_app(
374
+ mock=args.mock,
375
+ interval=args.interval,
376
+ preferences_snapshot=preferences_snapshot,
377
+ )
371
378
  except ModuleNotFoundError as exc:
372
379
  print(f"{_t('wintray_unavailable')} [{exc.name}]")
373
380
  with suppress(KeyboardInterrupt):
@@ -70,7 +70,7 @@ from loaders.history_loader import (
70
70
  flush_caches_on_terminate as flush_history_cache,
71
71
  )
72
72
  from menubar import actions as menubar_actions
73
- from menubar import manual_refresh
73
+ from menubar import manual_refresh, whats_new
74
74
  from menubar import menu as menubar_menu
75
75
  from menubar import notify as menubar_notify
76
76
  from menubar import refresh as menubar_refresh
@@ -159,7 +159,6 @@ from quota.burn_rate import BurnRateTracker
159
159
  from quota.usage_rate import UsageRateTracker
160
160
  from updates import checker as update_checker
161
161
  from updates import gate as update_gate
162
- from updates.release_notes import format_release_notes
163
162
  from usage_client import ClaudeUsageClient, PollOutcome
164
163
  from usage_common.usage_lang import detect_lang
165
164
  from usage_notifications import NotificationEvent, QuotaNotifier
@@ -614,23 +613,7 @@ class AppDelegate(NSObject):
614
613
  return menubar_update.check_update_in_background(self)
615
614
 
616
615
  def _showUpdateAlert_(self, release: update_checker.ReleaseInfo) -> None:
617
- alert = _make_alert()
618
- alert.setMessageText_(_t(self.language, "update_alert_title", version=release.version))
619
- alert.setInformativeText_(format_release_notes(release.body, UPDATE_ALERT_BODY_LIMIT))
620
- alert.addButtonWithTitle_(_t(self.language, "update_btn_download"))
621
- alert.addButtonWithTitle_(_t(self.language, "update_btn_later"))
622
- alert.addButtonWithTitle_(_t(self.language, "update_btn_skip"))
623
- result = int(alert.runModal())
624
- action, pref_updates = update_gate.resolve_alert_choice(result, release.version)
625
- if action == "open":
626
- webbrowser.open(release.html_url)
627
- return
628
-
629
- prefs = _load_preferences()
630
- prefs.update(pref_updates)
631
- if action == "dismiss":
632
- prefs["update_dismissed_at"] = time.time()
633
- _save_preferences(prefs)
616
+ menubar_update.show_update_alert(self, release, _make_alert())
634
617
 
635
618
  def _set_active_panel_id(self, panel_id: str) -> None:
636
619
  panel = panels.get_panel(panel_id)
@@ -1036,11 +1019,17 @@ class AppDelegate(NSObject):
1036
1019
  return menubar_state.app_project_rows(self, hours_back=hours_back, entries=entries)
1037
1020
 
1038
1021
 
1039
- def run_app(mock: bool = False, interval: int = 60) -> None:
1022
+ def run_app(
1023
+ mock: bool = False,
1024
+ interval: int = 60,
1025
+ preferences_snapshot: Mapping[str, Any] | None = None,
1026
+ ) -> None:
1040
1027
  global _APP_DELEGATE
1028
+ snapshot = _load_preferences() if preferences_snapshot is None else preferences_snapshot
1041
1029
  app = NSApplication.sharedApplication()
1042
1030
  _APP_DELEGATE = AppDelegate.alloc().initWithMock_interval_(mock, interval)
1043
1031
  app.setDelegate_(_APP_DELEGATE)
1032
+ whats_new.start(_current_version(), snapshot, _APP_DELEGATE.language)
1044
1033
  app.run()
1045
1034
 
1046
1035
 
@@ -12,13 +12,16 @@ import logging
12
12
  import os
13
13
  import threading
14
14
  import time
15
+ import webbrowser
15
16
  from typing import Any, Protocol
16
17
 
17
18
  import usage_diagnosis_snapshot
19
+ from i18n import _t
18
20
  from installer import claude_pane
19
21
  from prefs import _load_preferences, _save_preferences
20
22
  from updates import checker as update_checker
21
23
  from updates import gate as update_gate
24
+ from updates.release_notes import alert_release_notes
22
25
 
23
26
  logger = logging.getLogger(__name__)
24
27
 
@@ -111,3 +114,26 @@ def check_update_in_background(app: _UpdateApp) -> bool:
111
114
  False,
112
115
  )
113
116
  return False
117
+
118
+
119
+ def show_update_alert(app: _UpdateApp, release: update_checker.ReleaseInfo, alert: Any) -> None:
120
+ from menubar.app import UPDATE_ALERT_BODY_LIMIT
121
+
122
+ alert.setMessageText_(_t(app.language, "update_alert_title", version=release.version))
123
+ alert.setInformativeText_(
124
+ alert_release_notes(release.body, app.language, UPDATE_ALERT_BODY_LIMIT)
125
+ )
126
+ alert.addButtonWithTitle_(_t(app.language, "update_btn_download"))
127
+ alert.addButtonWithTitle_(_t(app.language, "update_btn_later"))
128
+ alert.addButtonWithTitle_(_t(app.language, "update_btn_skip"))
129
+ result = int(alert.runModal())
130
+ action, pref_updates = update_gate.resolve_alert_choice(result, release.version)
131
+ if action == "open":
132
+ webbrowser.open(release.html_url)
133
+ return
134
+
135
+ prefs = _load_preferences()
136
+ prefs.update(pref_updates)
137
+ if action == "dismiss":
138
+ prefs["update_dismissed_at"] = time.time()
139
+ _save_preferences(prefs)
@@ -0,0 +1,39 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-only
2
+ # Copyright (C) 2026 lollapalooza <https://github.com/aqua5230>
3
+ #
4
+ # Part of "usage". Free software licensed under the GNU Affero General Public
5
+ # License v3.0 only; see the LICENSE file for full terms and the warranty disclaimer.
6
+
7
+ """Present post-update notes on the Cocoa main thread."""
8
+
9
+ from __future__ import annotations
10
+
11
+ from collections.abc import Callable, Mapping
12
+ from typing import Any
13
+
14
+ from i18n import _t
15
+ from updates.checker import ReleaseInfo
16
+ from updates.release_notes import alert_release_notes
17
+ from updates.whats_new import start_notice
18
+
19
+
20
+ def show_notice(release: ReleaseInfo, language: str) -> bool:
21
+ from menubar.app import UPDATE_ALERT_BODY_LIMIT
22
+ from menubar.chrome import _make_alert
23
+
24
+ alert = _make_alert()
25
+ alert.setMessageText_(_t(language, "whats_new_title", version=release.version))
26
+ alert.setInformativeText_(alert_release_notes(release.body, language, UPDATE_ALERT_BODY_LIMIT))
27
+ alert.addButtonWithTitle_(_t(language, "whats_new_ok"))
28
+ alert.addButtonWithTitle_(_t(language, "whats_new_open"))
29
+ return int(alert.runModal()) == 1001
30
+
31
+
32
+ def _dispatch(callback: Callable[[], None]) -> None:
33
+ from PyObjCTools.AppHelper import callAfter
34
+
35
+ callAfter(callback)
36
+
37
+
38
+ def start(version: str, snapshot: Mapping[str, Any], language: str) -> None:
39
+ start_notice(version, snapshot, lambda release: show_notice(release, language), _dispatch)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "usage-cli"
3
- version = "0.32.5"
3
+ version = "0.32.6"
4
4
  description = "usage — 在 macOS menu bar 與 Windows 系統匣顯示 Claude Code、Codex、Antigravity 用量的小工具(也提供終端機 TUI)"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -119,7 +119,7 @@ ignore_missing_imports = true
119
119
  # `ignore_missing_imports` is the standard idiom for stubless modules — this
120
120
  # is NOT suppressing a real bug, just declaring the frameworks are untyped.
121
121
  [[tool.mypy.overrides]]
122
- module = ["AppKit", "Foundation", "objc", "Quartz", "UserNotifications", "WebKit"]
122
+ module = ["AppKit", "Foundation", "objc", "PyObjCTools.*", "Quartz", "UserNotifications", "WebKit"]
123
123
  ignore_missing_imports = true
124
124
 
125
125
  # usage_cli's key readers pair a POSIX branch (termios/tty) with a Windows
@@ -145,6 +145,7 @@ def _patch_main_for_win32(monkeypatch: Any, calls: list[dict[str, Any]]) -> None
145
145
  },
146
146
  )(),
147
147
  )
148
+ monkeypatch.setattr(main, "_load_preferences", lambda: {})
148
149
  monkeypatch.setattr(main, "_self_heal", lambda: None)
149
150
  monkeypatch.setattr(main, "run_tui", fake_run_tui)
150
151
  monkeypatch.setattr(main, "_t", lambda key: "fallback")
@@ -4,7 +4,7 @@ from typing import cast
4
4
 
5
5
  import pytest
6
6
 
7
- from updates.release_notes import format_release_notes
7
+ from updates.release_notes import alert_release_notes, format_release_notes, headline_release_notes
8
8
 
9
9
 
10
10
  def test_formats_changelog_style_release_notes() -> None:
@@ -61,3 +61,32 @@ def test_truncates_at_a_whitespace_boundary_with_ellipsis() -> None:
61
61
  )
62
62
  def test_handles_empty_none_like_and_plain_text(body: str, expected: str) -> None:
63
63
  assert format_release_notes(body, 100) == expected
64
+
65
+
66
+ def test_headlines_keep_only_bold_leads() -> None:
67
+ body = (
68
+ "### Changed\n"
69
+ "- **Context color now accounts for window size.** It turns yellow sooner.\n"
70
+ "- **Build:** the app is re-signed. `codesign --verify` passes.\n"
71
+ "- **打包:** 刪除快取後重新簽章。下載的 app 可以通過檢查。\n"
72
+ "- **Faster** startup via `usage status`.\n"
73
+ "- Plain bullet stays whole."
74
+ )
75
+
76
+ assert headline_release_notes(body) == (
77
+ "### Changed\n"
78
+ "- **Context color now accounts for window size.**\n"
79
+ "- **Build:** the app is re-signed.\n"
80
+ "- **打包:**刪除快取後重新簽章。\n"
81
+ "- **Faster** startup via `usage status`.\n"
82
+ "- Plain bullet stays whole."
83
+ )
84
+
85
+
86
+ def test_alert_notes_select_language_then_headline() -> None:
87
+ body = (
88
+ "### Added\n- **English lead.** Detail.\n\n## 繁體中文\n\n### 新增\n- **中文標題。** 細節。"
89
+ )
90
+
91
+ assert alert_release_notes(body, "zh-TW", 100) == "新增\n\n• 中文標題。"
92
+ assert alert_release_notes(body, "ja", 100) == "Added\n\n• English lead."