usage-cli 0.34.15__tar.gz → 0.34.16__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 (294) hide show
  1. {usage_cli-0.34.15/usage_cli.egg-info → usage_cli-0.34.16}/PKG-INFO +1 -1
  2. {usage_cli-0.34.15 → usage_cli-0.34.16}/analyzer/reporter.py +1 -1
  3. {usage_cli-0.34.15 → usage_cli-0.34.16}/i18n.json +10 -10
  4. {usage_cli-0.34.15 → usage_cli-0.34.16}/installer/session_hooks.py +2 -2
  5. {usage_cli-0.34.15 → usage_cli-0.34.16}/loaders/codex_disk_cache.py +10 -2
  6. {usage_cli-0.34.15 → usage_cli-0.34.16}/loaders/codex_events.py +1 -0
  7. {usage_cli-0.34.15 → usage_cli-0.34.16}/loaders/codex_loader.py +8 -4
  8. {usage_cli-0.34.15 → usage_cli-0.34.16}/pyproject.toml +1 -1
  9. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_codex_loader.py +139 -0
  10. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_disk_cache_shards.py +75 -0
  11. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_pricing.py +7 -0
  12. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_usage_statusline_agy.py +39 -1
  13. {usage_cli-0.34.15 → usage_cli-0.34.16}/ui/tables.py +1 -0
  14. {usage_cli-0.34.15 → usage_cli-0.34.16/usage_cli.egg-info}/PKG-INFO +1 -1
  15. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_common/pricing.py +6 -0
  16. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_hooks/usage_statusline_agy.py +21 -16
  17. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_hooks/usage_terse_mode.py +50 -8
  18. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_hooks/usage_terse_reminder.py +17 -15
  19. {usage_cli-0.34.15 → usage_cli-0.34.16}/LICENSE +0 -0
  20. {usage_cli-0.34.15 → usage_cli-0.34.16}/README.md +0 -0
  21. {usage_cli-0.34.15 → usage_cli-0.34.16}/THIRD_PARTY_NOTICES.md +0 -0
  22. {usage_cli-0.34.15 → usage_cli-0.34.16}/adapters/__init__.py +0 -0
  23. {usage_cli-0.34.15 → usage_cli-0.34.16}/adapters/agy.py +0 -0
  24. {usage_cli-0.34.15 → usage_cli-0.34.16}/adapters/claude.py +0 -0
  25. {usage_cli-0.34.15 → usage_cli-0.34.16}/adapters/codex.py +0 -0
  26. {usage_cli-0.34.15 → usage_cli-0.34.16}/adapters/grok.py +0 -0
  27. {usage_cli-0.34.15 → usage_cli-0.34.16}/adapters/muse.py +0 -0
  28. {usage_cli-0.34.15 → usage_cli-0.34.16}/adapters/rate_limits.py +0 -0
  29. {usage_cli-0.34.15 → usage_cli-0.34.16}/adapters/registry.py +0 -0
  30. {usage_cli-0.34.15 → usage_cli-0.34.16}/adapters/types.py +0 -0
  31. {usage_cli-0.34.15 → usage_cli-0.34.16}/analyzer/__init__.py +0 -0
  32. {usage_cli-0.34.15 → usage_cli-0.34.16}/analyzer/aggregator.py +0 -0
  33. {usage_cli-0.34.15 → usage_cli-0.34.16}/analyzer/blocks.py +0 -0
  34. {usage_cli-0.34.15 → usage_cli-0.34.16}/analyzer/diagnoser.py +0 -0
  35. {usage_cli-0.34.15 → usage_cli-0.34.16}/analyzer/diagnosis_snapshot.py +0 -0
  36. {usage_cli-0.34.15 → usage_cli-0.34.16}/analyzer/dispatch_ledger.py +0 -0
  37. {usage_cli-0.34.15 → usage_cli-0.34.16}/analyzer/history_persist.py +0 -0
  38. {usage_cli-0.34.15 → usage_cli-0.34.16}/analyzer/insights.py +0 -0
  39. {usage_cli-0.34.15 → usage_cli-0.34.16}/analyzer/ledger_backfill.py +0 -0
  40. {usage_cli-0.34.15 → usage_cli-0.34.16}/analyzer/ledger_legacy_backfill.py +0 -0
  41. {usage_cli-0.34.15 → usage_cli-0.34.16}/analyzer/persona_loader.py +0 -0
  42. {usage_cli-0.34.15 → usage_cli-0.34.16}/analyzer/subscription.py +0 -0
  43. {usage_cli-0.34.15 → usage_cli-0.34.16}/analyzer/usage_snapshot.py +0 -0
  44. {usage_cli-0.34.15 → usage_cli-0.34.16}/installer/__init__.py +0 -0
  45. {usage_cli-0.34.15 → usage_cli-0.34.16}/installer/claude_pane.py +0 -0
  46. {usage_cli-0.34.15 → usage_cli-0.34.16}/installer/codex_hook_trust.py +0 -0
  47. {usage_cli-0.34.15 → usage_cli-0.34.16}/installer/login_item.py +0 -0
  48. {usage_cli-0.34.15 → usage_cli-0.34.16}/installer/setup_hook.py +0 -0
  49. {usage_cli-0.34.15 → usage_cli-0.34.16}/installer/statusline_settings.py +0 -0
  50. {usage_cli-0.34.15 → usage_cli-0.34.16}/loaders/__init__.py +0 -0
  51. {usage_cli-0.34.15 → usage_cli-0.34.16}/loaders/_shared_cache_file.py +0 -0
  52. {usage_cli-0.34.15 → usage_cli-0.34.16}/loaders/agy_disk_cache.py +0 -0
  53. {usage_cli-0.34.15 → usage_cli-0.34.16}/loaders/agy_loader.py +0 -0
  54. {usage_cli-0.34.15 → usage_cli-0.34.16}/loaders/agy_quota_probe.py +0 -0
  55. {usage_cli-0.34.15 → usage_cli-0.34.16}/loaders/cache_quarantine.py +0 -0
  56. {usage_cli-0.34.15 → usage_cli-0.34.16}/loaders/chromium_cache.py +0 -0
  57. {usage_cli-0.34.15 → usage_cli-0.34.16}/loaders/claude_desktop.py +0 -0
  58. {usage_cli-0.34.15 → usage_cli-0.34.16}/loaders/claude_paths.py +0 -0
  59. {usage_cli-0.34.15 → usage_cli-0.34.16}/loaders/claude_usage.py +0 -0
  60. {usage_cli-0.34.15 → usage_cli-0.34.16}/loaders/codex_fork_replay.py +0 -0
  61. {usage_cli-0.34.15 → usage_cli-0.34.16}/loaders/codex_paths.py +0 -0
  62. {usage_cli-0.34.15 → usage_cli-0.34.16}/loaders/disk_cache_common.py +0 -0
  63. {usage_cli-0.34.15 → usage_cli-0.34.16}/loaders/disk_cache_lifecycle.py +0 -0
  64. {usage_cli-0.34.15 → usage_cli-0.34.16}/loaders/grok_loader.py +0 -0
  65. {usage_cli-0.34.15 → usage_cli-0.34.16}/loaders/grok_quota_probe.py +0 -0
  66. {usage_cli-0.34.15 → usage_cli-0.34.16}/loaders/history_disk_cache.py +0 -0
  67. {usage_cli-0.34.15 → usage_cli-0.34.16}/loaders/history_loader.py +0 -0
  68. {usage_cli-0.34.15 → usage_cli-0.34.16}/loaders/jsonl_limits.py +0 -0
  69. {usage_cli-0.34.15 → usage_cli-0.34.16}/loaders/jsonl_utils.py +0 -0
  70. {usage_cli-0.34.15 → usage_cli-0.34.16}/loaders/muse_loader.py +0 -0
  71. {usage_cli-0.34.15 → usage_cli-0.34.16}/loaders/muse_quota_probe.py +0 -0
  72. {usage_cli-0.34.15 → usage_cli-0.34.16}/main.py +0 -0
  73. {usage_cli-0.34.15 → usage_cli-0.34.16}/menubar/__init__.py +0 -0
  74. {usage_cli-0.34.15 → usage_cli-0.34.16}/menubar/actions.py +0 -0
  75. {usage_cli-0.34.15 → usage_cli-0.34.16}/menubar/agy.py +0 -0
  76. {usage_cli-0.34.15 → usage_cli-0.34.16}/menubar/app.py +0 -0
  77. {usage_cli-0.34.15 → usage_cli-0.34.16}/menubar/chrome.py +0 -0
  78. {usage_cli-0.34.15 → usage_cli-0.34.16}/menubar/fsevents_watch.py +0 -0
  79. {usage_cli-0.34.15 → usage_cli-0.34.16}/menubar/grok.py +0 -0
  80. {usage_cli-0.34.15 → usage_cli-0.34.16}/menubar/manual_refresh.py +0 -0
  81. {usage_cli-0.34.15 → usage_cli-0.34.16}/menubar/menu.py +0 -0
  82. {usage_cli-0.34.15 → usage_cli-0.34.16}/menubar/muse.py +0 -0
  83. {usage_cli-0.34.15 → usage_cli-0.34.16}/menubar/new_badge.py +0 -0
  84. {usage_cli-0.34.15 → usage_cli-0.34.16}/menubar/notify.py +0 -0
  85. {usage_cli-0.34.15 → usage_cli-0.34.16}/menubar/popover.py +0 -0
  86. {usage_cli-0.34.15 → usage_cli-0.34.16}/menubar/prefs.py +0 -0
  87. {usage_cli-0.34.15 → usage_cli-0.34.16}/menubar/quota_countdown.py +0 -0
  88. {usage_cli-0.34.15 → usage_cli-0.34.16}/menubar/refresh.py +0 -0
  89. {usage_cli-0.34.15 → usage_cli-0.34.16}/menubar/state.py +0 -0
  90. {usage_cli-0.34.15 → usage_cli-0.34.16}/menubar/title.py +0 -0
  91. {usage_cli-0.34.15 → usage_cli-0.34.16}/menubar/update.py +0 -0
  92. {usage_cli-0.34.15 → usage_cli-0.34.16}/menubar/whats_new.py +0 -0
  93. {usage_cli-0.34.15 → usage_cli-0.34.16}/panels/__init__.py +0 -0
  94. {usage_cli-0.34.15 → usage_cli-0.34.16}/panels/base.py +0 -0
  95. {usage_cli-0.34.15 → usage_cli-0.34.16}/panels/catalog.py +0 -0
  96. {usage_cli-0.34.15 → usage_cli-0.34.16}/panels/dynamic_height.py +0 -0
  97. {usage_cli-0.34.15 → usage_cli-0.34.16}/panels/panel_scale.py +0 -0
  98. {usage_cli-0.34.15 → usage_cli-0.34.16}/panels/panel_window.py +0 -0
  99. {usage_cli-0.34.15 → usage_cli-0.34.16}/panels/panel_window_state.py +0 -0
  100. {usage_cli-0.34.15 → usage_cli-0.34.16}/panels/payload.py +0 -0
  101. {usage_cli-0.34.15 → usage_cli-0.34.16}/panels/web_panel.py +0 -0
  102. {usage_cli-0.34.15 → usage_cli-0.34.16}/panels/window_drag.py +0 -0
  103. {usage_cli-0.34.15 → usage_cli-0.34.16}/quota/__init__.py +0 -0
  104. {usage_cli-0.34.15 → usage_cli-0.34.16}/quota/agy_window_keeper.py +0 -0
  105. {usage_cli-0.34.15 → usage_cli-0.34.16}/quota/burn_rate.py +0 -0
  106. {usage_cli-0.34.15 → usage_cli-0.34.16}/quota/codex_window_keeper.py +0 -0
  107. {usage_cli-0.34.15 → usage_cli-0.34.16}/quota/keeper_outcome.py +0 -0
  108. {usage_cli-0.34.15 → usage_cli-0.34.16}/quota/notifications.py +0 -0
  109. {usage_cli-0.34.15 → usage_cli-0.34.16}/quota/quota_snapshot.py +0 -0
  110. {usage_cli-0.34.15 → usage_cli-0.34.16}/quota/status_payload.py +0 -0
  111. {usage_cli-0.34.15 → usage_cli-0.34.16}/quota/usage_rate.py +0 -0
  112. {usage_cli-0.34.15 → usage_cli-0.34.16}/quota/window_keeper.py +0 -0
  113. {usage_cli-0.34.15 → usage_cli-0.34.16}/setup.cfg +0 -0
  114. {usage_cli-0.34.15 → usage_cli-0.34.16}/setup_app.py +0 -0
  115. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_adapters_claude.py +0 -0
  116. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_adapters_codex.py +0 -0
  117. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_adapters_rate_limits.py +0 -0
  118. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_aggregator.py +0 -0
  119. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_agy_adapter.py +0 -0
  120. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_agy_disk_cache.py +0 -0
  121. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_agy_loader.py +0 -0
  122. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_agy_quota_probe.py +0 -0
  123. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_agy_window_keeper.py +0 -0
  124. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_analyzer_blocks.py +0 -0
  125. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_analyzer_pipeline.py +0 -0
  126. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_app_version.py +0 -0
  127. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_burn_rate.py +0 -0
  128. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_cache_quarantine.py +0 -0
  129. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_check_release_version.py +0 -0
  130. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_check_upstream.py +0 -0
  131. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_chromium_cache.py +0 -0
  132. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_claude_desktop.py +0 -0
  133. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_claude_expired_windows.py +0 -0
  134. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_claude_pane.py +0 -0
  135. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_claude_paths.py +0 -0
  136. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_cli_lookup.py +0 -0
  137. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_codex_consistency.py +0 -0
  138. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_codex_hook_trust.py +0 -0
  139. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_codex_window_keeper.py +0 -0
  140. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_compact_cards.py +0 -0
  141. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_desktop_fresh_cache.py +0 -0
  142. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_desktop_quota_display.py +0 -0
  143. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_desktop_quota_review.py +0 -0
  144. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_diagnoser.py +0 -0
  145. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_dispatch_ledger.py +0 -0
  146. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_doctor.py +0 -0
  147. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_dynamic_height.py +0 -0
  148. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_fsevents_watch.py +0 -0
  149. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_grok_loader.py +0 -0
  150. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_grok_quota_probe.py +0 -0
  151. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_history_loader.py +0 -0
  152. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_history_persist.py +0 -0
  153. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_hook_scripts_are_standalone.py +0 -0
  154. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_html_report_snapshot.py +0 -0
  155. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_i18n_format_fallback.py +0 -0
  156. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_i18n_key_parity.py +0 -0
  157. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_i18n_packaged_path.py +0 -0
  158. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_insights.py +0 -0
  159. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_jsonl_utils.py +0 -0
  160. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_keeper_menu.py +0 -0
  161. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_keeper_outcome.py +0 -0
  162. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_ledger_backfill.py +0 -0
  163. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_ledger_legacy_backfill.py +0 -0
  164. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_login_item.py +0 -0
  165. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_main.py +0 -0
  166. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_manual_refresh.py +0 -0
  167. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_menubar.py +0 -0
  168. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_menubar_agy.py +0 -0
  169. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_menubar_grok.py +0 -0
  170. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_menubar_muse.py +0 -0
  171. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_menubar_notify.py +0 -0
  172. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_menubar_permission.py +0 -0
  173. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_menubar_prefs.py +0 -0
  174. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_menubar_state.py +0 -0
  175. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_menubar_update.py +0 -0
  176. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_muse_coverage.py +0 -0
  177. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_muse_loader.py +0 -0
  178. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_muse_quota_probe.py +0 -0
  179. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_muse_review_regressions.py +0 -0
  180. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_network.py +0 -0
  181. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_new_badge.py +0 -0
  182. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_packaged_resources.py +0 -0
  183. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_packaging.py +0 -0
  184. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_panel_footer_layout.py +0 -0
  185. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_panel_scale.py +0 -0
  186. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_panel_window_state.py +0 -0
  187. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_panels.py +0 -0
  188. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_panels_lazy_import.py +0 -0
  189. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_persona_loader.py +0 -0
  190. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_project_resolver.py +0 -0
  191. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_quota_aware.py +0 -0
  192. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_quota_label.py +0 -0
  193. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_quota_threshold_menu.py +0 -0
  194. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_report_chart_redesign.py +0 -0
  195. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_report_cold_read.py +0 -0
  196. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_report_daily_chart.py +0 -0
  197. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_report_filter_js.py +0 -0
  198. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_report_headings.py +0 -0
  199. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_report_share_mask.py +0 -0
  200. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_reporter_build_data.py +0 -0
  201. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_service_status.py +0 -0
  202. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_session_resume.py +0 -0
  203. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_setup_app.py +0 -0
  204. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_setup_hook.py +0 -0
  205. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_setup_hook_coexistence.py +0 -0
  206. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_setup_hook_resume.py +0 -0
  207. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_setup_hook_terse.py +0 -0
  208. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_subprocess_utils.py +0 -0
  209. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_subscription.py +0 -0
  210. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_taskbar_menu.py +0 -0
  211. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_taskbar_obstacles.py +0 -0
  212. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_time_utils.py +0 -0
  213. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_tui.py +0 -0
  214. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_tui_sprite.py +0 -0
  215. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_ui_tables.py +0 -0
  216. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_update_checker.py +0 -0
  217. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_update_gate.py +0 -0
  218. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_update_release_notes.py +0 -0
  219. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_usage_cli.py +0 -0
  220. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_usage_client.py +0 -0
  221. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_usage_diagnosis_snapshot.py +0 -0
  222. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_usage_dir_sweeper.py +0 -0
  223. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_usage_lang.py +0 -0
  224. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_usage_logging.py +0 -0
  225. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_usage_notifications.py +0 -0
  226. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_usage_rate.py +0 -0
  227. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_usage_snapshot.py +0 -0
  228. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_usage_snapshot_replay.py +0 -0
  229. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_usage_statusline.py +0 -0
  230. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_usage_statusline_forwarder.py +0 -0
  231. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_usage_statusline_grok.py +0 -0
  232. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_usage_terse_mode.py +0 -0
  233. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_usage_terse_reminder.py +0 -0
  234. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_web_panel_payload.py +0 -0
  235. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_whats_new.py +0 -0
  236. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_wheel_packaging.py +0 -0
  237. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_win_login_item.py +0 -0
  238. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_window_keeper.py +0 -0
  239. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_windows_manifest.py +0 -0
  240. {usage_cli-0.34.15 → usage_cli-0.34.16}/tests/test_wintray.py +0 -0
  241. {usage_cli-0.34.15 → usage_cli-0.34.16}/tui/__init__.py +0 -0
  242. {usage_cli-0.34.15 → usage_cli-0.34.16}/tui/app.py +0 -0
  243. {usage_cli-0.34.15 → usage_cli-0.34.16}/tui/sprite.py +0 -0
  244. {usage_cli-0.34.15 → usage_cli-0.34.16}/ui/__init__.py +0 -0
  245. {usage_cli-0.34.15 → usage_cli-0.34.16}/ui/html_report.py +0 -0
  246. {usage_cli-0.34.15 → usage_cli-0.34.16}/ui/report_charts.py +0 -0
  247. {usage_cli-0.34.15 → usage_cli-0.34.16}/ui/report_daily_chart.py +0 -0
  248. {usage_cli-0.34.15 → usage_cli-0.34.16}/ui/report_filter.py +0 -0
  249. {usage_cli-0.34.15 → usage_cli-0.34.16}/ui/report_ledger.py +0 -0
  250. {usage_cli-0.34.15 → usage_cli-0.34.16}/ui/report_scripts.py +0 -0
  251. {usage_cli-0.34.15 → usage_cli-0.34.16}/ui/report_styles.py +0 -0
  252. {usage_cli-0.34.15 → usage_cli-0.34.16}/updates/__init__.py +0 -0
  253. {usage_cli-0.34.15 → usage_cli-0.34.16}/updates/checker.py +0 -0
  254. {usage_cli-0.34.15 → usage_cli-0.34.16}/updates/gate.py +0 -0
  255. {usage_cli-0.34.15 → usage_cli-0.34.16}/updates/release_notes.py +0 -0
  256. {usage_cli-0.34.15 → usage_cli-0.34.16}/updates/whats_new.py +0 -0
  257. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_app/__init__.py +0 -0
  258. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_app/cli.py +0 -0
  259. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_app/doctor.py +0 -0
  260. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_cli.egg-info/SOURCES.txt +0 -0
  261. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_cli.egg-info/dependency_links.txt +0 -0
  262. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_cli.egg-info/entry_points.txt +0 -0
  263. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_cli.egg-info/requires.txt +0 -0
  264. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_cli.egg-info/top_level.txt +0 -0
  265. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_common/__init__.py +0 -0
  266. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_common/app_version.py +0 -0
  267. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_common/cli_lookup.py +0 -0
  268. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_common/i18n.py +0 -0
  269. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_common/network.py +0 -0
  270. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_common/prefs.py +0 -0
  271. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_common/project_resolver.py +0 -0
  272. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_common/service_status.py +0 -0
  273. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_common/subprocess_utils.py +0 -0
  274. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_common/time_utils.py +0 -0
  275. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_common/usage_dir_sweeper.py +0 -0
  276. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_common/usage_lang.py +0 -0
  277. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_common/usage_logging.py +0 -0
  278. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_hooks/__init__.py +0 -0
  279. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_hooks/usage_quota_aware.py +0 -0
  280. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_hooks/usage_session_resume.py +0 -0
  281. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_hooks/usage_statusline.py +0 -0
  282. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_hooks/usage_statusline_forwarder.py +0 -0
  283. {usage_cli-0.34.15 → usage_cli-0.34.16}/usage_hooks/usage_statusline_grok.py +0 -0
  284. {usage_cli-0.34.15 → usage_cli-0.34.16}/wintray/__init__.py +0 -0
  285. {usage_cli-0.34.15 → usage_cli-0.34.16}/wintray/app.py +0 -0
  286. {usage_cli-0.34.15 → usage_cli-0.34.16}/wintray/login_item.py +0 -0
  287. {usage_cli-0.34.15 → usage_cli-0.34.16}/wintray/menu.py +0 -0
  288. {usage_cli-0.34.15 → usage_cli-0.34.16}/wintray/prefs.py +0 -0
  289. {usage_cli-0.34.15 → usage_cli-0.34.16}/wintray/quota_label.py +0 -0
  290. {usage_cli-0.34.15 → usage_cli-0.34.16}/wintray/taskbar_menu.py +0 -0
  291. {usage_cli-0.34.15 → usage_cli-0.34.16}/wintray/taskbar_obstacles.py +0 -0
  292. {usage_cli-0.34.15 → usage_cli-0.34.16}/wintray/taskbar_overlay.py +0 -0
  293. {usage_cli-0.34.15 → usage_cli-0.34.16}/wintray/watch.py +0 -0
  294. {usage_cli-0.34.15 → usage_cli-0.34.16}/wintray/whats_new.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: usage-cli
