Bump to v2.2.0: fix warning/bubble sounds, add klaxon, refresh docs

- index.html: fix playWarningSound (440→550 Hz, louder), fix playBubbleSound
  (audible volumes/durations), add looping klaxon sound (sawtooth wah-wah),
  stopKlaxon() on tap and state clear, bump VERSION to v2.2.0
- kao_tui.py: add klaxon to SOUNDS list, drop notify duration 5→2s for
  faster iteration; also include improved post() error reporting
- CLAUDE.md: add kao_tui.py to file structure, fix personality table
  (remove ˙▿˙ row not in aggregator), add klaxon to sound list
- README.md: add klaxon to sound list, update counts
- openapi.yaml: bump version to 2.2.0, add klaxon to sound enum

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-20 17:30:19 -06:00
parent a36fd7037a
commit 50e34b24c6
5 changed files with 53 additions and 31 deletions

View File

@@ -11,7 +11,7 @@ BASE_URL = sys.argv[1].rstrip("/") if len(sys.argv) > 1 else "http://localhost:5
SOUNDS = [
"chime", "alert", "warning", "critical", "success", "notify",
"doorbell", "knock", "ding", "blip", "siren", "tada",
"ping", "bubble", "fanfare", "alarm", "none",
"ping", "bubble", "fanfare", "alarm", "klaxon", "none",
]
FACES = [
@@ -43,12 +43,12 @@ CONTROLS = [
]
def post(path: str, data: dict | None = None) -> bool:
def post(path: str, data: dict | None = None) -> tuple[bool, str]:
try:
resp = requests.post(f"{BASE_URL}/{path}", json=data or {}, timeout=5)
return resp.ok
except Exception:
return False
return resp.ok, "" if resp.ok else f"HTTP {resp.status_code}: {resp.text[:80]}"
except Exception as e:
return False, str(e)
class KaoTUI(App):
@@ -95,37 +95,37 @@ class KaoTUI(App):
if item_id.startswith("sound-"):
name = item_id[len("sound-"):]
ok = post("notify", {"message": f"sound: {name}", "sound": name, "duration": 5})
self.notify(f"{name} {'sent' if ok else 'FAILED'}", severity="information" if ok else "error")
ok, err = post("notify", {"message": f"sound: {name}", "sound": name, "duration": 2})
self.notify(f"{name} sent" if ok else f"{name} FAILED: {err}", severity="information" if ok else "error")
elif item_id.startswith("face-"):
desc = item_id[len("face-"):]
face = next((f for f in FACES if f["desc"] == desc), None)
if face:
ok = post("notify", {
ok, err = post("notify", {
"emote": face["emote"],
"animation": face["animation"],
"color": face["color"],
"message": f"face: {face['desc']}",
"duration": 5,
"duration": 2,
})
self.notify(f"{face['emote']} {face['desc']} {'sent' if ok else 'FAILED'}", severity="information" if ok else "error")
self.notify(f"{face['emote']} {face['desc']} sent" if ok else f"{face['desc']} FAILED: {err}", severity="information" if ok else "error")
elif item_id.startswith("event-"):
suffix = item_id[len("event-"):]
if suffix == "clearall":
ok = post("clear-all")
self.notify(f"clear-all {'sent' if ok else 'FAILED'}", severity="information" if ok else "error")
ok, err = post("clear-all")
self.notify("clear-all sent" if ok else f"clear-all FAILED: {err}", severity="information" if ok else "error")
else:
ev = next((e for e in EVENTS if e["id"] == suffix), None)
if ev:
ok = post("event", {"id": ev["id"], "priority": ev["priority"], "message": ev["message"], "ttl": 10})
self.notify(f"{ev['label'].strip()} {'sent' if ok else 'FAILED'}", severity="information" if ok else "error")
ok, err = post("event", {"id": ev["id"], "priority": ev["priority"], "message": ev["message"], "ttl": 10})
self.notify(f"{ev['label'].strip()} sent" if ok else f"{ev['label'].strip()} FAILED: {err}", severity="information" if ok else "error")
elif item_id.startswith("ctrl-"):
action = item_id[len("ctrl-"):]
ok = post(action)
self.notify(f"{action} {'sent' if ok else 'FAILED'}", severity="information" if ok else "error")
ok, err = post(action)
self.notify(f"{action} sent" if ok else f"{action} FAILED: {err}", severity="information" if ok else "error")
if __name__ == "__main__":