From d1bf20c86dfb0f5511e2468ec1689b088c693f3e Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 18 Jun 2026 20:18:39 +0000 Subject: [PATCH] feat(stt): auto-start via `stt --install-service` (phase 6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ajoute `stt --install-service` / `--uninstall-service` : génère et active un service systemd --user (lié à graphical-session.target) qui lance le HUD + voix à l'ouverture de session. Le kiosk existant (ui.kiosk) fournit l'« écran Jarvis ». - cli : _service_unit (génération du unit, fonction pure testable), _cmd_install_service (écrit le unit + import-environment DISPLAY/WAYLAND + daemon-reload/enable/start), _cmd_uninstall_service. - Docs : admin/ia/stt.md (phase 6 → ✅, section Auto-start + caveat DISPLAY), stt/README. - Bump client 0.2.1 → 0.3.0. Phase 6 de la roadmap STT. Reste : test sur poste, outils Hermes (7). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_017Qjq5jHiqNepnobJpHYpCa --- admin/ia/stt.md | 42 +++++++++++++---- stt/README.md | 1 + stt/client/pyproject.toml | 2 +- stt/client/stt/cli.py | 95 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 131 insertions(+), 9 deletions(-) diff --git a/admin/ia/stt.md b/admin/ia/stt.md index 2e839ba..cf1d506 100644 --- a/admin/ia/stt.md +++ b/admin/ia/stt.md @@ -12,10 +12,11 @@ Objectif ultérieur : « agir sur le homelab » via les outils de Hermes (phase > ✅ **2026-06-17** — déployé in-cluster (ArgoCD), mémoire court-terme (5a) + long-terme (5b) > **validées sur cible**, embeddings dédiés nomic `:1238` (5d) + fix thinking Qwen3 `/no_think` > (5e). -> ✅ **2026-06-18** — HUD avancé (4) implémenté (design **Claude Design**) : avatar portrait -> réactif (anneau/ping/spinner), transcript à bulles, drawer Réglages **câblé au backend** -> (reset / mode cerveau / mot de réveil à chaud). Reste : test audio bout-en-bout sur le poste -> (phase 1), portraits réels + test HUD sur cible, auto-start (6), outils Hermes (7). +> ✅ **2026-06-18** — HUD avancé (4) + auto-start (6). HUD : design **Claude Design** (avatar +> portrait réactif, transcript à bulles, drawer Réglages **câblé au backend** : reset / mode +> cerveau / mot de réveil à chaud). Auto-start : `stt --install-service` (systemd --user, +> `graphical-session.target`). Reste : test audio bout-en-bout (phase 1), portraits réels + +> test sur cible, outils Hermes (7). --- @@ -29,7 +30,7 @@ Objectif ultérieur : « agir sur le homelab » via les outils de Hermes (phase | Modèle local **ou** Claude | LiteLLM `:4000` + `hermes-default` + `hermes-switch qwen\|claude` | ✅ opérationnel | | Agir sur le homelab | Hermes Agent (`:8080`, profils funk-ai/system/monitor/brain) | ✅ opérationnel | | Voix (STT + TTS + wake word) | `tools/hermes-voice/` — faster-whisper + Piper + webrtcvad | ✅ existe (CLI only) | -| Démarrage auto au boot | pattern `systemd --user` (`tools/hermes-voice/install-service.sh`) | ✅ éprouvé | +| Démarrage auto au boot | `stt --install-service` (systemd --user) — repris du pattern `tools/hermes-voice/` | ✅ | | Mémoire sémantique | Qdrant `:6333` + RAG (`rag-query`/`rag-ingest`) sur s01 | ✅ Qdrant réparé 17/06 (RAG `funk-docs` à re-ingérer) | Maillon réellement manquant : **l'interface graphique HUD** + le packaging + la mémoire perso. @@ -157,6 +158,30 @@ Côté client, pilotable depuis `stt/client/config/` + l'écran de réglages du --- +## Auto-start (service systemd --user) + +Lancer le HUD + voix automatiquement à l'ouverture de session (écran « Jarvis ») : + +```bash +stt --install-service # écrit ~/.config/systemd/user/stt.service, enable + start +stt --uninstall-service # retire le service +``` + +Le unit est lié à `graphical-session.target` (démarre avec la session graphique, s'arrête avec). +Suivi : `systemctl --user status|restart stt`, `journalctl --user -u stt -f`. + +**Caveat DISPLAY** : un service `systemd --user` n'hérite de `DISPLAY`/`WAYLAND_DISPLAY` que si la +session graphique les a importés dans le gestionnaire user. `--install-service` exécute un +`systemctl --user import-environment DISPLAY WAYLAND_DISPLAY XAUTHORITY` (best-effort). Si le kiosk +ne s'ouvre pas : vérifier que le bureau peuple `graphical-session.target` (GNOME/KDE le font), ou +lancer `stt` via les « applications au démarrage » du bureau. Le kiosk est piloté par `[ui].kiosk` +(défaut `true`). + +**Boot sans login** (borne dédiée) : `sudo loginctl enable-linger "$USER"` + auto-login du bureau — +la session graphique démarre au boot et tire le service. + +--- + ## Roadmap | Phase | Objectif | État | @@ -171,7 +196,7 @@ Côté client, pilotable depuis `stt/client/config/` + l'écran de réglages du | **5c — Réparation Qdrant** | drop `funk-docs` corrompu + restart (s01) | ✅ (17/06) | | **5d — Embeddings dédiés** | `nomic-embed-text` (dim 768) sur gpu-01 `:1238` → recherche plus précise | ✅ (rôle llama_server + migration auto collection) | | **5e — Fix thinking Qwen3** | `/no_think` (content vide / timeout 502) + fallback reasoning_content | ✅ (17/06) | -| **6 — Auto-start client** | `install-service.sh` (systemd --user) + kiosk | ⏳ | +| **6 — Auto-start client** | `stt --install-service` (systemd --user, `graphical-session.target`) + kiosk | ✅ (à tester sur poste) | | **7 — Outils Hermes** | « agir sur Funk » via gateway `:8080` (API à spécifier) | ⏳ | ### État (validé sur cible) @@ -179,8 +204,9 @@ Côté client, pilotable depuis `stt/client/config/` + l'écran de réglages du Serveur déployé in-cluster via ArgoCD (image `sha-` gérée par CI). Mémoire **5a + 5b validées en prod** : court-terme (historique de session) et long-terme (rappel cross-session « Felix », `points_count` confirmé via `/v1/memory/health`). HUD avancé (4) **implémenté** -(design Claude Design, câblé au websocket backend). Reste côté poste : test audio bout-en-bout -(micro → STT → serveur → TTS) et test du HUD avec portraits réels. +(design Claude Design, câblé au websocket backend) ; auto-start (6) via `stt --install-service`. +Reste côté poste : test audio bout-en-bout (micro → STT → serveur → TTS) et test du HUD/kiosk +avec portraits réels. --- diff --git a/stt/README.md b/stt/README.md index 5369915..5de1e58 100644 --- a/stt/README.md +++ b/stt/README.md @@ -30,6 +30,7 @@ stt --setup # génère ~/.config/stt/stt.toml (dont [server] url) stt --text # chat texte simple (sans micro ni HUD) — idéal pour tester stt # voix + HUD ; dis "Ok Hermès …" stt --model claude # choisir le modèle : hermes (défaut) | claude | qwen | opus +stt --install-service # auto-start au login (systemd --user, kiosk « écran Jarvis ») ``` En mode texte : commandes `/model `, `/models`, `/quit`. Prérequis voix : micro, Piper + voix dans `~/.local/share/piper/`, `aplay` (alsa-utils). diff --git a/stt/client/pyproject.toml b/stt/client/pyproject.toml index f9be9fb..1622990 100644 --- a/stt/client/pyproject.toml +++ b/stt/client/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "stt" -version = "0.2.1" +version = "0.3.0" description = "STT — client vocal Jarvis du homelab Funk (voix + HUD, parle au STT-server)" requires-python = ">=3.11" readme = "README.md" diff --git a/stt/client/stt/cli.py b/stt/client/stt/cli.py index 8a590ed..8e3f945 100644 --- a/stt/client/stt/cli.py +++ b/stt/client/stt/cli.py @@ -4,6 +4,7 @@ from __future__ import annotations import argparse import asyncio +import os import sys @@ -22,6 +23,92 @@ def _cmd_setup() -> int: return 0 +SERVICE_NAME = "stt.service" + + +def _service_unit(stt_bin: str) -> str: + """Contenu du unit systemd --user : lance `stt` (HUD + voix) avec la session graphique.""" + return f"""[Unit] +Description=STT — Assistant vocal Jarvis (HUD + voix) +After=graphical-session.target +PartOf=graphical-session.target + +[Service] +Type=simple +ExecStart={stt_bin} +Restart=on-failure +RestartSec=5 +Environment=PYTHONUNBUFFERED=1 +StandardOutput=journal +StandardError=journal +SyslogIdentifier=stt + +[Install] +WantedBy=graphical-session.target +""" + + +def _cmd_install_service() -> int: + """Installe et démarre le service systemd --user (auto-start au login + kiosk).""" + import shutil + import subprocess + from pathlib import Path + + stt_bin = shutil.which("stt") or os.path.realpath(sys.argv[0]) + unit_dir = Path.home() / ".config" / "systemd" / "user" + unit_dir.mkdir(parents=True, exist_ok=True) + unit_path = unit_dir / SERVICE_NAME + unit_path.write_text(_service_unit(stt_bin)) + print(f"✓ Unit écrit : {unit_path}") + print(f" ExecStart = {stt_bin}") + + def sc(*args): + subprocess.run(["systemctl", "--user", *args], check=False) + + # Rendre DISPLAY/WAYLAND dispo au gestionnaire systemd --user (pour le kiosk) + subprocess.run( + ["systemctl", "--user", "import-environment", + "DISPLAY", "WAYLAND_DISPLAY", "XAUTHORITY"], + check=False, + ) + sc("daemon-reload") + sc("enable", SERVICE_NAME) + sc("start", SERVICE_NAME) + + print("\n✓ Service activé et démarré (systemd --user).") + print("\nCommandes utiles :") + print(" systemctl --user status stt") + print(" journalctl --user -u stt -f") + print(" systemctl --user restart stt") + print(" stt --uninstall-service") + print("\nDémarrer dès le boot (kiosk dédié, sans login manuel) :") + print(' sudo loginctl enable-linger "$USER" # + auto-login du bureau') + print("\nLe HUD s'ouvre en kiosk si [ui].kiosk = true (défaut).") + print("Si l'écran ne s'ouvre pas : voir le caveat DISPLAY dans admin/ia/stt.md.") + return 0 + + +def _cmd_uninstall_service() -> int: + """Arrête, désactive et supprime le service systemd --user.""" + import subprocess + from pathlib import Path + + def sc(*args): + subprocess.run(["systemctl", "--user", *args], check=False) + + sc("stop", SERVICE_NAME) + sc("disable", SERVICE_NAME) + unit_path = Path.home() / ".config" / "systemd" / "user" / SERVICE_NAME + if unit_path.exists(): + unit_path.unlink() + print(f"✓ Unit supprimé : {unit_path}") + else: + print("(unit déjà absent)") + sc("daemon-reload") + print("✓ Service STT désinstallé.") + return 0 + + def _make_client(args): from stt.api import ServerClient from stt.config import load_config, resolve_model @@ -116,10 +203,18 @@ def main(argv: list[str] | None = None) -> int: p.add_argument("--model", help="Modèle : hermes, claude, qwen, opus (ou alias LiteLLM)") p.add_argument("--server", help="Force l'URL du STT-server (sinon config)") p.add_argument("--no-tts", action="store_true", help="Réponses texte seulement (voix)") + p.add_argument("--install-service", action="store_true", + help="Installe le service systemd --user (auto-start au login + kiosk)") + p.add_argument("--uninstall-service", action="store_true", + help="Désinstalle le service systemd --user") args = p.parse_args(argv) if args.setup: return _cmd_setup() + if args.install_service: + return _cmd_install_service() + if args.uninstall_service: + return _cmd_uninstall_service() if args.text: return _cmd_text(args) return _cmd_voice(args)