3
- Version: 0.34.15
3
+ Version: 0.34.16
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
@@ -45,7 +45,7 @@ _YEAR_WEEKS = 53
45
45
  YEAR_CACHE_PATH = Path(os.path.expanduser("~/.usage/year_cache.json"))
46
46
  YEAR_LEDGER_PATH = YEAR_CACHE_PATH.with_name("year_ledger.json")
47
47
  YEAR_CACHE_TTL_SECONDS = 6 * 3600
48
- _YEAR_CACHE_SCHEMA = 7
48
+ _YEAR_CACHE_SCHEMA = 8
49
49
  _YEAR_LEDGER_SCHEMA = 1
50
50
  _year_ledger_lock = threading.RLock()
51
51
  _YEAR_LEDGER_TRIM_BUFFER_DAYS = 60
@@ -338,8 +338,8 @@
338
338
  "project_butler_tooltip": "開新對話時,自動把上次的進度交給 AI,不用再重講一次。",
339
339
  "terse_mode_menu": "省 token 模式",
340
340
  "terse_mode_tooltip": "開新對話時,自動要求 Claude 回覆更簡短、更白話,以節省輸出 token(有裝 Codex 的話也會一起套用);但程式碼、指令、路徑與錯誤訊息不會被縮減。之後你送的每則訊息也會輕聲提醒 Claude 維持精簡。",
