Files
skills/skills/kvb-coach-meditation/scripts/meditation.py
T
Kevin Veen-BirkenbachandClaude Opus 5.5 5f30644ca6 feat(kvb-coach-meditation): short alpha-wave meditation with fractals, tuned by feedback
kvb-coach-meditation writes a local session page with its settings embedded (xdg-open drops a file:// query): generative pentatonic ambience with reverb over an isochronic or binaural alpha layer (8-13 Hz), a WebGL Julia fractal that zooms with the breathing cue (box, 4-7-8 or none), breath-synced glowing type in URW Gothic, +/- keys for the picture's brightness kept in localStorage, and a progress line along the bottom edge. meditation.py validates every setting and refuses a session while one is missing; after every session kvb-think-listen collects feedback that becomes one set per answer, logged in the kvb-coach profile. Adds the category coach: every coaching skill beyond kvb-coach is named kvb-coach-<name>. tests/test_meditation.py keeps script and page in step. Operator order 2026-11-03; two descriptions trimmed for the listing budget.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:14:45 +01:00

111 lines
3.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""Keep the kvb-coach-meditation settings and write the session page.
Args:
--state: path of the settings JSON file.
set <key>=<value> ...: change settings; every value is validated.
show: print the settings.
session --page <meditation.html> --out <file>: write the page with the
settings embedded and print its path; fails while a setting is missing.
Exit codes:
0 done, 2 usage error.
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
PLACEHOLDER = "const SETTINGS = null;"
RANGES = {
"minutes": (int, 1, 30),
"beat": (float, 8.0, 13.0),
"base": (float, 100.0, 400.0),
"volume": (float, 0.05, 1.0),
}
CHOICES = {
"mode": ("binaural", "isochronic"),
"palette": ("aurora", "ocean", "ember", "forest"),
"breathing": ("box", "478", "none"),
}
KEYS = (*RANGES, *CHOICES)
def fail(message: str) -> None:
print(f"meditation: {message}", file=sys.stderr)
sys.exit(2)
def parse(pair: str) -> tuple[str, int | float | str]:
key, sep, raw = pair.partition("=")
if not sep or key not in KEYS:
fail(f"{pair}: expected <key>=<value> with key in {', '.join(KEYS)}")
if key in CHOICES:
if raw not in CHOICES[key]:
fail(f"{key} must be one of {', '.join(CHOICES[key])}")
return key, raw
kind, low, high = RANGES[key]
try:
value = kind(raw)
except ValueError:
fail(f"{key} must be a number")
if not low <= value <= high:
fail(f"{key} must lie between {low} and {high}")
return key, value
def load(path: Path) -> dict:
try:
return json.loads(path.read_text(encoding="utf-8"))
except FileNotFoundError:
return {}
except ValueError:
fail(f"{path} is not valid JSON")
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument("--state", required=True)
sub = parser.add_subparsers(dest="cmd", required=True)
sub.add_parser("set").add_argument("pairs", nargs="+")
sub.add_parser("show")
p = sub.add_parser("session")
p.add_argument("--page", required=True)
p.add_argument("--out", required=True)
args = parser.parse_args()
path = Path(args.state)
settings = load(path)
if args.cmd == "set":
settings.update(parse(pair) for pair in args.pairs)
path.parent.mkdir(parents=True, exist_ok=True)
tmp = path.with_suffix(".tmp")
tmp.write_text(json.dumps(settings, indent=1, sort_keys=True), encoding="utf-8")
tmp.replace(path)
if args.cmd in ("set", "show"):
for key in KEYS:
print(f"{key}={settings.get(key, '-')}")
return 0
missing = [key for key in KEYS if key not in settings]
if missing:
fail(f"missing settings: {', '.join(missing)}")
page = Path(args.page)
if not page.is_file():
fail(f"{page} does not exist")
template = page.read_text(encoding="utf-8")
if template.count(PLACEHOLDER) != 1:
fail(f"{page} lacks the line {PLACEHOLDER}")
embedded = json.dumps({key: settings[key] for key in KEYS})
out = Path(args.out)
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(template.replace(PLACEHOLDER, f"const SETTINGS = {embedded};"), encoding="utf-8")
print(out.resolve())
return 0
if __name__ == "__main__":
sys.exit(main())