usage-cli 0.29.32__py3-none-any.whl
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.
- adapters/__init__.py +5 -0
- adapters/agy.py +68 -0
- adapters/claude.py +215 -0
- adapters/codex.py +209 -0
- adapters/rate_limits.py +76 -0
- adapters/registry.py +17 -0
- adapters/types.py +139 -0
- agy_disk_cache.py +135 -0
- agy_loader.py +416 -0
- agy_quota_probe.py +748 -0
- agy_window_keeper.py +185 -0
- analyzer/__init__.py +5 -0
- analyzer/aggregator.py +139 -0
- analyzer/blocks.py +80 -0
- analyzer/diagnoser.py +638 -0
- analyzer/insights.py +277 -0
- analyzer/persona_loader.py +199 -0
- analyzer/reporter.py +989 -0
- analyzer/subscription.py +108 -0
- burn_rate.py +75 -0
- cache_quarantine.py +50 -0
- codex_disk_cache.py +227 -0
- codex_events.py +136 -0
- codex_fork_replay.py +111 -0
- codex_loader.py +1426 -0
- codex_paths.py +20 -0
- critter_frames.py +26 -0
- discussion_bridge.py +1196 -0
- discussion_cli.py +844 -0
- discussion_session.py +622 -0
- discussion_usage.py +13 -0
- discussion_window.py +955 -0
- disk_cache_common.py +132 -0
- disk_cache_lifecycle.py +39 -0
- doctor.py +452 -0
- fsevents_watch.py +207 -0
- history_disk_cache.py +110 -0
- history_loader.py +416 -0
- i18n.py +88 -0
- jsonl_limits.py +17 -0
- jsonl_utils.py +40 -0
- login_item.py +154 -0
- main.py +387 -0
- menubar.py +1201 -0
- menubar_actions.py +204 -0
- menubar_agy.py +193 -0
- menubar_chrome.py +156 -0
- menubar_menu.py +169 -0
- menubar_notify.py +102 -0
- menubar_popover.py +233 -0
- menubar_prefs.py +118 -0
- menubar_refresh.py +285 -0
- menubar_state.py +1200 -0
- menubar_title.py +157 -0
- menubar_update.py +123 -0
- panel_window.py +78 -0
- panel_window_state.py +159 -0
- panels/__init__.py +186 -0
- panels/base.py +83 -0
- panels/dynamic_height.py +140 -0
- panels/payload.py +178 -0
- panels/web_panel.py +513 -0
- panels/window_drag.py +56 -0
- prefs.py +44 -0
- pricing.py +452 -0
- project_resolver.py +112 -0
- service_status.py +383 -0
- session_hooks.py +1154 -0
- setup_app.py +171 -0
- setup_hook.py +1011 -0
- statusline_settings.py +160 -0
- talent_market_bridge.py +243 -0
- time_utils.py +24 -0
- tui.py +288 -0
- tui_sprite.py +206 -0
- ui/__init__.py +5 -0
- ui/html_report.py +923 -0
- ui/report_scripts.py +251 -0
- ui/report_styles.py +370 -0
- ui/tables.py +888 -0
- update_checker.py +156 -0
- update_gate.py +66 -0
- update_release_notes.py +49 -0
- usage_cli-0.29.32.data/data/share/usage/i18n.json +2427 -0
- usage_cli-0.29.32.dist-info/METADATA +223 -0
- usage_cli-0.29.32.dist-info/RECORD +109 -0
- usage_cli-0.29.32.dist-info/WHEEL +5 -0
- usage_cli-0.29.32.dist-info/entry_points.txt +3 -0
- usage_cli-0.29.32.dist-info/licenses/LICENSE +663 -0
- usage_cli-0.29.32.dist-info/top_level.txt +80 -0
- usage_cli.py +827 -0
- usage_client.py +487 -0
- usage_diagnosis_snapshot.py +143 -0
- usage_dir_sweeper.py +100 -0
- usage_lang.py +79 -0
- usage_logging.py +75 -0
- usage_notifications.py +96 -0
- usage_rate.py +97 -0
- usage_session_resume.py +913 -0
- usage_statusline.py +810 -0
- usage_statusline_agy.py +397 -0
- usage_statusline_forwarder.py +88 -0
- usage_terse_mode.py +223 -0
- usage_terse_reminder.py +151 -0
- win_login_item.py +53 -0
- window_keeper.py +264 -0
- windows_watch.py +443 -0
- wintray.py +2014 -0
- wintray_menu.py +136 -0
menubar_refresh.py
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
# Copyright (C) 2026 lollapalooza <https://github.com/aqua5230>
|
|
3
|
+
#
|
|
4
|
+
# Part of "usage". Free software licensed under the GNU Affero General Public
|
|
5
|
+
# License v3.0 only; see the LICENSE file for full terms and the warranty disclaimer.
|
|
6
|
+
|
|
7
|
+
"""Background-safe refresh loading and popover-state construction."""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import logging
|
|
13
|
+
import os
|
|
14
|
+
import time
|
|
15
|
+
from collections.abc import Coroutine
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from typing import Any, Protocol
|
|
18
|
+
|
|
19
|
+
import agy_window_keeper
|
|
20
|
+
import menubar_agy
|
|
21
|
+
import menubar_state
|
|
22
|
+
import talent_market_bridge
|
|
23
|
+
import window_keeper
|
|
24
|
+
from burn_rate import BurnRateTracker
|
|
25
|
+
from history_loader import UsageEntry
|
|
26
|
+
from menubar_prefs import (
|
|
27
|
+
_hide_agy_enabled,
|
|
28
|
+
_hide_claude_enabled,
|
|
29
|
+
_hide_codex_enabled,
|
|
30
|
+
_quota_card_order,
|
|
31
|
+
)
|
|
32
|
+
from service_status import CLAUDE_STATUS, CODEX_STATUS, get_service_status
|
|
33
|
+
from usage_client import PollOutcome, PollState
|
|
34
|
+
from usage_rate import UsageRateTracker
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
type ProjectRows = list[tuple[str, int, float | None]]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class _RefreshApp(Protocol):
|
|
42
|
+
language: str
|
|
43
|
+
mock: bool
|
|
44
|
+
tracker: UsageRateTracker
|
|
45
|
+
codex_tracker: UsageRateTracker
|
|
46
|
+
agy_tracker: UsageRateTracker
|
|
47
|
+
burn_rate_trackers: dict[str, BurnRateTracker]
|
|
48
|
+
latest_state: menubar_state.PopoverState
|
|
49
|
+
active_panel: Any
|
|
50
|
+
_history_load_error_key: str | None
|
|
51
|
+
|
|
52
|
+
def _fetch(self) -> Coroutine[Any, Any, PollOutcome]: ...
|
|
53
|
+
|
|
54
|
+
def _load_codex_refresh_result(self) -> dict[str, Any]: ...
|
|
55
|
+
|
|
56
|
+
def _load_history_entries(
|
|
57
|
+
self, *, scan: menubar_state.HistorySourceScan | None = None
|
|
58
|
+
) -> list[UsageEntry]: ...
|
|
59
|
+
|
|
60
|
+
def _project_rows(
|
|
61
|
+
self, hours_back: int = 24, entries: list[UsageEntry] | None = None
|
|
62
|
+
) -> ProjectRows: ...
|
|
63
|
+
|
|
64
|
+
def _statusline_setup_available(self) -> bool: ...
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass(slots=True)
|
|
68
|
+
class RefreshSources:
|
|
69
|
+
codex_result: dict[str, Any]
|
|
70
|
+
history_scan: menubar_state.HistorySourceScan | None
|
|
71
|
+
agy_result: menubar_agy.AgyRefreshResult
|
|
72
|
+
agy_projection: menubar_agy.AgyQuotaProjection
|
|
73
|
+
animation_groups: tuple[int, int, int]
|
|
74
|
+
debug_timing: bool
|
|
75
|
+
|
|
76
|
+
def measure(self, stage: str, started_at: float) -> None:
|
|
77
|
+
if self.debug_timing:
|
|
78
|
+
elapsed_ms = (time.monotonic() - started_at) * 1000
|
|
79
|
+
logger.debug("refresh_timing stage=%s elapsed_ms=%.1f", stage, elapsed_ms)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def load_sources(app: _RefreshApp) -> RefreshSources:
|
|
83
|
+
debug_timing = os.environ.get("USAGE_DEBUG") == "1"
|
|
84
|
+
animation_groups = (
|
|
85
|
+
int(getattr(app, "critter_group", 0)),
|
|
86
|
+
int(getattr(app, "dragon_group", 0)),
|
|
87
|
+
int(getattr(app, "lion_group", 0)),
|
|
88
|
+
)
|
|
89
|
+
started_at = time.monotonic() if debug_timing else 0.0
|
|
90
|
+
codex_result = app._load_codex_refresh_result()
|
|
91
|
+
history_scan = codex_result.get("_history_scan")
|
|
92
|
+
if debug_timing:
|
|
93
|
+
elapsed_ms = (time.monotonic() - started_at) * 1000
|
|
94
|
+
logger.debug("refresh_timing stage=%s elapsed_ms=%.1f", "codex_load", elapsed_ms)
|
|
95
|
+
started_at = time.monotonic() if debug_timing else 0.0
|
|
96
|
+
agy_result = menubar_agy.load_refresh_result(app.language)
|
|
97
|
+
if debug_timing:
|
|
98
|
+
elapsed_ms = (time.monotonic() - started_at) * 1000
|
|
99
|
+
logger.debug("refresh_timing stage=%s elapsed_ms=%.1f", "agy_load", elapsed_ms)
|
|
100
|
+
agy_projection = agy_result.projection or menubar_agy.fallback_projection(app.language)
|
|
101
|
+
return RefreshSources(
|
|
102
|
+
codex_result=codex_result,
|
|
103
|
+
history_scan=(
|
|
104
|
+
history_scan if isinstance(history_scan, menubar_state.HistorySourceScan) else None
|
|
105
|
+
),
|
|
106
|
+
agy_result=agy_result,
|
|
107
|
+
agy_projection=agy_projection,
|
|
108
|
+
animation_groups=animation_groups,
|
|
109
|
+
debug_timing=debug_timing,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def build_result(app: _RefreshApp, sources: RefreshSources) -> dict[str, Any]:
|
|
114
|
+
codex_result = sources.codex_result
|
|
115
|
+
agy_result = sources.agy_result
|
|
116
|
+
agy_projection = sources.agy_projection
|
|
117
|
+
animation_groups = sources.animation_groups
|
|
118
|
+
fallback_state = getattr(app, "latest_state", menubar_state._empty_state(app.language))
|
|
119
|
+
project_rows = list(fallback_state.projects)
|
|
120
|
+
project_rows_yesterday = list(fallback_state.projects_yesterday)
|
|
121
|
+
project_rows_7d = list(fallback_state.projects_7d)
|
|
122
|
+
project_rows_30d = list(fallback_state.projects_30d)
|
|
123
|
+
project_rows_all = list(fallback_state.projects_all)
|
|
124
|
+
today_text = fallback_state.today_text
|
|
125
|
+
yesterday_text = fallback_state.yesterday_text
|
|
126
|
+
statusline = fallback_state.statusline
|
|
127
|
+
hide_claude = fallback_state.hide_claude
|
|
128
|
+
hide_codex = fallback_state.hide_codex
|
|
129
|
+
hide_agy = agy_result.hide_agy or _hide_agy_enabled()
|
|
130
|
+
card_order = _quota_card_order()
|
|
131
|
+
try:
|
|
132
|
+
started_at = time.monotonic() if sources.debug_timing else 0.0
|
|
133
|
+
if sources.history_scan is not None:
|
|
134
|
+
all_entries = app._load_history_entries(scan=sources.history_scan)
|
|
135
|
+
else:
|
|
136
|
+
all_entries = app._load_history_entries()
|
|
137
|
+
sources.measure("history_load", started_at)
|
|
138
|
+
started_at = time.monotonic() if sources.debug_timing else 0.0
|
|
139
|
+
if app.mock:
|
|
140
|
+
project_rows = app._project_rows(hours_back=24, entries=all_entries)
|
|
141
|
+
project_rows_yesterday = project_rows
|
|
142
|
+
project_rows_7d = app._project_rows(hours_back=168, entries=all_entries)
|
|
143
|
+
project_rows_30d = app._project_rows(hours_back=720, entries=all_entries)
|
|
144
|
+
project_rows_all = app._project_rows(hours_back=0, entries=all_entries)
|
|
145
|
+
else:
|
|
146
|
+
(
|
|
147
|
+
project_rows,
|
|
148
|
+
project_rows_yesterday,
|
|
149
|
+
project_rows_7d,
|
|
150
|
+
project_rows_30d,
|
|
151
|
+
project_rows_all,
|
|
152
|
+
) = menubar_state.project_rows_for_windows(all_entries)
|
|
153
|
+
sources.measure("project_rows_windows", started_at)
|
|
154
|
+
today_text = menubar_state._today_title(app.mock, app.language, entries=all_entries)
|
|
155
|
+
yesterday_text = menubar_state._yesterday_title(
|
|
156
|
+
app.mock, app.language, entries=all_entries
|
|
157
|
+
)
|
|
158
|
+
statusline = menubar_state._statusline_payload(app.language)
|
|
159
|
+
hide_claude = _hide_claude_enabled()
|
|
160
|
+
hide_codex = _hide_codex_enabled()
|
|
161
|
+
hide_agy = agy_result.hide_agy or _hide_agy_enabled()
|
|
162
|
+
except Exception:
|
|
163
|
+
if os.environ.get("USAGE_DEBUG") == "1":
|
|
164
|
+
logger.warning("local usage refresh failed", exc_info=True)
|
|
165
|
+
try:
|
|
166
|
+
started_at = time.monotonic() if sources.debug_timing else 0.0
|
|
167
|
+
outcome = asyncio.run(app._fetch())
|
|
168
|
+
sources.measure("fetch", started_at)
|
|
169
|
+
codex_rows = codex_result["codex_rows"]
|
|
170
|
+
codex_5h_pct = codex_result["codex_5h_pct"]
|
|
171
|
+
codex_model = codex_result.get("codex_model", "unknown")
|
|
172
|
+
codex_stale = codex_result.get("codex_stale")
|
|
173
|
+
codex_credits = codex_result.get("codex_credits")
|
|
174
|
+
show_install_button = (
|
|
175
|
+
not hide_claude
|
|
176
|
+
and outcome.state == PollState.TOKEN_ERROR
|
|
177
|
+
and app._statusline_setup_available()
|
|
178
|
+
)
|
|
179
|
+
service_statuses = (
|
|
180
|
+
get_service_status(CLAUDE_STATUS),
|
|
181
|
+
get_service_status(CODEX_STATUS),
|
|
182
|
+
)
|
|
183
|
+
group = int(app.tracker.group())
|
|
184
|
+
try:
|
|
185
|
+
codex_group = int(app.codex_tracker.group())
|
|
186
|
+
except Exception:
|
|
187
|
+
codex_group = 0
|
|
188
|
+
if os.environ.get("USAGE_DEBUG") == "1":
|
|
189
|
+
logger.warning("Codex animation group load failed", exc_info=True)
|
|
190
|
+
try:
|
|
191
|
+
agy_group = int(app.agy_tracker.group())
|
|
192
|
+
except Exception:
|
|
193
|
+
agy_group = 0
|
|
194
|
+
if os.environ.get("USAGE_DEBUG") == "1":
|
|
195
|
+
logger.warning("Antigravity animation group load failed", exc_info=True)
|
|
196
|
+
animation_groups = (
|
|
197
|
+
group,
|
|
198
|
+
codex_group,
|
|
199
|
+
agy_group,
|
|
200
|
+
)
|
|
201
|
+
state = menubar_state.build_popover_state(
|
|
202
|
+
outcome=outcome,
|
|
203
|
+
codex_rows=codex_rows,
|
|
204
|
+
agy_rows=(agy_projection.session, agy_projection.weekly),
|
|
205
|
+
agy_group_name=agy_projection.group_name,
|
|
206
|
+
projects=project_rows,
|
|
207
|
+
projects_yesterday=project_rows_yesterday,
|
|
208
|
+
projects_7d=project_rows_7d,
|
|
209
|
+
projects_30d=project_rows_30d,
|
|
210
|
+
projects_all=project_rows_all,
|
|
211
|
+
language=app.language,
|
|
212
|
+
group=group,
|
|
213
|
+
burn_rate_trackers=app.burn_rate_trackers,
|
|
214
|
+
today_text=today_text,
|
|
215
|
+
yesterday_text=yesterday_text,
|
|
216
|
+
statusline=statusline,
|
|
217
|
+
show_install_button=show_install_button,
|
|
218
|
+
hide_claude=hide_claude,
|
|
219
|
+
hide_codex=hide_codex,
|
|
220
|
+
hide_agy=hide_agy,
|
|
221
|
+
codex_stale=codex_stale,
|
|
222
|
+
codex_credits=codex_credits,
|
|
223
|
+
agy_stale=agy_projection.stale,
|
|
224
|
+
card_order=card_order,
|
|
225
|
+
history_error=menubar_state.history_load_error_state(
|
|
226
|
+
app._history_load_error_key, app.language
|
|
227
|
+
),
|
|
228
|
+
service_statuses=service_statuses,
|
|
229
|
+
)
|
|
230
|
+
if outcome.snapshot is not None:
|
|
231
|
+
window_keeper.maybe_ping(
|
|
232
|
+
outcome.snapshot.current_reset_at,
|
|
233
|
+
outcome.snapshot.current_percent,
|
|
234
|
+
outcome.snapshot.data_source,
|
|
235
|
+
app.mock,
|
|
236
|
+
)
|
|
237
|
+
agy_window_keeper.maybe_ping(agy_result, app.mock)
|
|
238
|
+
except Exception as exc:
|
|
239
|
+
if os.environ.get("USAGE_DEBUG") == "1":
|
|
240
|
+
logger.warning("refresh failed", exc_info=True)
|
|
241
|
+
codex_rows = codex_result["codex_rows"]
|
|
242
|
+
codex_5h_pct = codex_result["codex_5h_pct"]
|
|
243
|
+
codex_model = codex_result.get("codex_model", "unknown")
|
|
244
|
+
state = menubar_state._error_state(type(exc).__name__, app.mock, app.language)
|
|
245
|
+
state.codex_session = codex_rows[0]
|
|
246
|
+
state.codex_weekly = codex_rows[1]
|
|
247
|
+
state.codex_stale = codex_result.get("codex_stale")
|
|
248
|
+
state.agy_session = agy_projection.session
|
|
249
|
+
state.agy_weekly = agy_projection.weekly
|
|
250
|
+
state.agy_group_name = agy_projection.group_name
|
|
251
|
+
state.agy_stale = agy_projection.stale
|
|
252
|
+
state.history_error = menubar_state.history_load_error_state(
|
|
253
|
+
app._history_load_error_key, app.language
|
|
254
|
+
)
|
|
255
|
+
state.projects = project_rows
|
|
256
|
+
state.projects_yesterday = project_rows_yesterday
|
|
257
|
+
state.projects_7d = project_rows_7d
|
|
258
|
+
state.projects_30d = project_rows_30d
|
|
259
|
+
state.projects_all = project_rows_all
|
|
260
|
+
state.today_text = today_text
|
|
261
|
+
state.yesterday_text = yesterday_text
|
|
262
|
+
state.statusline = statusline
|
|
263
|
+
state.hide_claude = hide_claude
|
|
264
|
+
state.hide_codex = hide_codex
|
|
265
|
+
state.hide_agy = hide_agy
|
|
266
|
+
state.card_order = card_order
|
|
267
|
+
|
|
268
|
+
# Talent-market data is panel-local (no quota numbers). Fetch it
|
|
269
|
+
# only when that panel is active so classic/matrix users never pay
|
|
270
|
+
# the subprocess cost. list_state already swallows CLI errors and
|
|
271
|
+
# returns {ok:False,...}, so the panel shows its empty state.
|
|
272
|
+
active_panel = getattr(app, "active_panel", None)
|
|
273
|
+
if active_panel is not None and active_panel.id == "talent_market":
|
|
274
|
+
try:
|
|
275
|
+
state.talent = talent_market_bridge.list_state(app.language)
|
|
276
|
+
except Exception:
|
|
277
|
+
if os.environ.get("USAGE_DEBUG") == "1":
|
|
278
|
+
logger.warning("talent market state load failed", exc_info=True)
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
"state": state,
|
|
282
|
+
"codex_5h_pct": codex_5h_pct,
|
|
283
|
+
"codex_model": codex_model,
|
|
284
|
+
"animation_groups": animation_groups,
|
|
285
|
+
}
|