341
- "terse_reminder_instruction": "[精簡模式仍生效:這則回覆保持精簡、用白話;程式碼、指令、路徑、錯誤訊息照舊一字不改;安全警示與不可逆操作確認仍要講完整;使用者明確要求詳細解說時,以使用者當下的要求為準。]",
342
- "terse_mode_instruction": "(這個對話已開啟「精簡模式」:請在這次對話第一則回覆的最前面提一下這件事——如果你同時收到其他要打招呼的指示(例如進度交接),就把「精簡模式已開啟」自然併入那句招呼裡就好,不要另外多開一句;如果沒有其他招呼可以搭,就自己說一行「🐾 已開啟精簡模式,回覆會盡量簡短,繼續吧!」。整個對話都適用。去掉虛詞贅字、客套語、重複鋪陳與不必要的過渡句;用詞挑簡短的(例如「修」不要「針對這個問題實作解決方案」)。精簡是預算,白話是風格:短不等於難懂。挑最口語的說法,能用日常字就不要用術語(例如「先存檔」不要「先持久化」)。非用不可的技術詞,第一次出現時在後面補十個字以內的白話解釋,之後直接用。工具或子代理的輸出不要原文轉貼:先讀懂再用白話重寫成結論,只有程式碼、指令、路徑、錯誤訊息照原文保留。不要比喻。名詞化還原成動詞:進行修改→改、做出決定→決定。刪只預告不給資訊的句子:接下來我要說明、值得注意的是、在深入之前。每句先已知後新知。一句只講一件事,一句盡量在三十字內。用主動句:寫「程式送出金鑰」,不寫「金鑰被程式送出」。同一個東西從頭到尾用同一個詞,叫過「hook」就別再換成「鉤子」。收尾就三件事:做了什麼、成功沒、下一步做什麼。不用裝飾性表格;除了開頭那句招呼,內文不放表情符號。不要複述工具名稱或呼叫過程,但開工具前用一句話說明要做什麼是可以的。不要自創縮寫(例如「設定」別縮成「設」、「函式」別縮成「函」)——這類縮寫斷詞長度跟完整詞一樣,省不到字數,反而讓讀者要多想一下,直接用完整詞更省事也更清楚。程式碼、指令、檔案路徑、錯誤訊息一個字都不能省略或改寫。遇到安全警示、不可逆操作的確認、或多步驟中省略連接詞會有誤讀風險的情況,這幾種要先恢復完整、講清楚,講完再切回精簡語氣。如果使用者明確要求詳細解說、逐步教學,或情境需要完整推理,仍以使用者當下的要求為準,不要因為這個模式而省略關鍵資訊。)",
341
+ "terse_reminder_instruction": "[精簡模式仍生效:這則回覆保持精簡、用白話、讀一遍就懂;程式碼、指令、路徑、錯誤訊息照舊一字不改;安全警示與不可逆操作確認仍要講完整;使用者明確要求詳細解說時,以使用者當下的要求為準。]",
342
+ "terse_mode_instruction": "(這個對話已開啟「精簡模式」:請在這次對話第一則回覆的最前面提一下這件事——如果你同時收到其他要打招呼的指示(例如進度交接),就把「精簡模式已開啟」自然併入那句招呼裡就好,不要另外多開一句;如果沒有其他招呼可以搭,就自己說一行「🐾 已開啟精簡模式,回覆會盡量簡短,繼續吧!」。整個對話都適用。去掉贅字(其實、基本上、真的)、客套語、重複鋪陳與不必要的過渡句;用詞挑簡短的(例如「修」不要「針對這個問題實作解決方案」)。精簡是預算,白話是風格:短不等於難懂。省字的底線是讀一遍就懂、不必回頭重讀;AI 味常常是省字省過頭造成的。讓讀者猜得到下一個詞:詞語照常見的搭配用;別讓東西做只有人會做的事,寫「快取存的是舊資料」,不寫「快取在說謊」;不用賦能、抓手、落地、閉環這類行話;不寫「這不是 X,而是 Y」的翻轉句,除非真的在糾正誤會。別讓讀者一次記太多:主詞和動詞擺近,名詞前面的長修飾語拆到下一句;名詞別疊成一串,寫「改快取過期後的處理方式」,不寫「快取失效處理機制調整」;「因為、所以、但是、如果」這類交代關係的詞,別為了省字拿掉。挑最口語的說法,能用日常字就不要用術語(例如「先存檔」不要「先持久化」)。非用不可的技術詞,第一次出現時在後面補十個字以內的白話解釋,之後直接用。工具或子代理的輸出不要原文轉貼:先讀懂再用白話重寫成結論,只有程式碼、指令、路徑、錯誤訊息照原文保留。不要比喻。名詞化還原成動詞:進行修改→改、做出決定→決定。刪只預告不給資訊的句子:接下來我要說明、值得注意的是、在深入之前。每句先已知後新知。一句只講一件事,一句盡量在三十字內。用主動句:寫「程式送出金鑰」,不寫「金鑰被程式送出」。同一個東西從頭到尾用同一個詞,叫過「hook」就別再換成「鉤子」。收尾就三件事:做了什麼、成功沒、下一步做什麼。不用裝飾性表格;除了開頭那句招呼,內文不放表情符號。不要複述工具名稱或呼叫過程,但開工具前用一句話說明要做什麼是可以的。不要自創縮寫(例如「設定」別縮成「設」、「函式」別縮成「函」)——這類縮寫斷詞長度跟完整詞一樣,省不到字數,反而讓讀者要多想一下,直接用完整詞更省事也更清楚。程式碼、指令、檔案路徑、錯誤訊息一個字都不能省略或改寫。遇到安全警示、不可逆操作的確認、或多步驟中省略連接詞會有誤讀風險的情況,這幾種要先恢復完整、講清楚,講完再切回精簡語氣。如果使用者明確要求詳細解說、逐步教學,或情境需要完整推理,仍以使用者當下的要求為準,不要因為這個模式而省略關鍵資訊。)",
343
343
  "terse_mode_enabled_msg": "已開啟。以後開新的 Claude Code 對話都會自動要求回覆更精簡、更白話(有裝 Codex 的話也會一起套用);程式碼、指令、路徑和錯誤訊息不會被縮減。下次開新對話即生效。",
344
344
  "terse_mode_disabled_msg": "已關閉。開新對話時不再自動要求精簡回覆。",
345
345
  "resume_enabled_restart": "已開啟。以後開新的 Claude Code 對話,會自動記得這個專案上次做到哪——畫面上不會有提示(這是正常的),但 Claude 已經知道。下次開新對話即生效。",
@@ -918,8 +918,8 @@
918
918
  "project_butler_tooltip": "When you open a new session, automatically hand your last progress to the AI — no need to re-explain.",
919
919
  "terse_mode_menu": "Token Saver",
920
920
  "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.",
921
- "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.]",
922
- "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.)",
921
+ "terse_reminder_instruction": "[Terse mode is still on: keep this reply brief, plain-spoken, and clear on a first read; 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.]",
922
+ "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. The floor for cutting: the reader gets it in one pass, without rereading. Much AI-sounding prose comes from cutting below that floor. Keep the next word predictable: use common word pairings; don't give things human actions (\"the cache holds stale data\", not \"the cache is lying\"); don't turn nouns into verbs (\"show the error\", not \"surface the error\"); no buzzwords like leverage or empower; skip the \"it's not X, it's Y\" reversal unless you're correcting a real misunderstanding. Don't make the reader hold too much in mind: keep the verb close to its subject and move long modifiers to the next sentence; unstack noun piles (\"fix how the cache handles expiry\", not \"fix the cache expiry handling logic\"); keep small words that mark structure, like \"that\" (\"she knew that the answer was wrong\") and because/so/but/if. 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.)",
923
923
  "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.",
924
924
  "terse_mode_disabled_msg": "Disabled. New sessions will no longer auto-request terse replies.",
925
925
  "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.",
@@ -1498,8 +1498,8 @@
1498
1498
  "project_butler_tooltip": "开新对话时,自动把上次的进度交给 AI,不用再重讲一次。",
1499
1499
  "terse_mode_menu": "省 token 模式",
1500
1500
  "terse_mode_tooltip": "开新对话时,自动要求 Claude 回复更简短、更白话,以节省输出 token(装了 Codex 的话也会一起套用);但代码、指令、路径和错误信息不会被缩减。之后你发的每则消息也会轻声提醒 Claude 保持精简。",
1501
- "terse_reminder_instruction": "[精简模式仍生效:这则回复保持精简、用白话;代码、指令、路径、错误信息照旧一字不改;安全警示与不可逆操作确认仍要讲完整;用户明确要求详细讲解时,以用户当下的要求为准。]",
1502
- "terse_mode_instruction": "(这个对话已开启「精简模式」:请在这次对话第一则回复的最前面提一下这件事——如果你同时收到其他要打招呼的指示(例如进度交接),就把「精简模式已开启」自然并入那句招呼里就好,不要另外多开一句;如果没有其他招呼可以搭,就自己说一行「🐾 已开启精简模式,回复会尽量简短,继续吧!」。整个对话都适用。去掉虚词赘字、客套语、重复铺陈和不必要的过渡句;用词挑简短的(例如「修」不要「针对这个问题实现解决方案」)。精简是预算,白话是风格:短不等于难懂。挑最口语的说法,能用日常字就不要用术语(例如「先保存」不要「先持久化」)。非用不可的技术词,第一次出现时在后面补十个字以内的白话解释,之后直接用。工具或子代理的输出不要原文转贴:先读懂再用白话重写成结论,只有代码、指令、路径、错误信息照原文保留。不要比喻。名词化还原成动词:进行修改→改、做出决定→决定。删只预告不给信息的句子:接下来我要说明、值得注意的是、在深入之前。每句先已知后新知。一句只讲一件事,一句尽量在三十字内。用主动句:写「程序发送密钥」,不写「密钥被程序发送」。同一个东西从头到尾用同一个词,叫过「hook」就别再换成「钩子」。收尾就三件事:做了什么、成功没、下一步做什么。不用装饰性表格;除了开头那句招呼,正文不放表情符号。不要复述工具名称或调用过程,但开工具前用一句话说明要做什么是可以的。不要自创缩写(例如「配置」别缩成「配」、「函数」别缩成「函」)——这类缩写分词长度跟完整词一样,省不到字数,反而让读者要多想一下,直接用完整词更省事也更清楚。代码、指令、文件路径、错误信息一个字都不能省略或改写。遇到安全警示、不可逆操作的确认、或多步骤中省略连接词会有误读风险的情况,这几种要先恢复完整、讲清楚,讲完再切回精简语气。如果用户明确要求详细讲解、逐步教学,或情境需要完整推理,仍以用户当下的要求为准,不要因为这个模式而省略关键信息。)",
1501
+ "terse_reminder_instruction": "[精简模式仍生效:这则回复保持精简、用白话、读一遍就懂;代码、指令、路径、错误信息照旧一字不改;安全警示与不可逆操作确认仍要讲完整;用户明确要求详细讲解时,以用户当下的要求为准。]",
1502
+ "terse_mode_instruction": "(这个对话已开启「精简模式」:请在这次对话第一则回复的最前面提一下这件事——如果你同时收到其他要打招呼的指示(例如进度交接),就把「精简模式已开启」自然并入那句招呼里就好,不要另外多开一句;如果没有其他招呼可以搭,就自己说一行「🐾 已开启精简模式,回复会尽量简短,继续吧!」。整个对话都适用。去掉赘字(其实、基本上、真的)、客套语、重复铺陈和不必要的过渡句;用词挑简短的(例如「修」不要「针对这个问题实现解决方案」)。精简是预算,白话是风格:短不等于难懂。省字的底线是读一遍就懂、不用回头重读;AI 味常常是省字省过头造成的。让读者猜得到下一个词:词语按常见的搭配用;别让东西做只有人会做的事,写「缓存存的是旧数据」,不写「缓存在撒谎」;不用赋能、抓手、落地、闭环这类黑话;不写「这不是 X,而是 Y」的反转句,除非真的在纠正误会。别让读者一次记太多:主语和动词放近,名词前面的长修饰语拆到下一句;名词别叠成一串,写「改缓存过期后的处理方式」,不写「缓存失效处理机制调整」;「因为、所以、但是、如果」这类交代关系的词,别为了省字删掉。挑最口语的说法,能用日常字就不要用术语(例如「先保存」不要「先持久化」)。非用不可的技术词,第一次出现时在后面补十个字以内的白话解释,之后直接用。工具或子代理的输出不要原文转贴:先读懂再用白话重写成结论,只有代码、指令、路径、错误信息照原文保留。不要比喻。名词化还原成动词:进行修改→改、做出决定→决定。删只预告不给信息的句子:接下来我要说明、值得注意的是、在深入之前。每句先已知后新知。一句只讲一件事,一句尽量在三十字内。用主动句:写「程序发送密钥」,不写「密钥被程序发送」。同一个东西从头到尾用同一个词,叫过「hook」就别再换成「钩子」。收尾就三件事:做了什么、成功没、下一步做什么。不用装饰性表格;除了开头那句招呼,正文不放表情符号。不要复述工具名称或调用过程,但开工具前用一句话说明要做什么是可以的。不要自创缩写(例如「配置」别缩成「配」、「函数」别缩成「函」)——这类缩写分词长度跟完整词一样,省不到字数,反而让读者要多想一下,直接用完整词更省事也更清楚。代码、指令、文件路径、错误信息一个字都不能省略或改写。遇到安全警示、不可逆操作的确认、或多步骤中省略连接词会有误读风险的情况,这几种要先恢复完整、讲清楚,讲完再切回精简语气。如果用户明确要求详细讲解、逐步教学,或情境需要完整推理,仍以用户当下的要求为准,不要因为这个模式而省略关键信息。)",
1503
1503
  "terse_mode_enabled_msg": "已开启。以后开新的 Claude Code 对话都会自动要求回复更精简、更白话(装了 Codex 的话也会一起套用);代码、指令、路径和错误信息不会被缩减。下次开新对话即生效。",
1504
1504
  "terse_mode_disabled_msg": "已关闭。开新对话时不再自动要求精简回复。",
1505
1505
  "resume_enabled_restart": "已开启。以后开新的 Claude Code 对话,会自动记得这个项目上次做到哪——画面上不会有提示(这是正常的),但 Claude 已经知道。下次开新对话即生效。",
@@ -2078,8 +2078,8 @@
2078
2078
  "project_butler_tooltip": "新規セッションを開くと、前回の進捗を自動で AI に引き継ぎます。説明し直す必要はありません。",
2079
2079
  "terse_mode_menu": "トークン節約モード",
2080
2080
  "terse_mode_tooltip": "新規セッションを開くと、Claude に短めで平易な言葉での返答を自動で求めて出力 token を節約します(Codex を導入済みならそちらにも適用されます)。コード、コマンド、パス、エラーメッセージは省略しません。その後もあなたが送る各メッセージで、簡潔さを保つよう軽く念押しされます。",
