When multiple /notify calls arrive in quick succession, they now queue up and display one at a time rather than clobbering each other. Each notification plays for its full duration before the next is promoted. - /notify returns `queued: true` and `notify_queue_size` when buffered - Cleanup thread auto-advances the queue when the playing notification expires - /clear on the playing notification promotes the next immediately - /clear on a queued (not-yet-playing) notification removes it from the queue - /clear-all also drains the queue - Status response includes `notify_queue_size` for frontend awareness Bump to v2.3.3. Update OpenAPI spec, README, TODO. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7.5 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Important:
- When updating this file, always update
README.mdas well. The README is the main user-facing documentation for the project. - On every commit, bump the version number in
index.html(theVERSIONconstant) and updateREADME.mdwith any relevant changes.
Project Overview
Kao is a minimalist ambient display for an old Pixel phone. It uses ASCII "emotes" to represent system health instead of graphs or dashboards. External systems (Home Assistant, Uptime Kuma, scripts, etc.) push events and notifications to Kao via REST — Kao itself does not poll or monitor anything.
Design Philosophy
Kao is a display, not a monitor. It should not duplicate work that other tools already do. The right pattern is: your existing monitoring stack detects a problem, then POSTs to Kao to show it. This keeps Kao simple and lets each tool do what it's good at.
The bundled detectors/ scripts are legacy and exist for standalone use cases. New work should focus on making the REST API richer, not adding more detectors.
Architecture
REST push model:
┌─────────────────────┐ POST /event ┌─────────────┐ GET /status ┌─────────────┐
│ External systems │ ──────────────────▶ │ Aggregator │ ◀────────────────── │ Emote-UI │
│ (HA, scripts, etc) │ POST /notify │ (broker) │ │ (display) │
└─────────────────────┘ └─────────────┘ └─────────────┘
- Aggregator (
aggregator.py) — Flask service managing the event queue and priority logic - Emote-UI (
index.html) — OLED-optimized web frontend - Sentry (
kao.py) — Unified entry point managing all processes - Detectors (
detectors/*.py) — Legacy standalone sensors; prefer REST push from existing tools
Quick Start
python -m venv venv
source venv/bin/activate # or .\venv\Scripts\activate on Windows
pip install -r requirements.txt
python kao.py
UI available at http://localhost:5100
Configuration
Edit config.json to configure detectors:
{
"aggregator_url": "http://localhost:5100",
"aggregator": { "script": "aggregator.py" },
"detectors": [
{
"name": "cpu",
"enabled": true,
"script": "detectors/cpu.py",
"env": { "CHECK_INTERVAL": "30", "THRESHOLD_WARNING": "85", "THRESHOLD_CRITICAL": "95" }
}
]
}
Legacy Detectors
These bundled scripts exist for standalone use but are not the preferred approach. Prefer pushing from Home Assistant, Uptime Kuma, or any tool that already monitors what you care about.
| Detector | Script | Required Env Vars |
|---|---|---|
| Disk Space | detectors/disk_space.py |
— |
| CPU | detectors/cpu.py |
— |
| Memory | detectors/memory.py |
— |
| Service | detectors/service.py |
SERVICES (comma-separated process names) |
| Network | detectors/network.py |
HOSTS (comma-separated hostnames/IPs) |
| Docker | detectors/docker.py |
CONTAINERS (optional, monitors all if empty) |
All detectors support: AGGREGATOR_URL, CHECK_INTERVAL, THRESHOLD_WARNING, THRESHOLD_CRITICAL
API Endpoints
| Endpoint | Method | Description |
|---|---|---|
/event |
POST | Register event: {"id": "name", "priority": 1-4, "message": "optional", "ttl": seconds} |
/clear |
POST | Clear event: {"id": "name"} |
/clear-all |
POST | Clear all active events |
/notify |
POST | Queued notification — buffered if one is playing, auto-advances when it expires |
/sleep |
POST | Enter sleep mode |
/wake |
POST | Exit sleep mode |
/stream |
GET | SSE stream — pushes state JSON on every change (used by the frontend) |
/status |
GET | Current state JSON (one-shot query) |
/events |
GET | List active events |
/docs |
GET | Interactive API documentation (Swagger UI) |
/notify Endpoint
{
"message": "Someone at the door",
"duration": 10,
"emote": "( °o°)",
"color": "#FF9900",
"animation": "popping",
"sound": "chime"
}
Rapid calls are buffered — each notification plays for its full duration before the next is shown. /clear-all also drains the queue.
| Field | Required | Description |
|---|---|---|
message |
No | Text to display below emote |
duration |
No | Seconds before auto-expire (default: 5) |
emote |
No | Custom emote to display |
color |
No | Custom color (hex, e.g., #FF9900) |
animation |
No | One of: breathing, shaking, popping, celebrating, floating, bouncing, swaying |
sound |
No | One of: chime, alert, warning, critical, success, notify, doorbell, knock, ding, blip, siren, tada, ping, bubble, fanfare, alarm, klaxon, none |
Priority System
Lower number = higher priority. Events with a ttl auto-expire (heartbeat pattern).
| Priority | State | Emote | Color | Animation |
|---|---|---|---|---|
| 1 | Critical | ( x_x) |
Red | shaking |
| 2 | Warning | ( o_o) |
Yellow | breathing |
| 3 | Notify | ( 'o') |
Blue | popping |
| 4 | Optimal | varies | Green | varies |
Personality System
The optimal state face is set once per day on /wake (random pick). Each morning a fresh emote is chosen:
| Emote | Animation | Vibe |
|---|---|---|
( ^_^) |
breathing | calm |
(◕‿◕) |
bouncing | cheerful |
( ・ω・) |
swaying | curious |
( ˘▽˘) |
breathing | cozy |
Additional states:
- Idle expressions (15% chance):
( -_^),( ^_~),( ᵕ.ᵕ)with blink animation - Recovery celebration:
\(^o^)/with bounce for 5 seconds after issues resolve - Connection lost:
( ?.?)gray, searching animation - Sleep mode:
( -_-)zzZdim, very slow breathing
Home Assistant Integration
Add REST commands to configuration.yaml:
rest_command:
kao_notify:
url: "http://kao-host:5100/notify"
method: POST
content_type: "application/json"
payload: '{"message": "{{ message }}", "duration": {{ duration | default(5) }}}'
kao_sleep:
url: "http://kao-host:5100/sleep"
method: POST
kao_wake:
url: "http://kao-host:5100/wake"
method: POST
Use in automations:
# Doorbell notification
- service: rest_command.kao_notify
data:
message: "Someone at the door"
duration: 10
# Bedtime routine
- service: rest_command.kao_sleep
# Morning routine
- service: rest_command.kao_wake
File Structure
├── kao.py # Unified entry point
├── aggregator.py # Event broker/API server
├── index.html # OLED-optimized frontend
├── kao_tui.py # Developer TUI for testing sounds/events
├── config.json # Runtime configuration
├── openapi.yaml # API documentation (OpenAPI 3.0)
├── detectors/
│ ├── base.py
│ ├── disk_space.py
│ ├── cpu.py
│ ├── memory.py
│ ├── service.py
│ ├── network.py
│ └── docker.py
├── requirements.txt
├── TODO.md # Planned features and improvements
└── SPEC.md # Original project specification