OpenAICodexProvider replaces the openai stub. It reads the rate-limit snapshot
the Codex CLI already records locally (no second auth): each turn Codex writes a
token_count event into $CODEX_HOME/sessions/**/rollout-*.jsonl whose
payload.rate_limits mirrors Claude's model — primary (5h) + secondary (weekly),
each with used_percent + resets_at + plan_type. The provider surfaces the
freshest such snapshot; a window whose reset time has passed is reported as a
fresh 0% (local read, so current in-session and self-heals between sessions).
primary -> s/sr, secondary -> w/wr, plan_type -> status ("Plus"/…),
rate_limit_reached_type -> "limited". No per-token cost (subscription), so the
two rate-limit bars are the metric, per the locked v3 decision.
Verified against the real ~/.codex: full daemon payload with openai active =
{pv:openai, pnm:"OpenAI Codex", ac:0x10a37f, st:"Plus", ok:true, weekly reset}.
registry test updated (openai now real, zai still stub); +7 provider tests.
113 passed, 2 Linux-only failures (baseline). spec: hiddenimport added.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
101 lines
3.7 KiB
RPMSpec
101 lines
3.7 KiB
RPMSpec
# -*- mode: python ; coding: utf-8 -*-
|
|
#
|
|
# clawdmeter.spec — build the standalone Windows daemon + tray executable.
|
|
#
|
|
# Produces a single, self-contained dist\Clawdmeter.exe that bundles its own
|
|
# Python, bleak (WinRT BLE) and the winrt media projection — so it runs on ANY
|
|
# Windows 11 machine with no Python and no pip install. The exe is the tray app
|
|
# (daemon thread + notification-area icon + login-autostart toggle); it is built
|
|
# windowed, so there is no console window and it logs to
|
|
# %LOCALAPPDATA%\Clawdmeter\daemon.log.
|
|
#
|
|
# Build (from the repo root, inside the venv) — or just run build-exe.ps1:
|
|
# .venv\Scripts\python.exe -m PyInstaller --noconfirm clawdmeter.spec
|
|
#
|
|
# winrt + bleak ship C-extension projections (.pyd) that PyInstaller's static
|
|
# analysis misses: bleak imports its WinRT backend dynamically, and the
|
|
# winrt.windows.* namespaces are split across separate distributions. collect_all
|
|
# pulls in their submodules, binaries and metadata so both BLE (bluetooth) and the
|
|
# now-playing media session work inside the frozen exe. Verified on hardware:
|
|
# connects, reads the media session, and pushes Cyrillic now-playing payloads.
|
|
|
|
from PyInstaller.utils.hooks import collect_all
|
|
|
|
datas = [
|
|
('firmware/src/logo.h', 'firmware/src'), # tray icon parsed at runtime
|
|
('daemon/web', 'daemon/web'), # Phase 7 control-panel UI (server._web_dir)
|
|
]
|
|
binaries = []
|
|
hiddenimports = [
|
|
# Imported lazily inside tray_windows.main(), so name them explicitly.
|
|
'daemon.claude_usage_daemon_windows',
|
|
'daemon.autostart_windows',
|
|
'daemon.icon_assets',
|
|
# Phase 7 control panel (lazy string-imports inside tray_windows / server).
|
|
'daemon.config',
|
|
'daemon.server',
|
|
'daemon.panel',
|
|
'daemon.ha_client',
|
|
# v3 provider package (registry + providers are lazy-imported in the loop).
|
|
'daemon.providers',
|
|
'daemon.providers.base',
|
|
'daemon.providers.anthropic',
|
|
'daemon.providers.openai_codex',
|
|
# The exact winrt media modules read_now_playing() pulls in.
|
|
'winrt.windows.media',
|
|
'winrt.windows.media.control',
|
|
# pywebview's Windows backend + pythonnet bridge load these dynamically.
|
|
'webview.platforms.edgechromium',
|
|
'clr',
|
|
]
|
|
# Phase 7 adds the FastAPI control panel + the pywebview WebView2 window. uvicorn
|
|
# and pywebview both import their submodules (loop/protocol pickers; the
|
|
# edgechromium backend + bundled WebView2 DLLs) dynamically, which PyInstaller's
|
|
# static analysis misses — collect_all pulls submodules, binaries and data files.
|
|
# clr_loader/pythonnet ship the .NET runtime-config JSON pywebview's WinForms host
|
|
# needs. If a package is absent the build fails loudly (better than a silent
|
|
# blank window at runtime).
|
|
for _pkg in ('winrt', 'bleak', 'pystray', 'PIL',
|
|
'fastapi', 'uvicorn', 'webview', 'clr_loader', 'pythonnet'):
|
|
_d, _b, _h = collect_all(_pkg)
|
|
datas += _d
|
|
binaries += _b
|
|
hiddenimports += _h
|
|
|
|
|
|
a = Analysis(
|
|
['daemon\\tray_windows.py'],
|
|
pathex=['.'],
|
|
binaries=binaries,
|
|
datas=datas,
|
|
hiddenimports=hiddenimports,
|
|
hookspath=[],
|
|
hooksconfig={},
|
|
runtime_hooks=[],
|
|
excludes=['tkinter', 'pytest'],
|
|
noarchive=False,
|
|
optimize=0,
|
|
)
|
|
pyz = PYZ(a.pure)
|
|
|
|
exe = EXE(
|
|
pyz,
|
|
a.scripts,
|
|
a.binaries,
|
|
a.datas,
|
|
[],
|
|
name='Clawdmeter',
|
|
debug=False,
|
|
bootloader_ignore_signals=False,
|
|
strip=False,
|
|
upx=False, # UPX compression raises AV false-positive rates — leave it off
|
|
upx_exclude=[],
|
|
runtime_tmpdir=None,
|
|
console=False, # windowed tray app: no console window; logs to daemon.log
|
|
disable_windowed_traceback=False,
|
|
argv_emulation=False,
|
|
target_arch=None,
|
|
codesign_identity=None,
|
|
entitlements_file=None,
|
|
)
|