2081
- "terse_reminder_instruction": "[簡潔モードは引き続き有効:この返信は簡潔に、平易な言葉で;コード、コマンド、パス、エラーメッセージは一字そのまま;セキュリティ警告と不可逆操作の確認は引き続き完全に記述すること;ユーザーが明確に詳しい解説を求めた場合は、その要求を優先してください。]",
2082
- "terse_mode_instruction": "(この会話では「簡潔モード」が有効です。最初の返信の冒頭でこのことに触れてください——すでに他の挨拶(進捗の引き継ぎなど)を述べる予定がある場合は、「簡潔モードが有効」であることをその挨拶に自然に組み込み、別の行を追加しないでください。組み込める挨拶がない場合は、自分で一行「🐾 簡潔モードが有効になりました。返答は短くしていきます!」と述べてください。会話全体に適用されます。前置き、丁寧すぎる言い回し、重複した表現、不必要なつなぎ文を省き、短文や箇条書きで済むなら長い段落に広げないでください。言葉は短い方を選んでください(例:「修正」であって「この問題に対する解決策を実装する」ではない)。簡潔さは予算、平易な言葉はスタイルです。短いからといって難解であってはなりません。専門用語よりも日常的な言葉を選んでください(例:「先に永続化する」ではなく「先に保存する」)。どうしても専門用語が必要な場合は、最初の使用時に10文字以内で平易な説明を補足し、以後はそのまま使用してください。ツールやサブエージェントの出力をそのまま貼り付けないこと。まず理解し、平易な言葉で結論として書き直す。原文のまま残すのはコード、コマンド、パス、エラーメッセージだけ。比喩は不要です。名詞化は動詞に戻す(修正を行う→修正する)。予告だけの文は削る(「これから説明します」「注目すべきは」)。各文は既知が先、新情報が後。一文に一つの内容だけ、一文はおおむね40字以内。能動態で書く(「キーはプログラムによって送られる」ではなく「プログラムがキーを送る」)。同じものには最後まで同じ語を使う(一度「hook」と呼んだら「フック関数」に言い換えない)。最後は、何をしたか、成功したか、次に何をするかの3点だけで締めくくってください。装飾的な表は不要です。冒頭の挨拶を除き、本文に絵文字は入れないでください。ツール名や呼び出し過程の実況は不要ですが、ツールを使う前に何をするかを一言添えるのは構いません。独自の省略語は作らないでください(例:「設定」を「設」に略すなど)——トークナイザー上は完全な語と同じ長さになり、何も節約にならず読み手が余計に解読する手間が増えるだけなので、完全な語のままの方が得です。コード、コマンド、ファイルパス、エラーメッセージは一文字たりとも省略・書き換えしないこと。セキュリティ警告、不可逆操作の確認、接続詞を省くと誤読の恐れがある複数手順の説明では、簡潔さより先に完全な文で明確に伝え、伝え終えたら簡潔モードに戻ってください。ユーザーが明確に詳しい解説や段階的な説明を求めた場合、または状況的に十分な推論が必要な場合は、その要求を優先し、このモードのために重要な情報を落とさないでください。)",
2081
+ "terse_reminder_instruction": "[簡潔モードは引き続き有効:この返信は簡潔に、平易な言葉で、一度読めば分かるように;コード、コマンド、パス、エラーメッセージは一字そのまま;セキュリティ警告と不可逆操作の確認は引き続き完全に記述すること;ユーザーが明確に詳しい解説を求めた場合は、その要求を優先してください。]",
2082
+ "terse_mode_instruction": "(この会話では「簡潔モード」が有効です。最初の返信の冒頭でこのことに触れてください——すでに他の挨拶(進捗の引き継ぎなど)を述べる予定がある場合は、「簡潔モードが有効」であることをその挨拶に自然に組み込み、別の行を追加しないでください。組み込める挨拶がない場合は、自分で一行「🐾 簡潔モードが有効になりました。返答は短くしていきます!」と述べてください。会話全体に適用されます。前置き、丁寧すぎる言い回し、重複した表現、不必要なつなぎ文を省き、短文や箇条書きで済むなら長い段落に広げないでください。言葉は短い方を選んでください(例:「修正」であって「この問題に対する解決策を実装する」ではない)。簡潔さは予算、平易な言葉はスタイルです。短いからといって難解であってはなりません。削ってよいのは、読み返さずに一度で分かるところまでです。AI っぽい文章の多くは、そこを越えて削ることから生まれます。次の語を予想しやすくする:語の組み合わせはよくあるものを使う。物に人間しかしない動作をさせない(「キャッシュが嘘をついている」ではなく「キャッシュに古いデータが残っている」)。エンパワー、ソリューション、シームレスのようなバズワードは使わない。誤解を本当に正すとき以外は、「これは X ではなく Y です」という反転の言い回しを使わない。一度に多くを覚えさせない:主語と述語を近づけ、名詞の前の長い修飾語は次の文に分ける。名詞を積み重ねない(「キャッシュ失効処理機構調整」ではなく「キャッシュが切れた後の処理を直す」)。「なので、だから、でも、もし」のような関係を示す語は、字数を減らすために削らない。専門用語よりも日常的な言葉を選んでください(例:「先に永続化する」ではなく「先に保存する」)。どうしても専門用語が必要な場合は、最初の使用時に10文字以内で平易な説明を補足し、以後はそのまま使用してください。ツールやサブエージェントの出力をそのまま貼り付けないこと。まず理解し、平易な言葉で結論として書き直す。原文のまま残すのはコード、コマンド、パス、エラーメッセージだけ。比喩は不要です。名詞化は動詞に戻す(修正を行う→修正する)。予告だけの文は削る(「これから説明します」「注目すべきは」)。各文は既知が先、新情報が後。一文に一つの内容だけ、一文はおおむね40字以内。能動態で書く(「キーはプログラムによって送られる」ではなく「プログラムがキーを送る」)。同じものには最後まで同じ語を使う(一度「hook」と呼んだら「フック関数」に言い換えない)。最後は、何をしたか、成功したか、次に何をするかの3点だけで締めくくってください。装飾的な表は不要です。冒頭の挨拶を除き、本文に絵文字は入れないでください。ツール名や呼び出し過程の実況は不要ですが、ツールを使う前に何をするかを一言添えるのは構いません。独自の省略語は作らないでください(例:「設定」を「設」に略すなど)——トークナイザー上は完全な語と同じ長さになり、何も節約にならず読み手が余計に解読する手間が増えるだけなので、完全な語のままの方が得です。コード、コマンド、ファイルパス、エラーメッセージは一文字たりとも省略・書き換えしないこと。セキュリティ警告、不可逆操作の確認、接続詞を省くと誤読の恐れがある複数手順の説明では、簡潔さより先に完全な文で明確に伝え、伝え終えたら簡潔モードに戻ってください。ユーザーが明確に詳しい解説や段階的な説明を求めた場合、または状況的に十分な推論が必要な場合は、その要求を優先し、このモードのために重要な情報を落とさないでください。)",
2083
2083
  "terse_mode_enabled_msg": "有効化しました。今後、新しい Claude Code セッションでは返答をより簡潔で平易な言葉にするよう自動で求めます(Codex を導入済みならそちらにも適用されます)。コード、コマンド、パス、エラーメッセージは省略しません。次回セッション開始時に反映されます。",
2084
2084
  "terse_mode_disabled_msg": "無効化しました。新規セッションで簡潔な返答を自動要求しなくなります。",
2085
2085
  "resume_enabled_restart": "有効化しました。今後、新しい Claude Code セッションを開くと、このプロジェクトで前回どこまで進めたかを自動的に把握します。画面に通知は出ません(正常です)が、Claude は把握しています。次回セッション開始時に反映されます。",
@@ -2658,8 +2658,8 @@
2658
2658
  "project_butler_tooltip": "새 세션을 열 때 지난 진행 상황을 AI에게 자동으로 전달합니다. 처음부터 다시 설명할 필요 없어요.",
2659
2659
  "terse_mode_menu": "토큰 절약 모드",
2660
2660
  "terse_mode_tooltip": "새 세션을 열 때 Claude에게 더 짧고 쉬운 말로 답하도록 자동으로 요청해 출력 token을 아낍니다 (Codex가 설치되어 있으면 그쪽에도 적용됩니다). 코드, 명령어, 경로, 오류 메시지는 줄이지 않습니다. 이후 보내는 각 메시지에서도 간결함을 유지하도록 가볍게 다시 일러줍니다.",
