usage-cli 0.33.3__tar.gz → 0.33.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. {usage_cli-0.33.3/usage_cli.egg-info → usage_cli-0.33.4}/PKG-INFO +2 -2
  2. {usage_cli-0.33.3 → usage_cli-0.33.4}/README.md +1 -1
  3. {usage_cli-0.33.3 → usage_cli-0.33.4}/installer/session_hooks.py +1 -1
  4. {usage_cli-0.33.3 → usage_cli-0.33.4}/pyproject.toml +1 -1
  5. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_pricing.py +1 -1
  6. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_project_resolver.py +4 -1
  7. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_session_resume.py +170 -0
  8. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_wintray.py +4 -2
  9. {usage_cli-0.33.3 → usage_cli-0.33.4/usage_cli.egg-info}/PKG-INFO +2 -2
  10. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_common/pricing.py +2 -2
  11. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_hooks/usage_session_resume.py +79 -1
  12. {usage_cli-0.33.3 → usage_cli-0.33.4}/wintray/app.py +8 -1
  13. {usage_cli-0.33.3 → usage_cli-0.33.4}/LICENSE +0 -0
  14. {usage_cli-0.33.3 → usage_cli-0.33.4}/THIRD_PARTY_NOTICES.md +0 -0
  15. {usage_cli-0.33.3 → usage_cli-0.33.4}/adapters/__init__.py +0 -0
  16. {usage_cli-0.33.3 → usage_cli-0.33.4}/adapters/agy.py +0 -0
  17. {usage_cli-0.33.3 → usage_cli-0.33.4}/adapters/claude.py +0 -0
  18. {usage_cli-0.33.3 → usage_cli-0.33.4}/adapters/codex.py +0 -0
  19. {usage_cli-0.33.3 → usage_cli-0.33.4}/adapters/grok.py +0 -0
  20. {usage_cli-0.33.3 → usage_cli-0.33.4}/adapters/muse.py +0 -0
  21. {usage_cli-0.33.3 → usage_cli-0.33.4}/adapters/rate_limits.py +0 -0
  22. {usage_cli-0.33.3 → usage_cli-0.33.4}/adapters/registry.py +0 -0
  23. {usage_cli-0.33.3 → usage_cli-0.33.4}/adapters/types.py +0 -0
  24. {usage_cli-0.33.3 → usage_cli-0.33.4}/analyzer/__init__.py +0 -0
  25. {usage_cli-0.33.3 → usage_cli-0.33.4}/analyzer/aggregator.py +0 -0
  26. {usage_cli-0.33.3 → usage_cli-0.33.4}/analyzer/blocks.py +0 -0
  27. {usage_cli-0.33.3 → usage_cli-0.33.4}/analyzer/diagnoser.py +0 -0
  28. {usage_cli-0.33.3 → usage_cli-0.33.4}/analyzer/diagnosis_snapshot.py +0 -0
  29. {usage_cli-0.33.3 → usage_cli-0.33.4}/analyzer/dispatch_ledger.py +0 -0
  30. {usage_cli-0.33.3 → usage_cli-0.33.4}/analyzer/insights.py +0 -0
  31. {usage_cli-0.33.3 → usage_cli-0.33.4}/analyzer/persona_loader.py +0 -0
  32. {usage_cli-0.33.3 → usage_cli-0.33.4}/analyzer/reporter.py +0 -0
  33. {usage_cli-0.33.3 → usage_cli-0.33.4}/analyzer/subscription.py +0 -0
  34. {usage_cli-0.33.3 → usage_cli-0.33.4}/analyzer/usage_snapshot.py +0 -0
  35. {usage_cli-0.33.3 → usage_cli-0.33.4}/i18n.json +0 -0
  36. {usage_cli-0.33.3 → usage_cli-0.33.4}/installer/__init__.py +0 -0
  37. {usage_cli-0.33.3 → usage_cli-0.33.4}/installer/claude_pane.py +0 -0
  38. {usage_cli-0.33.3 → usage_cli-0.33.4}/installer/codex_hook_trust.py +0 -0
  39. {usage_cli-0.33.3 → usage_cli-0.33.4}/installer/login_item.py +0 -0
  40. {usage_cli-0.33.3 → usage_cli-0.33.4}/installer/setup_hook.py +0 -0
  41. {usage_cli-0.33.3 → usage_cli-0.33.4}/installer/statusline_settings.py +0 -0
  42. {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/__init__.py +0 -0
  43. {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/_shared_cache_file.py +0 -0
  44. {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/agy_disk_cache.py +0 -0
  45. {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/agy_loader.py +0 -0
  46. {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/agy_quota_probe.py +0 -0
  47. {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/cache_quarantine.py +0 -0
  48. {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/chromium_cache.py +0 -0
  49. {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/claude_desktop.py +0 -0
  50. {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/claude_paths.py +0 -0
  51. {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/claude_usage.py +0 -0
  52. {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/codex_disk_cache.py +0 -0
  53. {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/codex_events.py +0 -0
  54. {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/codex_fork_replay.py +0 -0
  55. {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/codex_loader.py +0 -0
  56. {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/codex_paths.py +0 -0
  57. {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/disk_cache_common.py +0 -0
  58. {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/disk_cache_lifecycle.py +0 -0
  59. {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/grok_loader.py +0 -0
  60. {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/grok_quota_probe.py +0 -0
  61. {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/history_disk_cache.py +0 -0
  62. {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/history_loader.py +0 -0
  63. {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/jsonl_limits.py +0 -0
  64. {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/jsonl_utils.py +0 -0
  65. {usage_cli-0.33.3 → usage_cli-0.33.4}/loaders/muse_loader.py +0 -0
  66. {usage_cli-0.33.3 → usage_cli-0.33.4}/main.py +0 -0
  67. {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/__init__.py +0 -0
  68. {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/actions.py +0 -0
  69. {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/agy.py +0 -0
  70. {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/app.py +0 -0
  71. {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/chrome.py +0 -0
  72. {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/fsevents_watch.py +0 -0
  73. {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/grok.py +0 -0
  74. {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/manual_refresh.py +0 -0
  75. {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/menu.py +0 -0
  76. {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/new_badge.py +0 -0
  77. {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/notify.py +0 -0
  78. {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/popover.py +0 -0
  79. {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/prefs.py +0 -0
  80. {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/refresh.py +0 -0
  81. {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/state.py +0 -0
  82. {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/title.py +0 -0
  83. {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/update.py +0 -0
  84. {usage_cli-0.33.3 → usage_cli-0.33.4}/menubar/whats_new.py +0 -0
  85. {usage_cli-0.33.3 → usage_cli-0.33.4}/panels/__init__.py +0 -0
  86. {usage_cli-0.33.3 → usage_cli-0.33.4}/panels/base.py +0 -0
  87. {usage_cli-0.33.3 → usage_cli-0.33.4}/panels/catalog.py +0 -0
  88. {usage_cli-0.33.3 → usage_cli-0.33.4}/panels/dynamic_height.py +0 -0
  89. {usage_cli-0.33.3 → usage_cli-0.33.4}/panels/panel_scale.py +0 -0
  90. {usage_cli-0.33.3 → usage_cli-0.33.4}/panels/panel_window.py +0 -0
  91. {usage_cli-0.33.3 → usage_cli-0.33.4}/panels/panel_window_state.py +0 -0
  92. {usage_cli-0.33.3 → usage_cli-0.33.4}/panels/payload.py +0 -0
  93. {usage_cli-0.33.3 → usage_cli-0.33.4}/panels/web_panel.py +0 -0
  94. {usage_cli-0.33.3 → usage_cli-0.33.4}/panels/window_drag.py +0 -0
  95. {usage_cli-0.33.3 → usage_cli-0.33.4}/quota/__init__.py +0 -0
  96. {usage_cli-0.33.3 → usage_cli-0.33.4}/quota/agy_window_keeper.py +0 -0
  97. {usage_cli-0.33.3 → usage_cli-0.33.4}/quota/burn_rate.py +0 -0
  98. {usage_cli-0.33.3 → usage_cli-0.33.4}/quota/codex_window_keeper.py +0 -0
  99. {usage_cli-0.33.3 → usage_cli-0.33.4}/quota/keeper_outcome.py +0 -0
  100. {usage_cli-0.33.3 → usage_cli-0.33.4}/quota/notifications.py +0 -0
  101. {usage_cli-0.33.3 → usage_cli-0.33.4}/quota/quota_snapshot.py +0 -0
  102. {usage_cli-0.33.3 → usage_cli-0.33.4}/quota/status_payload.py +0 -0
  103. {usage_cli-0.33.3 → usage_cli-0.33.4}/quota/usage_rate.py +0 -0
  104. {usage_cli-0.33.3 → usage_cli-0.33.4}/quota/window_keeper.py +0 -0
  105. {usage_cli-0.33.3 → usage_cli-0.33.4}/setup.cfg +0 -0
  106. {usage_cli-0.33.3 → usage_cli-0.33.4}/setup_app.py +0 -0
  107. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_adapters_claude.py +0 -0
  108. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_adapters_codex.py +0 -0
  109. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_adapters_rate_limits.py +0 -0
  110. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_aggregator.py +0 -0
  111. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_agy_adapter.py +0 -0
  112. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_agy_disk_cache.py +0 -0
  113. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_agy_loader.py +0 -0
  114. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_agy_quota_probe.py +0 -0
  115. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_agy_window_keeper.py +0 -0
  116. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_analyzer_blocks.py +0 -0
  117. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_analyzer_pipeline.py +0 -0
  118. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_app_version.py +0 -0
  119. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_burn_rate.py +0 -0
  120. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_cache_quarantine.py +0 -0
  121. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_check_upstream.py +0 -0
  122. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_chromium_cache.py +0 -0
  123. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_claude_desktop.py +0 -0
  124. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_claude_pane.py +0 -0
  125. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_claude_paths.py +0 -0
  126. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_codex_consistency.py +0 -0
  127. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_codex_hook_trust.py +0 -0
  128. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_codex_loader.py +0 -0
  129. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_codex_window_keeper.py +0 -0
  130. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_desktop_fresh_cache.py +0 -0
  131. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_desktop_quota_display.py +0 -0
  132. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_desktop_quota_review.py +0 -0
  133. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_diagnoser.py +0 -0
  134. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_disk_cache_shards.py +0 -0
  135. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_dispatch_ledger.py +0 -0
  136. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_doctor.py +0 -0
  137. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_dynamic_height.py +0 -0
  138. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_fsevents_watch.py +0 -0
  139. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_grok_loader.py +0 -0
  140. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_grok_quota_probe.py +0 -0
  141. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_history_loader.py +0 -0
  142. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_hook_scripts_are_standalone.py +0 -0
  143. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_html_report_snapshot.py +0 -0
  144. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_i18n_format_fallback.py +0 -0
  145. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_i18n_key_parity.py +0 -0
  146. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_i18n_packaged_path.py +0 -0
  147. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_insights.py +0 -0
  148. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_jsonl_utils.py +0 -0
  149. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_keeper_menu.py +0 -0
  150. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_keeper_outcome.py +0 -0
  151. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_login_item.py +0 -0
  152. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_main.py +0 -0
  153. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_manual_refresh.py +0 -0
  154. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_menubar.py +0 -0
  155. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_menubar_agy.py +0 -0
  156. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_menubar_grok.py +0 -0
  157. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_menubar_notify.py +0 -0
  158. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_menubar_prefs.py +0 -0
  159. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_menubar_state.py +0 -0
  160. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_menubar_update.py +0 -0
  161. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_muse_loader.py +0 -0
  162. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_new_badge.py +0 -0
  163. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_packaged_resources.py +0 -0
  164. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_packaging.py +0 -0
  165. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_panel_footer_layout.py +0 -0
  166. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_panel_scale.py +0 -0
  167. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_panel_window_state.py +0 -0
  168. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_panels.py +0 -0
  169. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_panels_lazy_import.py +0 -0
  170. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_persona_loader.py +0 -0
  171. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_quota_aware.py +0 -0
  172. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_quota_label.py +0 -0
  173. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_report_daily_chart.py +0 -0
  174. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_report_filter_js.py +0 -0
  175. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_reporter_build_data.py +0 -0
  176. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_service_status.py +0 -0
  177. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_setup_app.py +0 -0
  178. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_setup_hook.py +0 -0
  179. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_setup_hook_coexistence.py +0 -0
  180. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_setup_hook_resume.py +0 -0
  181. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_setup_hook_terse.py +0 -0
  182. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_subprocess_utils.py +0 -0
  183. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_subscription.py +0 -0
  184. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_taskbar_menu.py +0 -0
  185. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_time_utils.py +0 -0
  186. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_tui.py +0 -0
  187. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_tui_sprite.py +0 -0
  188. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_ui_tables.py +0 -0
  189. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_update_checker.py +0 -0
  190. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_update_gate.py +0 -0
  191. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_update_release_notes.py +0 -0
  192. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_cli.py +0 -0
  193. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_client.py +0 -0
  194. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_diagnosis_snapshot.py +0 -0
  195. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_dir_sweeper.py +0 -0
  196. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_lang.py +0 -0
  197. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_logging.py +0 -0
  198. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_notifications.py +0 -0
  199. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_rate.py +0 -0
  200. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_snapshot.py +0 -0
  201. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_snapshot_replay.py +0 -0
  202. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_statusline.py +0 -0
  203. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_statusline_agy.py +0 -0
  204. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_statusline_forwarder.py +0 -0
  205. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_statusline_grok.py +0 -0
  206. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_terse_mode.py +0 -0
  207. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_usage_terse_reminder.py +0 -0
  208. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_web_panel_payload.py +0 -0
  209. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_whats_new.py +0 -0
  210. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_wheel_packaging.py +0 -0
  211. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_win_login_item.py +0 -0
  212. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_window_keeper.py +0 -0
  213. {usage_cli-0.33.3 → usage_cli-0.33.4}/tests/test_windows_manifest.py +0 -0
  214. {usage_cli-0.33.3 → usage_cli-0.33.4}/tui/__init__.py +0 -0
  215. {usage_cli-0.33.3 → usage_cli-0.33.4}/tui/app.py +0 -0
  216. {usage_cli-0.33.3 → usage_cli-0.33.4}/tui/sprite.py +0 -0
  217. {usage_cli-0.33.3 → usage_cli-0.33.4}/ui/__init__.py +0 -0
  218. {usage_cli-0.33.3 → usage_cli-0.33.4}/ui/html_report.py +0 -0
  219. {usage_cli-0.33.3 → usage_cli-0.33.4}/ui/report_charts.py +0 -0
  220. {usage_cli-0.33.3 → usage_cli-0.33.4}/ui/report_daily_chart.py +0 -0
  221. {usage_cli-0.33.3 → usage_cli-0.33.4}/ui/report_filter.py +0 -0
  222. {usage_cli-0.33.3 → usage_cli-0.33.4}/ui/report_ledger.py +0 -0
  223. {usage_cli-0.33.3 → usage_cli-0.33.4}/ui/report_scripts.py +0 -0
  224. {usage_cli-0.33.3 → usage_cli-0.33.4}/ui/report_styles.py +0 -0
  225. {usage_cli-0.33.3 → usage_cli-0.33.4}/ui/tables.py +0 -0
  226. {usage_cli-0.33.3 → usage_cli-0.33.4}/updates/__init__.py +0 -0
  227. {usage_cli-0.33.3 → usage_cli-0.33.4}/updates/checker.py +0 -0
  228. {usage_cli-0.33.3 → usage_cli-0.33.4}/updates/gate.py +0 -0
  229. {usage_cli-0.33.3 → usage_cli-0.33.4}/updates/release_notes.py +0 -0
  230. {usage_cli-0.33.3 → usage_cli-0.33.4}/updates/whats_new.py +0 -0
  231. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_app/__init__.py +0 -0
  232. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_app/cli.py +0 -0
  233. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_app/doctor.py +0 -0
  234. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_cli.egg-info/SOURCES.txt +0 -0
  235. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_cli.egg-info/dependency_links.txt +0 -0
  236. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_cli.egg-info/entry_points.txt +0 -0
  237. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_cli.egg-info/requires.txt +0 -0
  238. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_cli.egg-info/top_level.txt +0 -0
  239. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_common/__init__.py +0 -0
  240. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_common/app_version.py +0 -0
  241. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_common/i18n.py +0 -0
  242. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_common/prefs.py +0 -0
  243. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_common/project_resolver.py +0 -0
  244. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_common/service_status.py +0 -0
  245. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_common/subprocess_utils.py +0 -0
  246. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_common/time_utils.py +0 -0
  247. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_common/usage_dir_sweeper.py +0 -0
  248. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_common/usage_lang.py +0 -0
  249. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_common/usage_logging.py +0 -0
  250. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_hooks/__init__.py +0 -0
  251. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_hooks/usage_quota_aware.py +0 -0
  252. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_hooks/usage_statusline.py +0 -0
  253. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_hooks/usage_statusline_agy.py +0 -0
  254. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_hooks/usage_statusline_forwarder.py +0 -0
  255. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_hooks/usage_statusline_grok.py +0 -0
  256. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_hooks/usage_terse_mode.py +0 -0
  257. {usage_cli-0.33.3 → usage_cli-0.33.4}/usage_hooks/usage_terse_reminder.py +0 -0
  258. {usage_cli-0.33.3 → usage_cli-0.33.4}/wintray/__init__.py +0 -0
  259. {usage_cli-0.33.3 → usage_cli-0.33.4}/wintray/login_item.py +0 -0
  260. {usage_cli-0.33.3 → usage_cli-0.33.4}/wintray/menu.py +0 -0
  261. {usage_cli-0.33.3 → usage_cli-0.33.4}/wintray/prefs.py +0 -0
  262. {usage_cli-0.33.3 → usage_cli-0.33.4}/wintray/quota_label.py +0 -0
  263. {usage_cli-0.33.3 → usage_cli-0.33.4}/wintray/taskbar_menu.py +0 -0
  264. {usage_cli-0.33.3 → usage_cli-0.33.4}/wintray/taskbar_obstacles.py +0 -0
  265. {usage_cli-0.33.3 → usage_cli-0.33.4}/wintray/taskbar_overlay.py +0 -0
  266. {usage_cli-0.33.3 → usage_cli-0.33.4}/wintray/watch.py +0 -0
  267. {usage_cli-0.33.3 → usage_cli-0.33.4}/wintray/whats_new.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: usage-cli
3
- Version: 0.33.3
3
+ Version: 0.33.4
4
4
  Summary: usage — 在 macOS menu bar 與 Windows 系統匣顯示 Claude Code、Codex、Antigravity 用量的小工具(也提供終端機 TUI)
5
5
  License-Expression: AGPL-3.0-only
6
6
  Requires-Python: >=3.13
@@ -138,7 +138,7 @@ Once set up, the bottom of the Claude Code window will show a status line like t
138
138
 
139
139
  See your quota, other conversations, and background jobs without leaving Claude Code. Available on macOS and Windows.
140
140
 
141
- <p align="center"><img src="docs/side-pane.png" alt="Claude Code side pane showing quotas, conversations, and background jobs" width="637"></p>
141
+ <p align="center"><img src="docs/side-pane.en.png" alt="Claude Code side pane showing quotas, conversations, and background jobs" width="637"></p>
142
142
 
143
143
  **What you’ll see**
144
144
 
@@ -116,7 +116,7 @@ Once set up, the bottom of the Claude Code window will show a status line like t
116
116
 
117
117
  See your quota, other conversations, and background jobs without leaving Claude Code. Available on macOS and Windows.
118
118
 
119
- <p align="center"><img src="docs/side-pane.png" alt="Claude Code side pane showing quotas, conversations, and background jobs" width="637"></p>
119
+ <p align="center"><img src="docs/side-pane.en.png" alt="Claude Code side pane showing quotas, conversations, and background jobs" width="637"></p>
120
120
 
121
121
  **What you’ll see**
122
122
 
@@ -59,7 +59,7 @@ CODEX_CONFIG = setup_hook.CODEX_CONFIG
59
59
  # session. Off by default: enabled only via the menu toggle, never by self_heal.
60
60
  RESUME_HOOK_TARGET = Path(os.path.expanduser("~/.claude/usage-session-resume.py"))
61
61
  RESUME_PROMPT_SIDECAR = Path(os.path.expanduser("~/.claude/usage-resume-prompt.json"))
62
- RESUME_HOOK_VERSION = "1.10"
62
+ RESUME_HOOK_VERSION = "1.11"
63
63
  RESUME_MATCHER = "startup|clear|resume"
64
64
  RESUME_LANGS = ("zh-TW", "zh-CN", "en", "ja", "ko")
65
65
  _RESUME_MARKER = "usage-session-resume"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "usage-cli"
3
- version = "0.33.3"
3
+ version = "0.33.4"
4
4
  description = "usage — 在 macOS menu bar 與 Windows 系統匣顯示 Claude Code、Codex、Antigravity 用量的小工具(也提供終端機 TUI)"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -383,7 +383,7 @@ def test_fallback_pricing_contains_expected_models() -> None:
383
383
  "input_cost_per_token": 2e-6,
384
384
  "output_cost_per_token": 10e-6,
385
385
  "cache_creation_input_token_cost": 2.5e-6,
386
- "cache_read_input_token_cost": 0.2e-6,
386
+ "cache_read_input_token_cost": 0.1e-6,
387
387
  }
388
388
  assert pricing._resolve_model_key("claude-sonnet-5", fallback) == "claude-sonnet-5"
389
389
  assert fallback["claude-haiku-5-5"] == {
@@ -214,7 +214,10 @@ def test_project_from_encoded_path_matches_names_claude_flattens(tmp_path: Path,
214
214
  def test_project_from_encoded_path_matches_truncated_long_path(tmp_path: Path) -> None:
215
215
  # Claude Code keeps the first 200 encoded characters and appends "-<hash>".
216
216
  projects_dir = tmp_path / "projects"
217
- name = "long-project-" + "x" * 220
217
+ # Size the path below Windows' MAX_PATH limit while keeping it over 200.
218
+ prefix = "long-project-"
219
+ padding = 220 - len(_claude_encode(tmp_path / "work")) - len(prefix) - 1
220
+ name = prefix + "x" * padding
218
221
  real_project = tmp_path / "work" / name
219
222
  real_project.mkdir(parents=True)
220
223
  encoded = _claude_encode(real_project)
@@ -1398,3 +1398,173 @@ def test_build_prompt_skips_diagnosis_reminder_when_snapshot_is_stale(
1398
1398
  )
1399
1399
 
1400
1400
  assert "Health check:" not in prompt
1401
+
1402
+
1403
+ def _register_session(tmp_path: Path, session_id: str, pid: int) -> None:
1404
+ sessions = tmp_path / "sessions"
1405
+ sessions.mkdir(exist_ok=True)
1406
+ (sessions / f"{pid}.json").write_text(
1407
+ json.dumps({"pid": pid, "sessionId": session_id, "kind": "interactive"}),
1408
+ encoding="utf-8",
1409
+ )
1410
+
1411
+
1412
+ def _two_sessions(tmp_path: Path, *, neighbour_age: timedelta) -> tuple[Path, Path]:
1413
+ """An older finished session plus a neighbour whose log was written ``neighbour_age`` ago."""
1414
+ project = _project_dir(tmp_path)
1415
+ now = datetime.now().astimezone()
1416
+ finished = project / "finished.jsonl"
1417
+ _write_session(finished, when=now - timedelta(hours=1), request="finish the release notes")
1418
+ stamp = now.timestamp() - 3600
1419
+ os.utime(finished, (stamp, stamp))
1420
+ neighbour = project / "neighbour.jsonl"
1421
+ _write_session(neighbour, when=now - neighbour_age, request="plan the kyoto trip app")
1422
+ stamp = now.timestamp() - neighbour_age.total_seconds()
1423
+ os.utime(neighbour, (stamp, stamp))
1424
+ current = project / "current.jsonl"
1425
+ current.write_text("", encoding="utf-8")
1426
+ return neighbour, current
1427
+
1428
+
1429
+ def _resume_prompt(current: Path) -> str:
1430
+ return mod._build_prompt({"transcript_path": str(current), "cwd": "/Users/me/Developer/myproj"})
1431
+
1432
+
1433
+ def test_build_prompt_skips_session_another_window_is_still_writing(
1434
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
1435
+ ) -> None:
1436
+ monkeypatch.setenv("USAGE_LANG", "en")
1437
+ _sidecar(tmp_path, monkeypatch)
1438
+ monkeypatch.delenv("CLAUDE_PID", raising=False)
1439
+ neighbour, current = _two_sessions(tmp_path, neighbour_age=timedelta(minutes=1))
1440
+ _register_session(tmp_path, neighbour.stem, os.getpid())
1441
+
1442
+ prompt = _resume_prompt(current)
1443
+
1444
+ assert "finish the release notes" in prompt
1445
+ assert "kyoto" not in prompt
1446
+
1447
+
1448
+ def test_build_prompt_keeps_session_closed_a_moment_ago(
1449
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
1450
+ ) -> None:
1451
+ # Closing a window and opening a new one is the main resume case: the closed session
1452
+ # is no longer registered, so its fresh log must still be handed over.
1453
+ monkeypatch.setenv("USAGE_LANG", "en")
1454
+ _sidecar(tmp_path, monkeypatch)
1455
+ _neighbour, current = _two_sessions(tmp_path, neighbour_age=timedelta(minutes=1))
1456
+ _register_session(tmp_path, "some-other-session", os.getpid())
1457
+
1458
+ assert "plan the kyoto trip app" in _resume_prompt(current)
1459
+
1460
+
1461
+ @pytest.mark.parametrize("via", ["env", "parent"])
1462
+ def test_build_prompt_keeps_session_this_window_just_cleared(
1463
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path, via: str
1464
+ ) -> None:
1465
+ # After /clear the registry may still list this window's pre-clear session.
1466
+ monkeypatch.setenv("USAGE_LANG", "en")
1467
+ _sidecar(tmp_path, monkeypatch)
1468
+ cleared, current = _two_sessions(tmp_path, neighbour_age=timedelta(minutes=1))
1469
+ _register_session(tmp_path, cleared.stem, 4242)
1470
+ monkeypatch.setattr(mod, "_pid_running", lambda pid: True)
1471
+ if via == "env":
1472
+ monkeypatch.setenv("CLAUDE_PID", "4242")
1473
+ else:
1474
+ monkeypatch.delenv("CLAUDE_PID", raising=False)
1475
+ monkeypatch.setattr(os, "getppid", lambda: 4242)
1476
+
1477
+ assert "plan the kyoto trip app" in _resume_prompt(current)
1478
+
1479
+
1480
+ def test_build_prompt_keeps_open_window_that_went_quiet(
1481
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
1482
+ ) -> None:
1483
+ monkeypatch.setenv("USAGE_LANG", "en")
1484
+ _sidecar(tmp_path, monkeypatch)
1485
+ neighbour, current = _two_sessions(tmp_path, neighbour_age=timedelta(minutes=30))
1486
+ _register_session(tmp_path, neighbour.stem, os.getpid())
1487
+
1488
+ assert "plan the kyoto trip app" in _resume_prompt(current)
1489
+
1490
+
1491
+ def test_build_prompt_keeps_session_whose_window_crashed(
1492
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
1493
+ ) -> None:
1494
+ monkeypatch.setenv("USAGE_LANG", "en")
1495
+ _sidecar(tmp_path, monkeypatch)
1496
+ neighbour, current = _two_sessions(tmp_path, neighbour_age=timedelta(minutes=1))
1497
+ _register_session(tmp_path, neighbour.stem, 4242)
1498
+ monkeypatch.setattr(mod, "_pid_running", lambda pid: False)
1499
+
1500
+ assert "plan the kyoto trip app" in _resume_prompt(current)
1501
+
1502
+
1503
+ def test_build_prompt_ignores_unreadable_session_registry(
1504
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
1505
+ ) -> None:
1506
+ monkeypatch.setenv("USAGE_LANG", "en")
1507
+ _sidecar(tmp_path, monkeypatch)
1508
+ _neighbour, current = _two_sessions(tmp_path, neighbour_age=timedelta(minutes=1))
1509
+ sessions = tmp_path / "sessions"
1510
+ sessions.mkdir()
1511
+ (sessions / "1.json").write_text("{not json", encoding="utf-8")
1512
+ (sessions / "2.json").write_text("[]", encoding="utf-8")
1513
+ (sessions / "3.json").write_text(json.dumps({"pid": "x", "sessionId": 7}), encoding="utf-8")
1514
+
1515
+ assert "plan the kyoto trip app" in _resume_prompt(current)
1516
+
1517
+
1518
+ def test_pid_running_detects_exited_process() -> None:
1519
+ child = subprocess.Popen([sys.executable, "-c", ""])
1520
+ child.wait()
1521
+
1522
+ assert mod._pid_running(os.getpid()) is True
1523
+ if os.name != "nt":
1524
+ assert mod._pid_running(child.pid) is False
1525
+
1526
+
1527
+ @pytest.mark.parametrize("pid", [None, "123", True, 0, -1])
1528
+ def test_pid_running_rejects_invalid_pid(pid: object) -> None:
1529
+ assert mod._pid_running(pid) is False
1530
+
1531
+
1532
+ def test_pid_running_trusts_registry_on_windows(monkeypatch: pytest.MonkeyPatch) -> None:
1533
+ def fail_kill(pid: int, sig: int) -> None:
1534
+ raise AssertionError("os.kill must not run on Windows")
1535
+
1536
+ monkeypatch.setattr(mod, "os", SimpleNamespace(name="nt", kill=fail_kill))
1537
+
1538
+ assert mod._pid_running(4242) is True
1539
+
1540
+
1541
+ @pytest.mark.parametrize(
1542
+ "value",
1543
+ [
1544
+ "<task-notification>\n<task-id>b1</task-id>\n<status>completed</status>"
1545
+ "\n<summary>Background agent finished the research</summary>\n</task-notification>",
1546
+ "<bash-input>rm -rf /tmp/frames</bash-input>",
1547
+ "<bash-stdout>(Bash completed with no output)</bash-stdout><bash-stderr></bash-stderr>",
1548
+ "<local-command-stdout>Set effort level to high for new sessions</local-command-stdout>",
1549
+ "<command-name>/clear</command-name>\n <command-message>clear</command-message>"
1550
+ "\n <command-args></command-args>",
1551
+ "<command-message>effort</command-message>\n<command-name>/effort</command-name>",
1552
+ ],
1553
+ )
1554
+ def test_clean_request_drops_turns_claude_code_writes(value: str) -> None:
1555
+ assert mod._clean_request(value) == ""
1556
+
1557
+
1558
+ def test_clean_request_keeps_slash_command_arguments() -> None:
1559
+ value = (
1560
+ "<command-name>/loop</command-name>\n <command-message>loop</command-message>"
1561
+ "\n <command-args>5m check the deploy</command-args>"
1562
+ )
1563
+
1564
+ assert mod._clean_request(value) == "/loop 5m check the deploy"
1565
+
1566
+
1567
+ def test_clean_request_keeps_user_tagged_prompt() -> None:
1568
+ value = "<Task>split the settings panel</Task> <Constraints>keep the old API</Constraints>"
1569
+
1570
+ assert mod._clean_request(value) == value
@@ -1814,7 +1814,7 @@ def test_run_app_wires_pystray_and_pywebview(
1814
1814
  fake_pystray = SimpleNamespace(Icon=FakeIcon, Menu=FakeMenu, MenuItem=FakeMenuItem)
1815
1815
  fake_webview = SimpleNamespace(
1816
1816
  create_window=create_window,
1817
- start=lambda **kwargs: events.append(("start", kwargs["gui"])),
1817
+ start=lambda **kwargs: events.append(("start", kwargs["gui"], kwargs["storage_path"])),
1818
1818
  )
1819
1819
  monkeypatch.setitem(sys.modules, "pystray", fake_pystray)
1820
1820
  monkeypatch.setitem(sys.modules, "webview", fake_webview)
@@ -1826,12 +1826,14 @@ def test_run_app_wires_pystray_and_pywebview(
1826
1826
 
1827
1827
  wintray.run_app(mock=True, interval=60)
1828
1828
 
1829
+ assert wintray._WEBVIEW_STORAGE_DIR.name == "webview"
1830
+ assert wintray._WEBVIEW_STORAGE_DIR.parent == Path.home() / ".usage"
1829
1831
  assert events == [
1830
1832
  ("window", "usage", True, "#eef2f7"),
1831
1833
  "loaded_handler",
1832
1834
  ("icon", "usage"),
1833
1835
  "run_detached",
1834
- ("start", "edgechromium"),
1836
+ ("start", "edgechromium", str(wintray._WEBVIEW_STORAGE_DIR)),
1835
1837
  ]
1836
1838
 
1837
1839
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: usage-cli
3
- Version: 0.33.3
3
+ Version: 0.33.4
4
4
  Summary: usage — 在 macOS menu bar 與 Windows 系統匣顯示 Claude Code、Codex、Antigravity 用量的小工具(也提供終端機 TUI)
5
5
  License-Expression: AGPL-3.0-only
6
6
  Requires-Python: >=3.13
@@ -138,7 +138,7 @@ Once set up, the bottom of the Claude Code window will show a status line like t
138
138
 
139
139
  See your quota, other conversations, and background jobs without leaving Claude Code. Available on macOS and Windows.
140
140
 
141
- <p align="center"><img src="docs/side-pane.png" alt="Claude Code side pane showing quotas, conversations, and background jobs" width="637"></p>
141
+ <p align="center"><img src="docs/side-pane.en.png" alt="Claude Code side pane showing quotas, conversations, and background jobs" width="637"></p>
142
142
 
143
143
  **What you’ll see**
144
144
 
@@ -33,7 +33,7 @@ FALLBACK_RETRY_SECONDS = 600
33
33
  MISSING_MODEL_REFRESH_SECONDS = FALLBACK_RETRY_SECONDS
34
34
  USER_AGENT = "usage/0.9"
35
35
  # The date when the hard-coded fallback table was last manually checked upstream.
36
- FALLBACK_PRICING_AS_OF: str = "2026-09-04"
36
+ FALLBACK_PRICING_AS_OF: str = "2026-10-12"
37
37
  # Anthropic's official cache write/read prices relative to input pricing. These
38
38
  # are only fallbacks for missing upstream fields and may be wrong for other providers.
39
39
  CACHE_WRITE_COST_MULTIPLIER = 1.25
@@ -484,7 +484,7 @@ def _fallback_pricing() -> PricingTable:
484
484
  "input_cost_per_token": 2e-6,
485
485
  "output_cost_per_token": 10e-6,
486
486
  "cache_creation_input_token_cost": 2.5e-6,
487
- "cache_read_input_token_cost": 0.2e-6,
487
+ "cache_read_input_token_cost": 0.1e-6,
488
488
  },
489
489
  "claude-fable-5": {
490
490
  "input_cost_per_token": 10e-6,
@@ -50,7 +50,7 @@ from datetime import datetime, timedelta, timezone
50
50
  from pathlib import Path
51
51
  from typing import Any, TypedDict, cast
52
52
 
53
- __version__ = "1.10"
53
+ __version__ = "1.11"
54
54
 
55
55
 
56
56
  class _HiddenConsoleKwargs(TypedDict, total=False):
@@ -100,6 +100,20 @@ _MAX_REQUESTS = 3
100
100
  _MAX_REQUEST_CHARS = 280
101
101
  _MIN_SUBSTANTIVE_CHARS = 7
102
102
  _IMAGE_MARKER = re.compile(r"\[Image(?:\s+#[0-9]+)?\]", re.I)
103
+ # User turns Claude Code writes on the user's behalf: background-task notices, `!` shell
104
+ # runs, and local command output. Matched exactly so a user's own `<Task>` prompt survives.
105
+ _SYSTEM_TURN_PREFIXES = (
106
+ "<task-notification>",
107
+ "<bash-input>",
108
+ "<bash-stdout>",
109
+ "<local-command-stdout>",
110
+ )
111
+ _SLASH_COMMAND_PREFIXES = ("<command-name>", "<command-message>")
112
+ _SLASH_COMMAND_NAME = re.compile(r"<command-name>(.*?)</command-name>")
113
+ _SLASH_COMMAND_ARGS = re.compile(r"<command-args>(.*?)</command-args>")
114
+ # A session another window still has open and wrote to this recently is that window's
115
+ # work in progress, not this window's previous session.
116
+ _LIVE_SESSION_WINDOW = timedelta(minutes=10)
103
117
  _DIAGNOSIS_MAX_AGE = timedelta(hours=48)
104
118
  _DIAGNOSIS_REMINDER_COOLDOWN = timedelta(days=7)
105
119
  _DIAGNOSIS_CAUSE_KEYS = (
@@ -520,7 +534,12 @@ def _other_jsonls_by_mtime(project_dir: Path, exclude: str) -> list[Path]:
520
534
 
521
535
  Newest-first so the caller can skip a freshly-written but empty/corrupt log and
522
536
  still fall back to the previous session that actually has progress to report.
537
+ Sessions another window still has open and is still writing are left out: with
538
+ several windows in one project, the newest log is usually a neighbour's live work.
539
+ A session closed a moment ago is no longer registered, so it stays a candidate.
523
540
  """
541
+ live_ids = _live_session_ids(project_dir.parent.parent / "sessions")
542
+ busy_since = (datetime.now() - _LIVE_SESSION_WINDOW).timestamp()
524
543
  candidates: list[tuple[float, Path]] = []
525
544
  for jsonl in project_dir.glob("*.jsonl"):
526
545
  if jsonl.name == exclude:
@@ -529,11 +548,60 @@ def _other_jsonls_by_mtime(project_dir: Path, exclude: str) -> list[Path]:
529
548
  mtime = jsonl.stat().st_mtime
530
549
  except OSError:
531
550
  continue
551
+ if mtime >= busy_since and jsonl.stem in live_ids:
552
+ continue
532
553
  candidates.append((mtime, jsonl))
533
554
  candidates.sort(key=lambda item: item[0], reverse=True)
534
555
  return [path for _, path in candidates]
535
556
 
536
557
 
558
+ def _live_session_ids(sessions_dir: Path) -> set[str]:
559
+ """Session IDs that open Claude Code windows list in ``~/.claude/sessions/<pid>.json``."""
560
+ ids: set[str] = set()
561
+ try:
562
+ entries = list(sessions_dir.glob("*.json"))
563
+ except OSError:
564
+ return ids
565
+ own_pids = _own_window_pids()
566
+ for entry in entries:
567
+ data = _read_json_file(entry)
568
+ if data is None:
569
+ continue
570
+ session_id = data.get("sessionId")
571
+ pid = data.get("pid")
572
+ if pid in own_pids:
573
+ continue
574
+ if isinstance(session_id, str) and session_id and _pid_running(pid):
575
+ ids.add(session_id)
576
+ return ids
577
+
578
+
579
+ def _own_window_pids() -> set[int]:
580
+ """This window's Claude Code process. After /clear its registry entry may still name
581
+ the session just cleared, which is the one to hand over."""
582
+ pids = {os.getppid()}
583
+ with contextlib.suppress(ValueError):
584
+ pids.add(int(os.environ.get("CLAUDE_PID", "")))
585
+ return pids
586
+
587
+
588
+ def _pid_running(pid: object) -> bool:
589
+ """False for a registry entry a crashed window left behind."""
590
+ if not isinstance(pid, int) or isinstance(pid, bool) or pid <= 0:
591
+ return False
592
+ # On Windows os.kill(pid, 0) sends CTRL_C_EVENT instead of probing, so the entry
593
+ # is trusted as is.
594
+ if os.name == "nt":
595
+ return True
596
+ try:
597
+ os.kill(pid, 0)
598
+ except PermissionError:
599
+ return True
600
+ except OSError:
601
+ return False
602
+ return True
603
+
604
+
537
605
  def _parse_session(path: Path) -> tuple[datetime, str, list[str], list[str], list[str]] | None:
538
606
  """Return (last_active, recent_requests, commits, todos, edited_files) for the previous session.
539
607
 
@@ -625,6 +693,16 @@ def _clean_request(value: object) -> str:
625
693
  if not isinstance(value, str):
626
694
  return ""
627
695
  text = " ".join(value.split())
696
+ if text.startswith(_SYSTEM_TURN_PREFIXES):
697
+ return ""
698
+ if text.startswith(_SLASH_COMMAND_PREFIXES):
699
+ # Keep a slash command only when it carries arguments: `/loop 5m check CI` is a
700
+ # request, a bare `/clear` is not.
701
+ name = _SLASH_COMMAND_NAME.search(text)
702
+ args = _SLASH_COMMAND_ARGS.search(text)
703
+ if name is None or args is None or not args.group(1).strip():
704
+ return ""
705
+ text = f"{name.group(1).strip()} {args.group(1).strip()}"
628
706
  # Skip the interruption marker Claude Code writes as a user turn — it is noise,
629
707
  # not a request.
630
708
  if not text or text.startswith("[Request interrupted"):
@@ -18,6 +18,7 @@ from collections.abc import Callable
18
18
  from dataclasses import dataclass
19
19
  from datetime import date, datetime, timedelta
20
20
  from enum import IntEnum
21
+ from pathlib import Path
21
22
  from typing import TYPE_CHECKING, Any
22
23
  from uuid import UUID
23
24
 
@@ -84,6 +85,8 @@ UPDATE_ALERT_BODY_LIMIT = 2000
84
85
  # szTip is 128 WCHARs including the terminator; pystray raises ValueError past that.
85
86
  TOOLTIP_MAX_LENGTH = 127
86
87
  PANEL_WIDTH = 380
88
+ # Abnormal exits reuse one dir; pywebview private mode deletes on clean exit: subdir, never ~/.usage
89
+ _WEBVIEW_STORAGE_DIR = Path.home() / ".usage" / "webview"
87
90
  _TOAST_AUMID = "com.lollapalooza.usage"
88
91
  _TOAST_OPEN_PANEL_ACTION = "open_panel"
89
92
  WINDOWS_PANELS = tuple((spec.id, spec.i18n_key, spec.html_filename) for spec in PANEL_SPECS)
@@ -2639,7 +2642,11 @@ def run_app(
2639
2642
 
2640
2643
  start(_current_version(), snapshot, controller.language)
2641
2644
  try:
2642
- webview.start(gui="edgechromium", debug=os.environ.get("USAGE_DEBUG") == "1")
2645
+ webview.start(
2646
+ gui="edgechromium",
2647
+ debug=os.environ.get("USAGE_DEBUG") == "1",
2648
+ storage_path=str(_WEBVIEW_STORAGE_DIR),
2649
+ )
2643
2650
  finally:
2644
2651
  controller.stopping.set()
2645
2652
  _release_single_instance_lock()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes