Files
claude-statusline/claude_usage_widget/app.py
Axel Meyer 6a1b4bd022 Add desktop tray widget + installer wizard
- Desktop widget (Python/pystray): system tray icon showing 5h usage as
  circular progress bar with Claude starburst logo, 10-step green-to-red
  color scale, right-click menu with usage stats and configuration
- Shared cache: both widget and CLI statusline read/write the same
  /tmp/claude_usage.json — only one fetcher needs to run
- Installer wizard (install_wizard.py): interactive cross-platform setup
  with component selection, session key prompt, cron/autostart config
- OS wrappers: install.sh (Linux/macOS) and install.ps1 (Windows) find
  Python 3.9+ and launch the wizard
- README with topology diagram, usage docs, and configuration reference

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 12:54:32 +00:00

92 lines
2.5 KiB
Python

"""Main orchestrator: tray icon, state, threads."""
import logging
import sys
import threading
import tkinter as tk
from tkinter import simpledialog
import pystray
from . import config
from .fetcher import UsageFetcher
from .menu import build_menu
from .renderer import render_icon
log = logging.getLogger(__name__)
class App:
def __init__(self):
self.usage_data = {}
self._lock = threading.Lock()
self.fetcher = UsageFetcher(on_update=self._on_usage_update)
self._icon = pystray.Icon(
name="claude-usage",
icon=render_icon(0),
title="Claude Usage Widget",
menu=build_menu(self),
)
def run(self):
"""Start fetcher and run tray icon (blocks until quit)."""
self.fetcher.start()
self._icon.run()
def _on_usage_update(self, data):
"""Callback from fetcher thread — update icon + menu."""
with self._lock:
self.usage_data = data
pct = data.get("five_hour_pct", 0) if not data.get("error") else 0
try:
self._icon.icon = render_icon(pct)
# Force menu rebuild for dynamic text
self._icon.update_menu()
except Exception:
pass # icon not yet visible
# Update tooltip
if data.get("error"):
self._icon.title = f"Claude Usage: {data['error']}"
else:
self._icon.title = f"Claude Usage: {data.get('five_hour_pct', 0)}%"
def on_refresh(self):
self.fetcher.refresh()
def on_set_interval(self, seconds):
self.fetcher.set_interval(seconds)
def on_session_key(self):
"""Show a simple dialog to enter/update the session key."""
threading.Thread(target=self._session_key_dialog, daemon=True).start()
def _session_key_dialog(self):
root = tk.Tk()
root.withdraw()
current = config.get_session_key()
key = simpledialog.askstring(
"Claude Session Key",
"Paste your claude.ai sessionKey cookie value:",
initialvalue=current,
parent=root,
)
root.destroy()
if key is not None and key.strip():
config.set_session_key(key.strip())
self.fetcher.refresh()
def on_quit(self):
self.fetcher.stop()
self._icon.stop()
def main():
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)
app = App()
app.run()