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
panels/base.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
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
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Protocol
|
|
12
|
+
|
|
13
|
+
if sys.platform == "darwin":
|
|
14
|
+
from Foundation import NSBundle, NSUserDefaults
|
|
15
|
+
else:
|
|
16
|
+
NSBundle = None
|
|
17
|
+
NSUserDefaults = None
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from menubar import PopoverState
|
|
21
|
+
|
|
22
|
+
ACTIVE_PANEL_DEFAULTS_KEY = "usage.activePanelId"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def next_panel_eviction_id(
|
|
26
|
+
panel_ids: list[str], active_panel_id: str, pending_evictions: set[str]
|
|
27
|
+
) -> str | None:
|
|
28
|
+
return next(
|
|
29
|
+
(
|
|
30
|
+
panel_id
|
|
31
|
+
for panel_id in panel_ids
|
|
32
|
+
if panel_id != active_panel_id and panel_id not in pending_evictions
|
|
33
|
+
),
|
|
34
|
+
None,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Panel(Protocol):
|
|
39
|
+
id: str
|
|
40
|
+
i18n_key: str
|
|
41
|
+
claude_card_height: float
|
|
42
|
+
codex_card_height: float
|
|
43
|
+
agy_card_height: float
|
|
44
|
+
service_alert_height: float
|
|
45
|
+
|
|
46
|
+
def build_view(self, delegate: Any) -> Any: ...
|
|
47
|
+
def apply_state(self, view: Any, state: PopoverState) -> None: ...
|
|
48
|
+
def preferred_size(self) -> tuple[float, float]: ...
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def load_active_panel_id(defaults: Any | None = None) -> str:
|
|
52
|
+
if defaults is None and sys.platform == "win32":
|
|
53
|
+
from prefs import _load_preferences
|
|
54
|
+
|
|
55
|
+
value = _load_preferences().get(ACTIVE_PANEL_DEFAULTS_KEY)
|
|
56
|
+
return str(value) if isinstance(value, str) and value else "classic"
|
|
57
|
+
assert NSUserDefaults is not None
|
|
58
|
+
store = defaults if defaults is not None else NSUserDefaults.standardUserDefaults()
|
|
59
|
+
value = store.stringForKey_(ACTIVE_PANEL_DEFAULTS_KEY)
|
|
60
|
+
return str(value) if value else "classic"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def save_active_panel_id(panel_id: str, defaults: Any | None = None) -> None:
|
|
64
|
+
if defaults is None and sys.platform == "win32":
|
|
65
|
+
from prefs import _load_preferences, _save_preferences
|
|
66
|
+
|
|
67
|
+
preferences = _load_preferences()
|
|
68
|
+
preferences[ACTIVE_PANEL_DEFAULTS_KEY] = panel_id
|
|
69
|
+
_save_preferences(preferences)
|
|
70
|
+
return
|
|
71
|
+
assert NSUserDefaults is not None
|
|
72
|
+
store = defaults if defaults is not None else NSUserDefaults.standardUserDefaults()
|
|
73
|
+
store.setObject_forKey_(panel_id, ACTIVE_PANEL_DEFAULTS_KEY)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def resolve_resource(name: str) -> str:
|
|
77
|
+
bundle = NSBundle.mainBundle() if NSBundle is not None else None
|
|
78
|
+
if bundle is not None:
|
|
79
|
+
stem, _, ext = name.rpartition(".")
|
|
80
|
+
path = bundle.pathForResource_ofType_(stem, ext)
|
|
81
|
+
if path:
|
|
82
|
+
return str(path)
|
|
83
|
+
return str(Path(__file__).resolve().parent.parent / "assets" / name)
|
panels/dynamic_height.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
# Copyright (C) 2026 lollapalooza <https://github.com/aqua5230>
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import math
|
|
7
|
+
|
|
8
|
+
MIN_PANEL_HEIGHT = 240.0
|
|
9
|
+
|
|
10
|
+
CONTENT_HEIGHT_SCRIPT = """
|
|
11
|
+
<script>
|
|
12
|
+
(function() {
|
|
13
|
+
var applyState = window.usageApplyState;
|
|
14
|
+
if (typeof applyState !== "function") return;
|
|
15
|
+
var scheduled = false;
|
|
16
|
+
var lastPostedHeight = null;
|
|
17
|
+
function naturalContentHeight() {
|
|
18
|
+
var wrap = document.querySelector(".wrap");
|
|
19
|
+
if (!wrap) return null;
|
|
20
|
+
// A panel can explicitly mark a flexible region whose current laid-out
|
|
21
|
+
// height is part of the design (for example, world_cup's empty pitch).
|
|
22
|
+
// Preserve only those declared floors while releasing the viewport height
|
|
23
|
+
// chain; ordinary content can still contract when a quota row disappears.
|
|
24
|
+
var floors = Array.from(
|
|
25
|
+
wrap.querySelectorAll("[data-usage-height-floor]"),
|
|
26
|
+
function(element) {
|
|
27
|
+
return {
|
|
28
|
+
element: element,
|
|
29
|
+
height: element.getBoundingClientRect().height,
|
|
30
|
+
minHeight: element.style.minHeight
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
// Panels nest .wrap differently: most put it straight in <body>, but the
|
|
35
|
+
// viewport-based ones (world_cup, aquarium, black_hole, ...) insert a
|
|
36
|
+
// padded .viewport in between. Walk the real ancestor chain instead of
|
|
37
|
+
// assuming a fixed depth, so every 100%-height link is released and every
|
|
38
|
+
// layer's spacing is counted.
|
|
39
|
+
var chain = [];
|
|
40
|
+
for (var element = wrap; element; element = element.parentElement) {
|
|
41
|
+
chain.push(element);
|
|
42
|
+
}
|
|
43
|
+
var properties = ["height", "minHeight", "maxHeight"];
|
|
44
|
+
var saved = chain.map(function(element) {
|
|
45
|
+
return properties.map(function(property) { return element.style[property]; });
|
|
46
|
+
});
|
|
47
|
+
try {
|
|
48
|
+
chain.forEach(function(element) {
|
|
49
|
+
element.style.height = "auto";
|
|
50
|
+
element.style.minHeight = "0";
|
|
51
|
+
element.style.maxHeight = "none";
|
|
52
|
+
});
|
|
53
|
+
floors.forEach(function(floor) {
|
|
54
|
+
floor.element.style.minHeight = floor.height + "px";
|
|
55
|
+
});
|
|
56
|
+
// Force reflow with viewport constraints disabled. Restoration remains
|
|
57
|
+
// in this synchronous task, so the temporary styles are never painted.
|
|
58
|
+
var total = wrap.getBoundingClientRect().height;
|
|
59
|
+
chain.forEach(function(element, index) {
|
|
60
|
+
var style = window.getComputedStyle(element);
|
|
61
|
+
total += (parseFloat(style.marginTop) || 0) + (parseFloat(style.marginBottom) || 0);
|
|
62
|
+
// wrap's own rect already covers its padding and border; every
|
|
63
|
+
// ancestor wraps additional spacing around the measured box.
|
|
64
|
+
if (index === 0) return;
|
|
65
|
+
total +=
|
|
66
|
+
(parseFloat(style.paddingTop) || 0) + (parseFloat(style.paddingBottom) || 0) +
|
|
67
|
+
(parseFloat(style.borderTopWidth) || 0) + (parseFloat(style.borderBottomWidth) || 0);
|
|
68
|
+
});
|
|
69
|
+
return Math.ceil(total);
|
|
70
|
+
} finally {
|
|
71
|
+
chain.forEach(function(element, elementIndex) {
|
|
72
|
+
properties.forEach(function(property, propertyIndex) {
|
|
73
|
+
element.style[property] = saved[elementIndex][propertyIndex];
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
floors.forEach(function(floor) {
|
|
77
|
+
floor.element.style.minHeight = floor.minHeight;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function reportContentHeight() {
|
|
82
|
+
scheduled = false;
|
|
83
|
+
var height = naturalContentHeight();
|
|
84
|
+
var bridge = window.webkit && window.webkit.messageHandlers
|
|
85
|
+
&& window.webkit.messageHandlers.usage;
|
|
86
|
+
if (Number.isFinite(height) && height > 0 && height !== lastPostedHeight && bridge
|
|
87
|
+
&& typeof bridge.postMessage === "function") {
|
|
88
|
+
lastPostedHeight = height;
|
|
89
|
+
bridge.postMessage(JSON.stringify({ action: "content_height", height: height }));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function requestContentHeight() {
|
|
93
|
+
if (scheduled) return;
|
|
94
|
+
scheduled = true;
|
|
95
|
+
// Measure after the browser has committed DOM, font, and layout changes.
|
|
96
|
+
// A second frame catches WebView2's first visible layout without relying
|
|
97
|
+
// on a user clicking another control.
|
|
98
|
+
requestAnimationFrame(function() {
|
|
99
|
+
requestAnimationFrame(reportContentHeight);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
window.usageRequestContentHeight = requestContentHeight;
|
|
103
|
+
window.usageApplyState = function usageApplyStateWithDynamicHeight(state) {
|
|
104
|
+
var result = applyState.apply(this, arguments);
|
|
105
|
+
requestContentHeight();
|
|
106
|
+
return result;
|
|
107
|
+
};
|
|
108
|
+
var wrap = document.querySelector(".wrap");
|
|
109
|
+
if (wrap && typeof MutationObserver === "function") {
|
|
110
|
+
new MutationObserver(requestContentHeight).observe(wrap, {
|
|
111
|
+
childList: true,
|
|
112
|
+
characterData: true,
|
|
113
|
+
subtree: true
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
if (wrap && typeof ResizeObserver === "function") {
|
|
117
|
+
new ResizeObserver(requestContentHeight).observe(wrap);
|
|
118
|
+
}
|
|
119
|
+
if (document.fonts && document.fonts.ready) {
|
|
120
|
+
document.fonts.ready.then(requestContentHeight);
|
|
121
|
+
}
|
|
122
|
+
requestContentHeight();
|
|
123
|
+
})();
|
|
124
|
+
</script>
|
|
125
|
+
""".strip()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def inject_content_height_script(html: str) -> str:
|
|
129
|
+
"""Install the wrapper after the panel has defined usageApplyState."""
|
|
130
|
+
return html.replace("</body>", f"{CONTENT_HEIGHT_SCRIPT}\n</body>", 1)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def clamp_content_height(height: object, maximum: float) -> float | None:
|
|
134
|
+
"""Validate an untrusted JS measurement and clamp it to the usable screen."""
|
|
135
|
+
if isinstance(height, bool) or not isinstance(height, (int, float)):
|
|
136
|
+
return None
|
|
137
|
+
value = float(height)
|
|
138
|
+
if not math.isfinite(value) or value <= 0 or maximum < MIN_PANEL_HEIGHT:
|
|
139
|
+
return None
|
|
140
|
+
return min(max(value, MIN_PANEL_HEIGHT), maximum)
|
panels/payload.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
# Copyright (C) 2026 lollapalooza <https://github.com/aqua5230>
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import base64
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
from functools import cache, lru_cache
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import TYPE_CHECKING, Any
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from menubar_state import PopoverState, QuotaRowState
|
|
16
|
+
|
|
17
|
+
CORE_SCRIPT_FILENAME = "panels/panel_core.js"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def resolve_resource(name: str) -> str:
|
|
21
|
+
resource_root = os.environ.get("RESOURCEPATH")
|
|
22
|
+
if resource_root:
|
|
23
|
+
bundled = Path(resource_root) / name
|
|
24
|
+
if bundled.exists():
|
|
25
|
+
return str(bundled)
|
|
26
|
+
frozen_root = getattr(sys, "_MEIPASS", None)
|
|
27
|
+
if frozen_root:
|
|
28
|
+
bundled = Path(frozen_root) / "assets" / name
|
|
29
|
+
if bundled.exists():
|
|
30
|
+
return str(bundled)
|
|
31
|
+
return str(Path(__file__).resolve().parent.parent / "assets" / name)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _i18n_path() -> Path:
|
|
35
|
+
resource_root = os.environ.get("RESOURCEPATH")
|
|
36
|
+
if resource_root:
|
|
37
|
+
bundled = Path(resource_root) / "i18n.json"
|
|
38
|
+
if bundled.exists():
|
|
39
|
+
return bundled
|
|
40
|
+
frozen_root = getattr(sys, "_MEIPASS", None)
|
|
41
|
+
if frozen_root:
|
|
42
|
+
bundled = Path(frozen_root) / "i18n.json"
|
|
43
|
+
if bundled.exists():
|
|
44
|
+
return bundled
|
|
45
|
+
return Path(__file__).resolve().parent.parent / "i18n.json"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _new_state_payload(view: Any, payload: dict[str, object]) -> str | None:
|
|
49
|
+
if payload == getattr(view, "_last_injected_state", None):
|
|
50
|
+
return None
|
|
51
|
+
encoded = json.dumps(payload, ensure_ascii=False, separators=(",", ":"))
|
|
52
|
+
view._last_injected_state = payload
|
|
53
|
+
if encoded == view._last_injected_payload:
|
|
54
|
+
return None
|
|
55
|
+
view._last_injected_payload = encoded
|
|
56
|
+
return encoded
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@cache
|
|
60
|
+
def _load_panel_html(filename: str) -> str:
|
|
61
|
+
html = Path(resolve_resource(f"panels/{filename}")).read_text(encoding="utf-8")
|
|
62
|
+
return (
|
|
63
|
+
html.replace("{{CLAUDE_ICON}}", _data_uri("claude.webp"))
|
|
64
|
+
.replace("{{CODEX_ICON}}", _data_uri("codex.webp"))
|
|
65
|
+
.replace("{{CORE_SCRIPT}}", _load_core_script())
|
|
66
|
+
.replace("{{I18N_BUNDLE}}", json.dumps(_load_i18n_bundle(), ensure_ascii=False))
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@lru_cache(maxsize=1)
|
|
71
|
+
def _load_core_script() -> str:
|
|
72
|
+
return Path(resolve_resource(CORE_SCRIPT_FILENAME)).read_text(encoding="utf-8")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@lru_cache(maxsize=1)
|
|
76
|
+
def _load_i18n_bundle() -> dict[str, dict[str, str]]:
|
|
77
|
+
data = json.loads(_i18n_path().read_text(encoding="utf-8"))
|
|
78
|
+
return {
|
|
79
|
+
str(lang): {str(key): str(value) for key, value in values.items()}
|
|
80
|
+
for lang, values in data.items()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@lru_cache(maxsize=4)
|
|
85
|
+
def _data_uri(asset_name: str) -> str:
|
|
86
|
+
path = Path(resolve_resource(asset_name))
|
|
87
|
+
mime = "image/png" if path.suffix.lower() == ".png" else "image/webp"
|
|
88
|
+
data = base64.b64encode(path.read_bytes()).decode("ascii")
|
|
89
|
+
return f"data:{mime};base64,{data}"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _row_payload(row: QuotaRowState) -> dict[str, object]:
|
|
93
|
+
return {
|
|
94
|
+
"percent": row.percent,
|
|
95
|
+
"percentText": row.percent_text,
|
|
96
|
+
"resetText": row.reset_text,
|
|
97
|
+
"warning": row.warning,
|
|
98
|
+
"available": row.available,
|
|
99
|
+
"title": row.title,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _state_payload(
|
|
104
|
+
state: PopoverState, *, system_accent_color: str | None = None
|
|
105
|
+
) -> dict[str, object]:
|
|
106
|
+
codex_rows = {
|
|
107
|
+
key: _row_payload(row)
|
|
108
|
+
for key, row in (("session", state.codex_session), ("weekly", state.codex_weekly))
|
|
109
|
+
if row.title
|
|
110
|
+
}
|
|
111
|
+
project_payloads = []
|
|
112
|
+
for rows in (
|
|
113
|
+
state.projects,
|
|
114
|
+
state.projects_yesterday,
|
|
115
|
+
state.projects_7d,
|
|
116
|
+
state.projects_30d,
|
|
117
|
+
state.projects_all,
|
|
118
|
+
):
|
|
119
|
+
project_payloads.append(
|
|
120
|
+
[
|
|
121
|
+
{
|
|
122
|
+
"name": name,
|
|
123
|
+
"tokens": tokens,
|
|
124
|
+
"tokensText": _fmt_tokens(tokens),
|
|
125
|
+
"costText": _fmt_cost(cost),
|
|
126
|
+
}
|
|
127
|
+
for name, tokens, cost in rows
|
|
128
|
+
]
|
|
129
|
+
)
|
|
130
|
+
payload: dict[str, object] = {
|
|
131
|
+
"language": state.language,
|
|
132
|
+
"claude": {
|
|
133
|
+
"session": _row_payload(state.claude_session),
|
|
134
|
+
"weekly": _row_payload(state.claude_weekly),
|
|
135
|
+
},
|
|
136
|
+
"codex": {
|
|
137
|
+
**codex_rows,
|
|
138
|
+
"stale": state.codex_stale,
|
|
139
|
+
"credits": state.codex_credits,
|
|
140
|
+
},
|
|
141
|
+
"agy": {
|
|
142
|
+
"session": _row_payload(state.agy_session),
|
|
143
|
+
"weekly": _row_payload(state.agy_weekly),
|
|
144
|
+
"groupName": state.agy_group_name,
|
|
145
|
+
"stale": state.agy_stale,
|
|
146
|
+
},
|
|
147
|
+
"projects": project_payloads[0],
|
|
148
|
+
"projectsYesterday": project_payloads[1],
|
|
149
|
+
"projects7d": project_payloads[2],
|
|
150
|
+
"projects30d": project_payloads[3],
|
|
151
|
+
"projectsAll": project_payloads[4],
|
|
152
|
+
"hideClaude": state.hide_claude,
|
|
153
|
+
"hideCodex": state.hide_codex,
|
|
154
|
+
"hideAgy": state.hide_agy,
|
|
155
|
+
"cardOrder": list(state.card_order),
|
|
156
|
+
"historyError": state.history_error,
|
|
157
|
+
"statusline": state.statusline,
|
|
158
|
+
"talent": state.talent,
|
|
159
|
+
"footer": {
|
|
160
|
+
"rate": state.rate_text,
|
|
161
|
+
"status": state.status_text,
|
|
162
|
+
"today": state.today_text,
|
|
163
|
+
"yesterday": state.yesterday_text,
|
|
164
|
+
"serviceAlerts": list(state.service_alerts),
|
|
165
|
+
"showInstall": state.show_install_button,
|
|
166
|
+
},
|
|
167
|
+
}
|
|
168
|
+
if system_accent_color is not None:
|
|
169
|
+
payload["system_accent_color"] = system_accent_color
|
|
170
|
+
return payload
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _fmt_tokens(tokens: int) -> str:
|
|
174
|
+
return f"{tokens:,}"
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _fmt_cost(cost: float | None) -> str:
|
|
178
|
+
return "--" if cost is None else f"${cost:.2f}"
|