Files
clawdmeter/clawdmeter.spec
T
wenilandClaude Opus 4.8 ac0742d92c v3 M3: z.ai (GLM) provider — Anthropic-compatible poll from a pasted API key
ZaiProvider replaces the zai stub. It reaches z.ai's Anthropic-compatible
endpoint (default https://api.z.ai/api/anthropic) exactly like Claude Code with
ANTHROPIC_BASE_URL pointed at z.ai: a /v1/messages POST with a Bearer key. The
key + base URL come from the config the control panel's z.ai field writes
(providers.zai.base_url/.api_key) — no OAuth, no local files.

Two z.ai-specific cares: (1) static API key, no self-refresh; (2) the GLM Coding
Plan meters by prompts, so a 60s poll would drain quota — the provider
self-throttles to one network call per poll_interval_s (default 15 min) and
serves a cached status in between. Rate-limit parsing assumes z.ai proxies
Anthropic's unified headers (5h->s/sr, 7d->w/wr); if a real response lacks them
it still reports connected and logs the limit-ish headers it did return, so the
mapping can be finalized against a live key.

tools/probe_zai.py dumps a real z.ai response's headers (key via env, never
printed) to pin the exact header names. Graceful without a key: selecting z.ai
shows the blue theme + "no key", never crashes the loop. Registry: all three
providers now real; unknown ids fall back to StubProvider. +7 z.ai tests (mocked
transport: header mapping, auth, throttle, fallback); 120 passed, 2 Linux-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 07:27:34 +03:00

102 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',
'daemon.providers.zai',
# 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,
)