codex-usage-tracking 0.3.0__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.
- codex_usage_tracker/__init__.py +7 -0
- codex_usage_tracker/__main__.py +6 -0
- codex_usage_tracker/allowance.py +759 -0
- codex_usage_tracker/api_payloads.py +90 -0
- codex_usage_tracker/cli.py +1326 -0
- codex_usage_tracker/context.py +410 -0
- codex_usage_tracker/costing.py +176 -0
- codex_usage_tracker/dashboard.py +389 -0
- codex_usage_tracker/diagnostics.py +624 -0
- codex_usage_tracker/formatting.py +225 -0
- codex_usage_tracker/json_contracts.py +350 -0
- codex_usage_tracker/mcp_server.py +371 -0
- codex_usage_tracker/models.py +92 -0
- codex_usage_tracker/parser.py +491 -0
- codex_usage_tracker/paths.py +18 -0
- codex_usage_tracker/plugin_data/__init__.py +1 -0
- codex_usage_tracker/plugin_data/assets/icon.svg +8 -0
- codex_usage_tracker/plugin_data/dashboard/dashboard.css +954 -0
- codex_usage_tracker/plugin_data/dashboard/dashboard.js +1833 -0
- codex_usage_tracker/plugin_data/dashboard/dashboard_data.js +155 -0
- codex_usage_tracker/plugin_data/dashboard/dashboard_format.js +132 -0
- codex_usage_tracker/plugin_data/dashboard/dashboard_state.js +157 -0
- codex_usage_tracker/plugin_data/dashboard/dashboard_template.html +141 -0
- codex_usage_tracker/plugin_data/docs/assets/dashboard-calls.png +0 -0
- codex_usage_tracker/plugin_data/docs/assets/dashboard-details.png +0 -0
- codex_usage_tracker/plugin_data/docs/assets/dashboard-insights.png +0 -0
- codex_usage_tracker/plugin_data/docs/assets/dashboard-threads.png +0 -0
- codex_usage_tracker/plugin_data/docs/dashboard-guide.html +136 -0
- codex_usage_tracker/plugin_data/rate_cards/codex-credit-rates.json +69 -0
- codex_usage_tracker/plugin_data/skills/codex-usage-api/SKILL.md +62 -0
- codex_usage_tracker/plugin_data/skills/codex-usage-tracker/SKILL.md +47 -0
- codex_usage_tracker/plugin_installer.py +312 -0
- codex_usage_tracker/pricing.py +57 -0
- codex_usage_tracker/pricing_config.py +223 -0
- codex_usage_tracker/pricing_estimates.py +44 -0
- codex_usage_tracker/pricing_openai.py +253 -0
- codex_usage_tracker/projects.py +347 -0
- codex_usage_tracker/recommendations.py +270 -0
- codex_usage_tracker/reports.py +637 -0
- codex_usage_tracker/schema.py +71 -0
- codex_usage_tracker/server.py +400 -0
- codex_usage_tracker/store.py +666 -0
- codex_usage_tracker/support.py +147 -0
- codex_usage_tracker/threads.py +183 -0
- codex_usage_tracking-0.3.0.dist-info/METADATA +278 -0
- codex_usage_tracking-0.3.0.dist-info/RECORD +50 -0
- codex_usage_tracking-0.3.0.dist-info/WHEEL +5 -0
- codex_usage_tracking-0.3.0.dist-info/entry_points.txt +2 -0
- codex_usage_tracking-0.3.0.dist-info/licenses/LICENSE +21 -0
- codex_usage_tracking-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
"""Static dashboard generation from aggregate-only usage rows."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import html
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import shutil
|
|
11
|
+
from importlib import resources
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from codex_usage_tracker.allowance import (
|
|
16
|
+
annotate_rows_with_allowance,
|
|
17
|
+
load_allowance_config,
|
|
18
|
+
summarize_allowance_usage,
|
|
19
|
+
)
|
|
20
|
+
from codex_usage_tracker.paths import (
|
|
21
|
+
DEFAULT_ALLOWANCE_PATH,
|
|
22
|
+
DEFAULT_DASHBOARD_PATH,
|
|
23
|
+
DEFAULT_PRICING_PATH,
|
|
24
|
+
DEFAULT_PROJECTS_PATH,
|
|
25
|
+
DEFAULT_RATE_CARD_PATH,
|
|
26
|
+
DEFAULT_THRESHOLDS_PATH,
|
|
27
|
+
)
|
|
28
|
+
from codex_usage_tracker.pricing import annotate_rows_with_efficiency, load_pricing_config
|
|
29
|
+
from codex_usage_tracker.projects import (
|
|
30
|
+
annotate_rows_with_project_identity,
|
|
31
|
+
apply_project_privacy_to_rows,
|
|
32
|
+
load_project_config,
|
|
33
|
+
project_privacy_metadata,
|
|
34
|
+
validate_privacy_mode,
|
|
35
|
+
)
|
|
36
|
+
from codex_usage_tracker.recommendations import (
|
|
37
|
+
annotate_rows_with_recommendations,
|
|
38
|
+
load_threshold_config,
|
|
39
|
+
)
|
|
40
|
+
from codex_usage_tracker.store import (
|
|
41
|
+
query_dashboard_event_count,
|
|
42
|
+
query_dashboard_events,
|
|
43
|
+
refresh_metadata,
|
|
44
|
+
)
|
|
45
|
+
from codex_usage_tracker.threads import annotate_thread_attachments
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def dashboard_payload(
|
|
49
|
+
db_path: Path,
|
|
50
|
+
limit: int | None = 5000,
|
|
51
|
+
offset: int = 0,
|
|
52
|
+
pricing_path: Path = DEFAULT_PRICING_PATH,
|
|
53
|
+
allowance_path: Path = DEFAULT_ALLOWANCE_PATH,
|
|
54
|
+
rate_card_path: Path = DEFAULT_RATE_CARD_PATH,
|
|
55
|
+
since: str | None = None,
|
|
56
|
+
api_token: str | None = None,
|
|
57
|
+
context_api_enabled: bool = False,
|
|
58
|
+
thresholds_path: Path = DEFAULT_THRESHOLDS_PATH,
|
|
59
|
+
projects_path: Path = DEFAULT_PROJECTS_PATH,
|
|
60
|
+
privacy_mode: str = "normal",
|
|
61
|
+
include_archived: bool = False,
|
|
62
|
+
) -> dict[str, object]:
|
|
63
|
+
"""Return aggregate-only dashboard data without rendering HTML."""
|
|
64
|
+
|
|
65
|
+
privacy_mode = validate_privacy_mode(privacy_mode)
|
|
66
|
+
normalized_offset = _normalize_offset(offset)
|
|
67
|
+
rows = annotate_thread_attachments(
|
|
68
|
+
query_dashboard_events(
|
|
69
|
+
db_path=db_path,
|
|
70
|
+
limit=limit,
|
|
71
|
+
offset=normalized_offset,
|
|
72
|
+
since=since,
|
|
73
|
+
include_archived=include_archived,
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
pricing = load_pricing_config(pricing_path)
|
|
77
|
+
allowance = load_allowance_config(allowance_path, rate_card_path=rate_card_path)
|
|
78
|
+
thresholds = load_threshold_config(thresholds_path)
|
|
79
|
+
projects = load_project_config(projects_path)
|
|
80
|
+
annotated_rows = annotate_rows_with_allowance(
|
|
81
|
+
annotate_rows_with_efficiency(rows, pricing),
|
|
82
|
+
allowance,
|
|
83
|
+
)
|
|
84
|
+
annotated_rows = annotate_rows_with_recommendations(annotated_rows, thresholds)
|
|
85
|
+
annotated_rows = annotate_rows_with_project_identity(annotated_rows, projects)
|
|
86
|
+
annotated_rows = apply_project_privacy_to_rows(annotated_rows, privacy_mode=privacy_mode)
|
|
87
|
+
allowance_summary = summarize_allowance_usage(annotated_rows, allowance)
|
|
88
|
+
normalized_limit = _normalize_limit(limit)
|
|
89
|
+
total_available_rows = query_dashboard_event_count(
|
|
90
|
+
db_path=db_path,
|
|
91
|
+
since=since,
|
|
92
|
+
include_archived=include_archived,
|
|
93
|
+
)
|
|
94
|
+
active_available_rows = query_dashboard_event_count(
|
|
95
|
+
db_path=db_path,
|
|
96
|
+
since=since,
|
|
97
|
+
include_archived=False,
|
|
98
|
+
)
|
|
99
|
+
all_history_available_rows = query_dashboard_event_count(
|
|
100
|
+
db_path=db_path,
|
|
101
|
+
since=since,
|
|
102
|
+
include_archived=True,
|
|
103
|
+
)
|
|
104
|
+
metadata = refresh_metadata(db_path)
|
|
105
|
+
parser_diagnostics = {
|
|
106
|
+
key.removeprefix("parser_"): _safe_int(value)
|
|
107
|
+
for key, value in metadata.items()
|
|
108
|
+
if key.startswith("parser_") and _safe_int(value)
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
"rows": annotated_rows,
|
|
112
|
+
"pricing_configured": pricing.loaded and not pricing.error,
|
|
113
|
+
"pricing_source": pricing.source,
|
|
114
|
+
"pricing_snapshot": _pricing_snapshot(pricing.loaded, pricing.source, pricing.models),
|
|
115
|
+
"allowance_configured": allowance.loaded and not allowance.error,
|
|
116
|
+
"allowance_source": allowance_summary["source"],
|
|
117
|
+
"allowance_windows": allowance_summary["windows"],
|
|
118
|
+
"allowance_error": allowance_summary["error"],
|
|
119
|
+
"rate_card_configured": allowance_summary["rate_card_loaded"],
|
|
120
|
+
"rate_card_error": allowance_summary["rate_card_error"],
|
|
121
|
+
"loaded_row_count": len(rows),
|
|
122
|
+
"total_available_rows": total_available_rows,
|
|
123
|
+
"active_available_rows": active_available_rows,
|
|
124
|
+
"all_history_available_rows": all_history_available_rows,
|
|
125
|
+
"archived_available_rows": max(all_history_available_rows - active_available_rows, 0),
|
|
126
|
+
"include_archived": include_archived,
|
|
127
|
+
"history_scope": "all-history" if include_archived else "active",
|
|
128
|
+
"limit": normalized_limit,
|
|
129
|
+
"offset": normalized_offset,
|
|
130
|
+
"has_more": (
|
|
131
|
+
normalized_limit is not None
|
|
132
|
+
and normalized_offset + len(rows) < total_available_rows
|
|
133
|
+
),
|
|
134
|
+
"next_offset": (
|
|
135
|
+
normalized_offset + len(rows)
|
|
136
|
+
if normalized_limit is not None
|
|
137
|
+
and normalized_offset + len(rows) < total_available_rows
|
|
138
|
+
else None
|
|
139
|
+
),
|
|
140
|
+
"limit_label": "All" if normalized_limit is None else str(normalized_limit),
|
|
141
|
+
"parser_diagnostics": parser_diagnostics,
|
|
142
|
+
"parser_adapter": metadata.get("parser_adapter"),
|
|
143
|
+
"api_token": api_token or "",
|
|
144
|
+
"context_api_enabled": context_api_enabled,
|
|
145
|
+
"action_thresholds": thresholds.thresholds,
|
|
146
|
+
"thresholds_configured": thresholds.loaded and not thresholds.error,
|
|
147
|
+
"thresholds_error": thresholds.error,
|
|
148
|
+
"project_configured": projects.loaded and not projects.error,
|
|
149
|
+
"project_config_error": projects.error,
|
|
150
|
+
"privacy_mode": privacy_mode,
|
|
151
|
+
"project_metadata_privacy": project_privacy_metadata(privacy_mode),
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def generate_dashboard(
|
|
156
|
+
db_path: Path,
|
|
157
|
+
output_path: Path = DEFAULT_DASHBOARD_PATH,
|
|
158
|
+
limit: int | None = 5000,
|
|
159
|
+
pricing_path: Path = DEFAULT_PRICING_PATH,
|
|
160
|
+
allowance_path: Path = DEFAULT_ALLOWANCE_PATH,
|
|
161
|
+
rate_card_path: Path = DEFAULT_RATE_CARD_PATH,
|
|
162
|
+
since: str | None = None,
|
|
163
|
+
api_token: str | None = None,
|
|
164
|
+
context_api_enabled: bool = False,
|
|
165
|
+
thresholds_path: Path = DEFAULT_THRESHOLDS_PATH,
|
|
166
|
+
projects_path: Path = DEFAULT_PROJECTS_PATH,
|
|
167
|
+
privacy_mode: str = "normal",
|
|
168
|
+
include_archived: bool = False,
|
|
169
|
+
) -> Path:
|
|
170
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
171
|
+
guide_href = _dashboard_guide_href(output_path)
|
|
172
|
+
asset_base = _dashboard_assets_href(output_path)
|
|
173
|
+
stylesheet_href = _versioned_asset_href(output_path, asset_base, "dashboard.css")
|
|
174
|
+
format_script_src = _versioned_asset_href(output_path, asset_base, "dashboard_format.js")
|
|
175
|
+
data_script_src = _versioned_asset_href(output_path, asset_base, "dashboard_data.js")
|
|
176
|
+
state_script_src = _versioned_asset_href(output_path, asset_base, "dashboard_state.js")
|
|
177
|
+
script_src = _versioned_asset_href(output_path, asset_base, "dashboard.js")
|
|
178
|
+
previous_payload = _previous_dashboard_payload(output_path)
|
|
179
|
+
payload_dict = dashboard_payload(
|
|
180
|
+
db_path=db_path,
|
|
181
|
+
limit=limit,
|
|
182
|
+
pricing_path=pricing_path,
|
|
183
|
+
allowance_path=allowance_path,
|
|
184
|
+
rate_card_path=rate_card_path,
|
|
185
|
+
since=since,
|
|
186
|
+
api_token=api_token,
|
|
187
|
+
context_api_enabled=context_api_enabled,
|
|
188
|
+
thresholds_path=thresholds_path,
|
|
189
|
+
projects_path=projects_path,
|
|
190
|
+
privacy_mode=privacy_mode,
|
|
191
|
+
include_archived=include_archived,
|
|
192
|
+
)
|
|
193
|
+
payload_dict["pricing_snapshot_warning"] = _pricing_snapshot_warning(
|
|
194
|
+
previous_payload, payload_dict
|
|
195
|
+
)
|
|
196
|
+
payload = json.dumps(payload_dict, ensure_ascii=True).replace("</", "<\\/")
|
|
197
|
+
output_path.write_text(
|
|
198
|
+
_html(
|
|
199
|
+
payload,
|
|
200
|
+
guide_href=guide_href,
|
|
201
|
+
stylesheet_href=stylesheet_href,
|
|
202
|
+
format_script_src=format_script_src,
|
|
203
|
+
data_script_src=data_script_src,
|
|
204
|
+
state_script_src=state_script_src,
|
|
205
|
+
script_src=script_src,
|
|
206
|
+
),
|
|
207
|
+
encoding="utf-8",
|
|
208
|
+
)
|
|
209
|
+
return output_path
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _normalize_limit(limit: int | None) -> int | None:
|
|
213
|
+
if limit is None or limit <= 0:
|
|
214
|
+
return None
|
|
215
|
+
return int(limit)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _normalize_offset(offset: int | None) -> int:
|
|
219
|
+
if offset is None or offset <= 0:
|
|
220
|
+
return 0
|
|
221
|
+
return int(offset)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _pricing_snapshot(
|
|
225
|
+
loaded: bool,
|
|
226
|
+
source: dict[str, Any] | None,
|
|
227
|
+
models: dict[str, dict[str, float]],
|
|
228
|
+
) -> dict[str, Any]:
|
|
229
|
+
if not loaded:
|
|
230
|
+
return {"configured": False, "fingerprint": None}
|
|
231
|
+
public_source = {
|
|
232
|
+
key: value
|
|
233
|
+
for key, value in (source or {}).items()
|
|
234
|
+
if key
|
|
235
|
+
in {
|
|
236
|
+
"name",
|
|
237
|
+
"url",
|
|
238
|
+
"tier",
|
|
239
|
+
"fetched_at",
|
|
240
|
+
"model_count",
|
|
241
|
+
"official_model_count",
|
|
242
|
+
"estimated_model_count",
|
|
243
|
+
"pinned",
|
|
244
|
+
"pinned_at",
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
public_source.setdefault("model_count", len(models))
|
|
248
|
+
rates_fingerprint = hashlib.sha256(
|
|
249
|
+
json.dumps(models, sort_keys=True, ensure_ascii=True).encode("utf-8")
|
|
250
|
+
).hexdigest()[:12]
|
|
251
|
+
fingerprint = hashlib.sha256(
|
|
252
|
+
json.dumps(
|
|
253
|
+
{**public_source, "rates_fingerprint": rates_fingerprint},
|
|
254
|
+
sort_keys=True,
|
|
255
|
+
ensure_ascii=True,
|
|
256
|
+
).encode("utf-8")
|
|
257
|
+
).hexdigest()[:12]
|
|
258
|
+
return {
|
|
259
|
+
"configured": True,
|
|
260
|
+
"fingerprint": fingerprint,
|
|
261
|
+
"rates_fingerprint": rates_fingerprint,
|
|
262
|
+
**public_source,
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _pricing_snapshot_warning(
|
|
267
|
+
previous_payload: dict[str, Any] | None, current_payload: dict[str, object]
|
|
268
|
+
) -> str | None:
|
|
269
|
+
if not previous_payload:
|
|
270
|
+
return None
|
|
271
|
+
previous = previous_payload.get("pricing_snapshot")
|
|
272
|
+
current = current_payload.get("pricing_snapshot")
|
|
273
|
+
if not isinstance(previous, dict) or not isinstance(current, dict):
|
|
274
|
+
return None
|
|
275
|
+
previous_fingerprint = previous.get("fingerprint")
|
|
276
|
+
current_fingerprint = current.get("fingerprint")
|
|
277
|
+
if not previous_fingerprint or not current_fingerprint:
|
|
278
|
+
return None
|
|
279
|
+
if previous_fingerprint == current_fingerprint:
|
|
280
|
+
return None
|
|
281
|
+
previous_label = previous.get("fetched_at") or previous.get("pinned_at") or previous_fingerprint
|
|
282
|
+
current_label = current.get("fetched_at") or current.get("pinned_at") or current_fingerprint
|
|
283
|
+
return f"Pricing snapshot changed since the previous dashboard render: {previous_label} -> {current_label}."
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _previous_dashboard_payload(output_path: Path) -> dict[str, Any] | None:
|
|
287
|
+
if not output_path.exists():
|
|
288
|
+
return None
|
|
289
|
+
try:
|
|
290
|
+
text = output_path.read_text(encoding="utf-8")
|
|
291
|
+
except OSError:
|
|
292
|
+
return None
|
|
293
|
+
match = _USAGE_DATA_RE.search(text)
|
|
294
|
+
if not match:
|
|
295
|
+
return None
|
|
296
|
+
try:
|
|
297
|
+
raw = json.loads(match.group("payload"))
|
|
298
|
+
except json.JSONDecodeError:
|
|
299
|
+
return None
|
|
300
|
+
return raw if isinstance(raw, dict) else None
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _dashboard_guide_href(output_path: Path) -> str | None:
|
|
304
|
+
override = os.environ.get("CODEX_USAGE_TRACKER_DOCS_URL")
|
|
305
|
+
if override:
|
|
306
|
+
return override
|
|
307
|
+
try:
|
|
308
|
+
docs_source = resources.files("codex_usage_tracker.plugin_data").joinpath("docs")
|
|
309
|
+
docs_target = output_path.parent / "codex-usage-tracker-guide"
|
|
310
|
+
if docs_target.exists():
|
|
311
|
+
shutil.rmtree(docs_target)
|
|
312
|
+
_copy_resource_tree(docs_source, docs_target)
|
|
313
|
+
except (FileNotFoundError, ModuleNotFoundError, OSError):
|
|
314
|
+
return None
|
|
315
|
+
return "codex-usage-tracker-guide/dashboard-guide.html"
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _dashboard_assets_href(output_path: Path) -> str:
|
|
319
|
+
assets_source = resources.files("codex_usage_tracker.plugin_data").joinpath("dashboard")
|
|
320
|
+
assets_target = output_path.parent / "codex-usage-tracker-assets"
|
|
321
|
+
if assets_target.exists():
|
|
322
|
+
shutil.rmtree(assets_target)
|
|
323
|
+
_copy_resource_tree(assets_source, assets_target)
|
|
324
|
+
return "codex-usage-tracker-assets"
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _versioned_asset_href(output_path: Path, asset_base: str, filename: str) -> str:
|
|
328
|
+
asset_path = output_path.parent / asset_base / filename
|
|
329
|
+
try:
|
|
330
|
+
digest = hashlib.sha256(asset_path.read_bytes()).hexdigest()[:12]
|
|
331
|
+
except OSError:
|
|
332
|
+
return f"{asset_base}/{filename}"
|
|
333
|
+
return f"{asset_base}/{filename}?v={digest}"
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _copy_resource_tree(source: Any, target: Path) -> None:
|
|
337
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
338
|
+
for child in source.iterdir():
|
|
339
|
+
destination = target / child.name
|
|
340
|
+
if child.is_dir():
|
|
341
|
+
_copy_resource_tree(child, destination)
|
|
342
|
+
else:
|
|
343
|
+
destination.write_bytes(child.read_bytes())
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _html(
|
|
347
|
+
payload: str,
|
|
348
|
+
guide_href: str | None = None,
|
|
349
|
+
*,
|
|
350
|
+
stylesheet_href: str = "codex-usage-tracker-assets/dashboard.css",
|
|
351
|
+
format_script_src: str = "codex-usage-tracker-assets/dashboard_format.js",
|
|
352
|
+
data_script_src: str = "codex-usage-tracker-assets/dashboard_data.js",
|
|
353
|
+
state_script_src: str = "codex-usage-tracker-assets/dashboard_state.js",
|
|
354
|
+
script_src: str = "codex-usage-tracker-assets/dashboard.js",
|
|
355
|
+
) -> str:
|
|
356
|
+
template = _read_dashboard_asset("dashboard_template.html")
|
|
357
|
+
guide_link = (
|
|
358
|
+
f'<a class="guide-link" href="{html.escape(guide_href, quote=True)}">Dashboard guide</a>'
|
|
359
|
+
if guide_href
|
|
360
|
+
else ""
|
|
361
|
+
)
|
|
362
|
+
return (
|
|
363
|
+
template.replace("__TITLE__", html.escape("Codex Usage Dashboard"))
|
|
364
|
+
.replace("__STYLESHEET_HREF__", html.escape(stylesheet_href, quote=True))
|
|
365
|
+
.replace("__GUIDE_LINK__", guide_link)
|
|
366
|
+
.replace("__PAYLOAD__", payload)
|
|
367
|
+
.replace("__FORMAT_SCRIPT_SRC__", html.escape(format_script_src, quote=True))
|
|
368
|
+
.replace("__DATA_SCRIPT_SRC__", html.escape(data_script_src, quote=True))
|
|
369
|
+
.replace("__STATE_SCRIPT_SRC__", html.escape(state_script_src, quote=True))
|
|
370
|
+
.replace("__SCRIPT_SRC__", html.escape(script_src, quote=True))
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _read_dashboard_asset(name: str) -> str:
|
|
375
|
+
asset = resources.files("codex_usage_tracker.plugin_data").joinpath("dashboard", name)
|
|
376
|
+
return asset.read_text(encoding="utf-8")
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _safe_int(value: object) -> int:
|
|
380
|
+
try:
|
|
381
|
+
return int(str(value))
|
|
382
|
+
except (TypeError, ValueError):
|
|
383
|
+
return 0
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
_USAGE_DATA_RE = re.compile(
|
|
387
|
+
r'<script id="usage-data" type="application/json">(?P<payload>.*?)</script>',
|
|
388
|
+
re.DOTALL,
|
|
389
|
+
)
|