2661
- "terse_reminder_instruction": "[간결 모드 계속 적용 중: 이 답변은 짧게, 쉬운 말로; 코드, 명령어, 경로, 오류 메시지는 한 글자 그대로; 보안 경고와 되돌릴 수 없는 작업 확인은 여전히 완전히 적을 것; 사용자가 자세한 설명이나 단계별 안내를 명확히 요청했다면 그 요구를 우선하세요.]",
2662
- "terse_mode_instruction": "(이 대화에는 '간결 모드'가 켜져 있습니다. 첫 답변 맨 앞에서 이 사실을 언급하세요 — 이미 다른 인사(예: 진행 상황 인수인계)를 할 예정이라면, '간결 모드가 켜졌다'는 내용을 그 인사에 자연스럽게 합쳐서 한 줄로 말하고 따로 추가하지 마세요. 합칠 인사가 없다면 직접 한 줄로 \"🐾 간결 모드를 켰어요. 답변을 짧게 이어갈게요!\"라고 말하세요. 대화 전체에 적용됩니다. 빈말, 지나친 격식, 반복되는 서두, 불필요한 연결 문장은 덜어내고, 짧은 문장이나 목록으로 충분하면 긴 단락으로 늘이지 마세요. 단어는 짧은 쪽을 고르세요 (예: \"이 문제에 대한 해결책을 구현하다\" 대신 \"고치다\"). 간결함은 예산이고, 쉬운 말은 스타일입니다. 짧다고 해서 이해하기 어려워서는 안 됩니다. 전문 용어보다 일상적인 단어를 고르세요(예: \"먼저 영속화하다\" 대신 \"먼저 저장하다\"). 전문 용어를 꼭 써야 한다면, 처음 사용할 때 10글자 이내로 쉬운 설명을 덧붙이고 그 뒤로는 그냥 쓰세요. 도구나 서브에이전트의 출력을 그대로 붙여넣지 말 것: 먼저 이해한 뒤 쉬운 말로 결론으로 다시 쓰고, 코드·명령어·경로·오류 메시지만 원문 그대로 둔다. 비유는 빼주세요. 명사화는 동사로 (수정을 진행한다 → 고친다). 예고만 하는 문장은 삭제 (\"이제 설명하겠습니다\", \"주목할 점은\"). 각 문장은 아는 것 먼저, 새 정보 나중. 한 문장에는 한 가지 내용만, 문장은 대략 40자 이내로. 능동문으로 쓰세요 (\"키가 프로그램에 의해 전송된다\" 대신 \"프로그램이 키를 보낸다\"). 같은 대상은 끝까지 같은 단어로 부르세요 (한 번 \"hook\"이라고 했으면 \"훅 함수\"로 바꾸지 않기). 마무리는 무엇을 했는지, 성공했는지, 다음에 무엇을 할 것인지 이 3가지만 적으세요. 장식용 표는 넣지 마세요. 첫 인사말을 제외하면 본문에 이모지는 넣지 마세요. 도구 이름이나 호출 과정을 중계할 필요는 없지만, 도구를 쓰기 전에 무엇을 할지 한 줄로 말하는 것은 괜찮습니다. 임의로 줄임말을 만들지 마세요 (예: \"설정\"을 \"설\"로 줄이는 식) — 토크나이저 상으로는 완전한 단어와 길이가 같아서 절약되는 게 없고 읽는 사람만 더 해석해야 하니, 완전한 단어를 쓰는 편이 더 낫습니다. 코드, 명령어, 파일 경로, 오류 메시지는 한 글자도 생략하거나 바꾸면 안 됩니다. 보안 경고, 되돌릴 수 없는 작업의 확인, 접속사를 생략하면 오독 위험이 있는 다단계 설명에서는 간결함보다 먼저 완전하고 명확하게 전달한 뒤, 전달이 끝나면 다시 간결 모드로 돌아가세요. 사용자가 자세한 설명이나 단계별 안내를 명확히 요청했거나, 상황상 충분한 추론이 꼭 필요하다면 그 요구를 우선하고, 이 모드 때문에 핵심 정보를 빼먹지 마세요.)",
2661
+ "terse_reminder_instruction": "[간결 모드 계속 적용 중: 이 답변은 짧게, 쉬운 말로, 한 번 읽으면 이해되게; 코드, 명령어, 경로, 오류 메시지는 한 글자 그대로; 보안 경고와 되돌릴 수 없는 작업 확인은 여전히 완전히 적을 것; 사용자가 자세한 설명이나 단계별 안내를 명확히 요청했다면 그 요구를 우선하세요.]",
2662
+ "terse_mode_instruction": "(이 대화에는 '간결 모드'가 켜져 있습니다. 첫 답변 맨 앞에서 이 사실을 언급하세요 — 이미 다른 인사(예: 진행 상황 인수인계)를 할 예정이라면, '간결 모드가 켜졌다'는 내용을 그 인사에 자연스럽게 합쳐서 한 줄로 말하고 따로 추가하지 마세요. 합칠 인사가 없다면 직접 한 줄로 \"🐾 간결 모드를 켰어요. 답변을 짧게 이어갈게요!\"라고 말하세요. 대화 전체에 적용됩니다. 빈말, 지나친 격식, 반복되는 서두, 불필요한 연결 문장은 덜어내고, 짧은 문장이나 목록으로 충분하면 긴 단락으로 늘이지 마세요. 단어는 짧은 쪽을 고르세요 (예: \"이 문제에 대한 해결책을 구현하다\" 대신 \"고치다\"). 간결함은 예산이고, 쉬운 말은 스타일입니다. 짧다고 해서 이해하기 어려워서는 안 됩니다. 줄여도 되는 한계는 다시 읽지 않고 한 번에 이해되는 데까지입니다. AI 같은 글은 흔히 그 한계를 넘어 줄이는 데서 생깁니다. 다음 단어를 예측할 수 있게 쓰세요: 단어는 흔히 쓰는 조합으로 쓰고, 사물에게 사람만 하는 행동을 시키지 마세요(\"캐시가 거짓말을 한다\" 대신 \"캐시에 오래된 데이터가 남아 있다\"). 시너지, 솔루션, 레버리지 같은 유행어는 쓰지 말고, 정말 오해를 바로잡을 때가 아니면 \"이것은 X가 아니라 Y입니다\" 식의 뒤집는 문장도 쓰지 마세요. 한 번에 너무 많이 기억하게 하지 마세요: 주어와 서술어를 가깝게 두고, 명사 앞의 긴 수식어는 다음 문장으로 나누세요. 명사를 줄줄이 쌓지 마세요(\"캐시 만료 처리 메커니즘 조정\" 대신 \"캐시가 만료된 뒤 처리 방식을 고치다\"). \"왜냐하면, 그래서, 하지만, 만약\" 같은 관계를 알려 주는 말은 글자 수를 줄이려고 빼지 마세요. 전문 용어보다 일상적인 단어를 고르세요(예: \"먼저 영속화하다\" 대신 \"먼저 저장하다\"). 전문 용어를 꼭 써야 한다면, 처음 사용할 때 10글자 이내로 쉬운 설명을 덧붙이고 그 뒤로는 그냥 쓰세요. 도구나 서브에이전트의 출력을 그대로 붙여넣지 말 것: 먼저 이해한 뒤 쉬운 말로 결론으로 다시 쓰고, 코드·명령어·경로·오류 메시지만 원문 그대로 둔다. 비유는 빼주세요. 명사화는 동사로 (수정을 진행한다 → 고친다). 예고만 하는 문장은 삭제 (\"이제 설명하겠습니다\", \"주목할 점은\"). 각 문장은 아는 것 먼저, 새 정보 나중. 한 문장에는 한 가지 내용만, 문장은 대략 40자 이내로. 능동문으로 쓰세요 (\"키가 프로그램에 의해 전송된다\" 대신 \"프로그램이 키를 보낸다\"). 같은 대상은 끝까지 같은 단어로 부르세요 (한 번 \"hook\"이라고 했으면 \"훅 함수\"로 바꾸지 않기). 마무리는 무엇을 했는지, 성공했는지, 다음에 무엇을 할 것인지 이 3가지만 적으세요. 장식용 표는 넣지 마세요. 첫 인사말을 제외하면 본문에 이모지는 넣지 마세요. 도구 이름이나 호출 과정을 중계할 필요는 없지만, 도구를 쓰기 전에 무엇을 할지 한 줄로 말하는 것은 괜찮습니다. 임의로 줄임말을 만들지 마세요 (예: \"설정\"을 \"설\"로 줄이는 식) — 토크나이저 상으로는 완전한 단어와 길이가 같아서 절약되는 게 없고 읽는 사람만 더 해석해야 하니, 완전한 단어를 쓰는 편이 더 낫습니다. 코드, 명령어, 파일 경로, 오류 메시지는 한 글자도 생략하거나 바꾸면 안 됩니다. 보안 경고, 되돌릴 수 없는 작업의 확인, 접속사를 생략하면 오독 위험이 있는 다단계 설명에서는 간결함보다 먼저 완전하고 명확하게 전달한 뒤, 전달이 끝나면 다시 간결 모드로 돌아가세요. 사용자가 자세한 설명이나 단계별 안내를 명확히 요청했거나, 상황상 충분한 추론이 꼭 필요하다면 그 요구를 우선하고, 이 모드 때문에 핵심 정보를 빼먹지 마세요.)",
2663
2663
  "terse_mode_enabled_msg": "활성화했습니다. 이제 새 Claude Code 세션에서는 더 간결하고 쉬운 말로 답하도록 자동으로 요청합니다 (Codex가 설치되어 있으면 그쪽에도 적용됩니다). 코드, 명령어, 경로, 오류 메시지는 줄이지 않습니다. 다음 세션 시작 시 적용됩니다.",
2664
2664
  "terse_mode_disabled_msg": "비활성화했습니다. 새 세션에서 더 이상 간결한 답변을 자동 요청하지 않습니다.",
2665
2665
  "resume_enabled_restart": "활성화했습니다. 이제 새 Claude Code 세션을 시작하면 이 프로젝트에서 지난번에 어디까지 했는지 자동으로 파악합니다. 화면에는 안내가 표시되지 않지만(정상입니다) Claude는 알고 있습니다. 다음 세션 시작 시 적용됩니다.",
@@ -74,7 +74,7 @@ _RESUME_DIAGNOSIS_CAUSE_KEYS = (
74
74
  )
75
75
  TERSE_HOOK_TARGET = Path(os.path.expanduser("~/.claude/usage-terse-mode.py"))
76
76
  TERSE_PROMPT_SIDECAR = Path(os.path.expanduser("~/.claude/usage-terse-prompt.json"))
77
- TERSE_HOOK_VERSION = "1.7"
77
+ TERSE_HOOK_VERSION = "1.8"
78
78
  TERSE_MATCHER = "startup|clear"
79
79
  TERSE_LANGS = ("zh-TW", "zh-CN", "en", "ja", "ko")
80
80
  _TERSE_MARKER = "usage-terse-mode"
@@ -89,7 +89,7 @@ _FEATURES_HOOKS_REGEX = re.compile(r"(?m)^[ \t]*hooks\s*=\s*[A-Za-z0-9_]+")
89
89
  # terse hook. Re-injects a one-line nudge on every prompt so the terse style holds across a
90
90
  # long conversation. Claude Code only — Codex CLI has no UserPromptSubmit equivalent.
91
91
  TERSE_REMINDER_HOOK_TARGET = Path(os.path.expanduser("~/.claude/usage-terse-reminder.py"))
92
- TERSE_REMINDER_HOOK_VERSION = "1.2"
92
+ TERSE_REMINDER_HOOK_VERSION = "1.3"
93
93
  TERSE_REMINDER_MATCHER = ""
94
94
  _TERSE_REMINDER_MARKER = "usage-terse-reminder"
95
95
  _TERSE_REMINDER_MARKERS = (_TERSE_REMINDER_MARKER, "usage_terse_reminder")
@@ -116,6 +116,9 @@ def seed_caches(
116
116
  size = file_data["size"]
117
117
  session_id = file_data["session_id"]
118
118
  forked_from_id = file_data["forked_from_id"]
119
+ is_thread_spawn = file_data["is_thread_spawn"]
120
+ if not isinstance(is_thread_spawn, bool):
121
+ continue
119
122
  entries_data = file_data["entries"]
120
123
  confirmed_offset = int(file_data.get("confirmed_offset", 0))
121
124
  digest_hex = file_data.get("confirmed_prefix_digest", "")
@@ -128,7 +131,11 @@ def seed_caches(
128
131
  file_info_cache[path] = (
129
132
  mtime,
130
133
  size,
131
- _SessionFileInfo(session_id=session_id, forked_from_id=forked_from_id),
134
+ _SessionFileInfo(
135
+ session_id=session_id,
136
+ forked_from_id=forked_from_id,
137
+ is_thread_spawn=is_thread_spawn,
138
+ ),
132
139
  )
133
140
  if entries_data is None:
134
141
  continue
@@ -184,7 +191,7 @@ def flush_caches(
184
191
  shards: list[dict[str, Any]] = [{} for _ in range(_SHARD_COUNT)]
185
192
  for path, entry in jsonl_cache.items():
186
193
  info = file_info_cache.get(path)
187
- if entry.replay_cache_key is not None:
194
+ if entry.replay_cache_key is not None or (info is not None and info[2].forked_from_id):
188
195
  if info is None:
189
196
  continue
190
197
  entries_data = None
@@ -205,6 +212,7 @@ def flush_caches(
205
212
  "size": entry.size,
206
213
  "session_id": session_id,
207
214
  "forked_from_id": forked_from_id,
215
+ "is_thread_spawn": info[2].is_thread_spawn if info is not None else False,
208
216
  "entries": entries_data,
209
217
  "confirmed_offset": entry.confirmed_offset,
210
218
  "confirmed_prefix_digest": entry.confirmed_prefix_digest.hex(),
@@ -32,6 +32,7 @@ class _ThreadMetadata:
32
32
  class _SessionFileInfo:
33
33
  session_id: str = ""
34
34
  forked_from_id: str = ""
35
+ is_thread_spawn: bool = False
35
36
 
36
37
 
37
38
  @dataclass(frozen=True, slots=True)
@@ -163,7 +163,7 @@ def _sqlite_file_fingerprint(path: Path) -> _SqliteFileFingerprint:
163
163
 
164
164
  # Disk cache for JSONL parsing results. Schema version must be bumped when the
165
165
  # serialization format or parsing logic changes incompatibly.
166
- _CODEX_JSONL_CACHE_SCHEMA = 6
166
+ _CODEX_JSONL_CACHE_SCHEMA = 7
167
167
  JSONL_CACHE_PATH = Path(os.path.expanduser("~/.usage/codex_jsonl_cache.json"))
168
168
 
169
169
  # Module-level flag to ensure seed loading happens exactly once.
@@ -410,6 +410,8 @@ def _read_session_file_info_uncached(path: Path) -> _SessionFileInfo:
410
410
  return _SessionFileInfo(
411
411
  session_id=_as_str(payload.get("id")),
412
412
  forked_from_id=_as_str(payload.get("forked_from_id")),
413
+ is_thread_spawn="thread_spawn"
414
+ in _as_dict(_as_dict(payload.get("source")).get("subagent")),
413
415
  )
414
416
  except (OSError, UnicodeDecodeError):
415
417
  return _SessionFileInfo()
@@ -1114,7 +1116,8 @@ def _fork_replay_boundary(
1114
1116
  match_count,
1115
1117
  )
1116
1118
 
1117
- if child_events and best_match == 0:
1119
+ # Newer spawned threads inherit context without the parent's usage events.
1120
+ if child_events and best_match == 0 and not info.is_thread_spawn:
1118
1121
  result = (None, None)
1119
1122
  _cache_fork_replay_boundary(path, lookup_key, result)
1120
1123
  return result
@@ -1318,6 +1321,9 @@ def _parse_jsonl(
1318
1321
  logger.warning("failed to parse codex session %s: %s", path, exc)
1319
1322
  return []
1320
1323
 
1324
+ if replay_boundary is None:
1325
+ return []
1326
+
1321
1327
  cache_entry = _jsonl_cache.get(path)
1322
1328
  # An entry without model provenance must be rebuilt from the source.
1323
1329
  if cache_entry is not None and cache_entry.fallback_entry_count is None:
@@ -1338,8 +1344,6 @@ def _parse_jsonl(
1338
1344
  return [entry for entry in cached_entries if entry.timestamp >= cutoff]
1339
1345
 
1340
1346
  info = file_info or _read_session_file_info(path)
1341
- if replay_boundary is None:
1342
- return []
1343
1347
 
1344
1348
  if not info.forked_from_id and replay_boundary == 0 and replay_cache_key is None:
1345
1349
  refreshed = _refresh_linear_jsonl_cache(path, st, info.session_id, models, cache_entry)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "usage-cli"
3
- version = "0.34.15"
3
+ version = "0.34.16"
4
4
  description = "usage — 在 macOS menu bar 與 Windows 系統匣顯示 Claude Code、Codex、Antigravity 用量的小工具(也提供終端機 TUI)"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -113,6 +113,7 @@ def _write_fork_session(
113
113
  timestamp: str,
114
114
  replay_events: list[dict[str, Any]],
115
115
  own_events: list[tuple[str, dict[str, Any]]],
116
+ source: str | dict[str, Any] = "exec",
116
117
  ) -> None:
117
118
  lines = [
118
119
  {
@@ -123,6 +124,7 @@ def _write_fork_session(
123
124
  "forked_from_id": parent_session_id,
124
125
  "timestamp": timestamp,
125
126
  "cwd": "/tmp/fork",
127
+ "source": source,
126
128
  },
127
129
  },
128
130
  {
@@ -132,6 +134,7 @@ def _write_fork_session(
132
134
  "id": parent_session_id,
133
135
  "timestamp": "2026-01-01T00:00:00+00:00",
134
136
  "cwd": "/tmp/parent",
137
+ "source": "exec",
135
138
  },
136
139
  },
137
140
  ]
@@ -583,6 +586,141 @@ def test_load_entries_handles_duplicate_snapshots_across_multiple_forks(
583
586
  assert [entry.total_tokens for entry in entries] == [130, 60, 68, 69]
584
587
 
585
588
 
589
+ @pytest.mark.parametrize("cold_start", [False, True])
590
+ def test_load_entries_counts_spawn_usage_without_replayed_prefix(
591
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path, cold_start: bool
592
+ ) -> None:
593
+ sessions_dir = tmp_path / "sessions"
594
+ monkeypatch.setattr(codex_loader, "SESSIONS_DIR", sessions_dir)
595
+ timestamp = "2026-11-10T00:00:00+00:00"
596
+ parent_path = sessions_dir / "parent.jsonl"
597
+ _write_session_with_usage_events(
598
+ parent_path,
599
+ session_id="parent",
600
+ events=[(timestamp, {"input_tokens": 2_000_000, "output_tokens": 10})],
601
+ )
602
+ _write_fork_session(
603
+ sessions_dir / "spawn.jsonl",
604
+ session_id="spawn",
605
+ parent_session_id="parent",
606
+ timestamp=timestamp,
607
+ source={"subagent": {"thread_spawn": {"parent_thread_id": "parent", "depth": 1}}},
608
+ replay_events=[],
609
+ own_events=[
610
+ (
611
+ timestamp,
612
+ {
613
+ "input_tokens": total - 31,
614
+ "cached_input_tokens": 1000,
615
+ "output_tokens": 31,
616
+ "total_tokens": total,
617
+ },
618
+ )
619
+ for total in (26731, 59927, 97631)
620
+ ],
621
+ )
622
+
623
+ entries = codex_loader.load_entries()
624
+ if cold_start:
625
+ codex_loader._flush_caches_to_disk(force=True)
626
+ codex_loader._jsonl_cache.clear()
627
+ codex_loader._file_info_cache.clear()
628
+ codex_loader._fork_replay_cache.clear()
629
+ monkeypatch.setattr(codex_loader, "_disk_cache_seeded", False)
630
+ entries = codex_loader.load_entries()
631
+
632
+ child_entries = [entry for entry in entries if entry.session_id == "spawn"]
633
+ assert [entry.total_tokens for entry in child_entries] == [26731, 33196, 37704]
634
+ assert sum(entry.total_tokens for entry in child_entries) == 97631
635
+
636
+ # A previously parsed child must also be excluded if its parent disappears.
637
+ parent_path.rename(tmp_path / "moved-parent.jsonl")
638
+ assert codex_loader.load_entries() == []
639
+
640
+
641
+ def test_load_entries_excludes_spawn_replayed_prefix(
642
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
643
+ ) -> None:
644
+ sessions_dir = tmp_path / "sessions"
645
+ monkeypatch.setattr(codex_loader, "SESSIONS_DIR", sessions_dir)
646
+ timestamp = "2026-11-10T00:00:00+00:00"
647
+ parent_usage = [
648
+ {"input_tokens": 100, "cached_input_tokens": 20, "output_tokens": 30},
649
+ {"input_tokens": 150, "cached_input_tokens": 30, "output_tokens": 40},
650
+ ]
651
+ _write_session_with_usage_events(
652
+ sessions_dir / "parent.jsonl",
653
+ session_id="parent",
654
+ events=[(timestamp, usage) for usage in parent_usage],
655
+ )
656
+ _write_fork_session(
657
+ sessions_dir / "spawn.jsonl",
658
+ session_id="spawn",
659
+ parent_session_id="parent",
660
+ timestamp=timestamp,
661
+ source={"subagent": {"thread_spawn": {"parent_thread_id": "parent"}}},
662
+ replay_events=parent_usage,
663
+ own_events=[
664
+ (timestamp, {"input_tokens": 60, "cached_input_tokens": 10, "output_tokens": 8}),
665
+ (timestamp, {"input_tokens": 90, "cached_input_tokens": 20, "output_tokens": 12}),
666
+ ],
667
+ )
668
+
669
+ entries = codex_loader.load_entries()
670
+
671
+ assert [entry.total_tokens for entry in entries if entry.session_id == "spawn"] == [68, 34]
672
+
673
+
674
+ def test_load_entries_skips_user_fork_without_matching_prefix(
675
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
676
+ ) -> None:
677
+ sessions_dir = tmp_path / "sessions"
678
+ monkeypatch.setattr(codex_loader, "SESSIONS_DIR", sessions_dir)
679
+ timestamp = "2026-11-10T00:00:00+00:00"
680
+ _write_session_with_usage_events(
681
+ sessions_dir / "parent.jsonl",
682
+ session_id="parent",
683
+ events=[(timestamp, {"input_tokens": 100, "output_tokens": 30})],
684
+ )
685
+ _write_fork_session(
686
+ sessions_dir / "fork.jsonl",
687
+ session_id="fork",
688
+ parent_session_id="parent",
689
+ timestamp=timestamp,
690
+ replay_events=[],
691
+ own_events=[(timestamp, {"input_tokens": 60, "output_tokens": 8})],
692
+ )
693
+
694
+ entries = codex_loader.load_entries()
695
+
696
+ assert [entry.session_id for entry in entries] == ["parent"]
697
+ fork_path = sessions_dir / "fork.jsonl"
698
+ assert codex_loader._fork_replay_boundary(
699
+ fork_path,
700
+ codex_loader._read_session_file_info(fork_path),
701
+ [sessions_dir / "parent.jsonl"],
702
+ ) == (None, None)
703
+
704
+
705
+ def test_load_entries_skips_spawn_when_parent_is_missing(
706
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
707
+ ) -> None:
708
+ sessions_dir = tmp_path / "sessions"
709
+ monkeypatch.setattr(codex_loader, "SESSIONS_DIR", sessions_dir)
710
+ timestamp = "2026-11-10T00:00:00+00:00"
711
+ _write_fork_session(
712
+ sessions_dir / "spawn.jsonl",
713
+ session_id="spawn",
714
+ parent_session_id="missing-parent",
715
+ timestamp=timestamp,
716
+ source={"subagent": {"thread_spawn": {"parent_thread_id": "missing-parent"}}},
717
+ replay_events=[],
718
+ own_events=[(timestamp, {"input_tokens": 26700, "output_tokens": 31})],
719
+ )
720
+
721
+ assert codex_loader.load_entries() == []
722
+
723
+
586
724
  def test_load_entries_skips_fork_jsonl_when_parent_replay_source_is_missing(
587
725
  monkeypatch: pytest.MonkeyPatch, tmp_path: Path
588
726
  ) -> None:
@@ -2463,6 +2601,7 @@ def test_disk_cache_seed_loads_on_cold_start(
2463
2601
  "size": 1000,
2464
2602
  "session_id": "test-session",
2465
2603
  "forked_from_id": "",
2604
+ "is_thread_spawn": False,
2466
2605
  "confirmed_offset": 1000,
2467
2606
  "confirmed_prefix_digest": "abcd",
2468
2607
  "parse_state": {
@@ -283,6 +283,81 @@ def test_codex_legacy_single_file_is_deleted(tmp_path: Path) -> None:
283
283
  assert not info
284
284
 
285
285
 
286
+ @pytest.mark.parametrize("is_thread_spawn", [False, True])
287
+ def test_codex_file_info_round_trips_spawn_flag_without_replay_key(
288
+ tmp_path: Path, is_thread_spawn: bool
289
+ ) -> None:
290
+ cache_path = tmp_path / "codex.json"
291
+ source = tmp_path / "session.jsonl"
292
+ source.write_text("", encoding="utf-8")
293
+ metadata = _SessionFileInfo(
294
+ session_id="child",
295
+ forked_from_id="parent",
296
+ is_thread_spawn=is_thread_spawn,
297
+ )
298
+ entries = OrderedDict([(source, _codex_entry(source))])
299
+ info = OrderedDict([(source, (1.0, 10, metadata))])
300
+ schema = codex_loader._CODEX_JSONL_CACHE_SCHEMA
301
+ sqlite_cache = codex_loader._SqliteLogCache()
302
+
303
+ codex_disk_cache.flush_caches(cache_path, schema, entries, info, sqlite_cache)
304
+ shard_path = codex_disk_cache._shard_path(cache_path, codex_disk_cache._shard_index(source))
305
+ file_data = json.loads(shard_path.read_text(encoding="utf-8"))["files"][str(source)]
306
+ assert file_data["is_thread_spawn"] is is_thread_spawn
307
+ assert file_data["forked_from_id"] == "parent"
308
+ assert file_data["entries"] is None
309
+
310
+ seeded: OrderedDict[Path, codex_loader._JsonlCacheEntry] = OrderedDict()
311
+ seeded_info: OrderedDict[Path, tuple[float, int, _SessionFileInfo]] = OrderedDict()
312
+ codex_disk_cache.seed_caches(cache_path, schema, 4096, seeded, seeded_info, sqlite_cache)
313
+
314
+ assert seeded_info[source][2] == metadata
315
+ assert not seeded
316
+
317
+
318
+ @pytest.mark.parametrize("stale_schema", [False, True])
319
+ def test_codex_seed_does_not_assume_missing_spawn_flag_is_false(
320
+ tmp_path: Path, stale_schema: bool
321
+ ) -> None:
322
+ cache_path = tmp_path / "codex.json"
323
+ source = tmp_path / "session.jsonl"
324
+ source.write_text(
325
+ json.dumps(
326
+ {
327
+ "type": "session_meta",
328
+ "payload": {
329
+ "id": "child",
330
+ "forked_from_id": "parent",
331
+ "source": {"subagent": {"thread_spawn": {"parent_thread_id": "parent"}}},
332
+ },
333
+ }
334
+ ),
335
+ encoding="utf-8",
336
+ )
337
+ entries = OrderedDict([(source, _codex_entry(source))])
338
+ stat = source.stat()
339
+ info = OrderedDict(
340
+ [(source, (stat.st_mtime, stat.st_size, _SessionFileInfo("child", "parent", True)))]
341
+ )
342
+ schema = codex_loader._CODEX_JSONL_CACHE_SCHEMA
343
+ sqlite_cache = codex_loader._SqliteLogCache()
344
+ codex_disk_cache.flush_caches(cache_path, schema, entries, info, sqlite_cache)
345
+ shard_path = codex_disk_cache._shard_path(cache_path, codex_disk_cache._shard_index(source))
346
+ payload = json.loads(shard_path.read_text(encoding="utf-8"))
347
+ del payload["files"][str(source)]["is_thread_spawn"]
348
+ if stale_schema:
349
+ payload["schema_version"] = schema - 1
350
+ shard_path.write_text(json.dumps(payload), encoding="utf-8")
351
+ seeded: OrderedDict[Path, codex_loader._JsonlCacheEntry] = OrderedDict()
352
+ seeded_info: OrderedDict[Path, tuple[float, int, _SessionFileInfo]] = OrderedDict()
353
+
354
+ codex_disk_cache.seed_caches(cache_path, schema, 4096, seeded, seeded_info, sqlite_cache)
355
+
356
+ assert not seeded
357
+ assert not seeded_info
358
+ assert codex_loader._read_session_file_info_uncached(source).is_thread_spawn is True
359
+
360
+
286
361
  def test_codex_seed_skips_missing_but_keeps_stat_errors(
287
362
  monkeypatch: pytest.MonkeyPatch, tmp_path: Path
288
363
  ) -> None:
@@ -399,6 +399,13 @@ def test_fallback_pricing_contains_expected_models() -> None:
399
399
  "cache_creation_input_token_cost": 12.5e-6,
400
400
  "cache_read_input_token_cost": 0.25e-6,
401
401
  }
402
+ assert fallback["claude-fable-5-2"] == {
403
+ "input_cost_per_token": 8e-6,
404
+ "output_cost_per_token": 40e-6,
405
+ "cache_creation_input_token_cost": 10e-6,
406
+ "cache_read_input_token_cost": 0.2e-6,
407
+ }
408
+ assert pricing._resolve_model_key("claude-fable-5-1", fallback) == "claude-fable-5-1"
402
409
  assert fallback["claude-opus-4-6"] == {
403
410
  "input_cost_per_token": 5e-6,
404
411
  "output_cost_per_token": 25e-6,
@@ -123,7 +123,9 @@ def test_render_fixture_contains_expected_status(monkeypatch: pytest.MonkeyPatch
123
123
  assert "Context:" in output
124
124
  assert "0%" in output
125
125
  assert "Gemini 3.6 Flash/Quick" in output
126
- assert "\n" not in output
126
+ first, second = output.split("\n")
127
+ assert "project" in first and "60%" in first
128
+ assert "Context:" in second and "Gemini 3.6 Flash/Quick" in second
127
129
 
128
130
 
129
131
  @pytest.mark.parametrize(
@@ -636,3 +638,39 @@ def test_fresh_cache_does_not_mix_cli_weekly(monkeypatch: pytest.MonkeyPatch) ->
636
638
  parts = usage_statusline_agy._quota_parts(data, 4)
637
639
  assert len(parts) == 1
638
640
  assert "Weekly" not in parts[0][0]
641
+
642
+
643
+ @pytest.mark.parametrize(
644
+ ("lang", "expected_five", "expected_weekly"),
645
+ [
646
+ ("zh-TW", "14:40(剩2h", "11/14 18:00(剩4d"),
647
+ ("en", "14:40(2h", "11/14 18:00(4d"),
648
+ ],
649
+ )
650
+ def test_render_shows_reset_clock_like_claude_statusline(
651
+ monkeypatch: pytest.MonkeyPatch, lang: str, expected_five: str, expected_weekly: str
652
+ ) -> None:
653
+ fixed_now = datetime(2026, 11, 10, 11, 56)
654
+
655
+ class _FixedDatetime(datetime):
656
+ @classmethod
657
+ def now(cls, tz: Any = None) -> Any:
658
+ return fixed_now if tz is None else fixed_now.astimezone(tz)
659
+
660
+ monkeypatch.setattr(usage_statusline_agy, "datetime", _FixedDatetime)
661
+ monkeypatch.setenv("USAGE_LANG", lang)
662
+ data = _fixture_data()
663
+ data["terminal_width"] = 300
664
+ data["model"]["id"] = "Gemini 3.6 Flash"
665
+ data["quota"] = {
666
+ "gemini-5h": {"remaining_fraction": 0.6, "reset_in_seconds": 2 * 3600 + 44 * 60},
667
+ "gemini-weekly": {
668
+ "remaining_fraction": 0.7,
669
+ "reset_in_seconds": 4 * 86400 + 6 * 3600 + 4 * 60,
670
+ },
671
+ }
672
+
673
+ output = re.sub(r"\x1b\[[0-9;]*m", "", usage_statusline_agy.render(data))
674
+
675
+ assert expected_five in output
676
+ assert expected_weekly in output
@@ -120,6 +120,7 @@ MODEL_SHORT = {
120
120
  "claude-haiku": "Haiku",
121
121
  "claude-fable-5": "Fable 5",
122
122
  "claude-fable-5-1": "Fable 5.1",
123
+ "claude-fable-5-2": "Fable 5.2",
123
124
  }
124
125
 
125
126
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: usage-cli
3
- Version: 0.34.15
3
+ Version: 0.34.16
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
@@ -503,6 +503,12 @@ def _fallback_pricing() -> PricingTable:
503
503
  "cache_creation_input_token_cost": 12.5e-6,
504
504
  "cache_read_input_token_cost": 0.25e-6,
505
505
  },
506
+ "claude-fable-5-2": {
507
+ "input_cost_per_token": 8e-6,
508
+ "output_cost_per_token": 40e-6,
509
+ "cache_creation_input_token_cost": 10e-6,
510
+ "cache_read_input_token_cost": 0.2e-6,
511
+ },
506
512
  "claude-haiku-4-5-20251001": {
507
513
  "input_cost_per_token": 1e-6,
508
514
  "output_cost_per_token": 5e-6,