From c6ddbc3dc9f08608a28cabc7000e083196235b84 Mon Sep 17 00:00:00 2001 From: boksbc Date: Mon, 11 May 2026 21:42:59 +0200 Subject: [PATCH] Add Fox Desktop design spec Co-Authored-By: Claude Sonnet 4.6 --- .../specs/2026-05-11-fox-desktop-design.md | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-11-fox-desktop-design.md diff --git a/docs/superpowers/specs/2026-05-11-fox-desktop-design.md b/docs/superpowers/specs/2026-05-11-fox-desktop-design.md new file mode 100644 index 0000000..cddeff2 --- /dev/null +++ b/docs/superpowers/specs/2026-05-11-fox-desktop-design.md @@ -0,0 +1,182 @@ +# Fox Desktop — Design Spec +**Datum:** 2026-05-11 +**Status:** Genehmigt + +## Überblick + +Fox Desktop ist eine native Mac-App (Electron), die als dünne Hülle um die bestehende Laravel-HUD-App (`fox.pxo.at/hud`) fungiert. Electron fügt native Mac-Fähigkeiten hinzu, ohne das HUD neu zu schreiben. + +## Architektur + +``` +Mac OS + ├── Tray Icon (Menu Bar) + ├── BrowserWindow (Overlay, fullscreen, always-on-top) + │ └── lädt https://fox.pxo.at/hud + │ └── preload.js injiziert window.foxBridge + ├── Global Shortcut: Cmd+Shift+F + └── Python Subprocess: openwakeword ("hey_jarvis") + └── stdout "WAKE" → IPC → showHud() +``` + +Der Electron-Main-Process hat **keine** eigene WebSocket-Verbindung zu Reverb. Der WebSocket-Kanal gehört der HUD-Seite selbst. Electron ist reiner Plattform-Layer. + +## Prinzipien + +- **Pure Shell:** Electron kapselt, erweitert nicht die App-Logik +- **Bridge ist optional:** `window.foxBridge` wird nur in Electron injiziert; das HUD funktioniert im normalen Browser unverändert weiter +- **Kein Vite Build:** Es gibt kein eigenes Frontend — nur Electron-Module und Python-Script +- **Kein electron-builder** vorerst — erst nach stabilem Dev-Setup + +## Dateien + +| Datei | Aufgabe | +|---|---| +| `package.json` | Electron-App-Konfiguration, npm-Scripts | +| `electron/main.js` | App-Lifecycle, IPC-Handler, globaler Shortcut | +| `electron/hud.js` | BrowserWindow erstellen, show/hide mit Transition | +| `electron/tray.js` | Menu Bar Icon (Template Image) + Kontextmenü | +| `electron/wakeword.js` | Python-Subprocess starten, Auto-Restart bei Absturz | +| `electron/mac-agent.js` | AppleScript-Ausführung via osascript | +| `electron/preload.js` | `window.foxBridge` in HUD-Seite injizieren | +| `python/wakeword.py` | OpenWakeWord Loop, schreibt "WAKE" auf stdout | +| `assets/fox-tray.png` | 16×16 Menu Bar Icon (Template Image) | + +## Datenflüsse + +### HUD öffnen (Wakeword) +``` +"hey jarvis" gesprochen + → python/wakeword.py erkennt Wakeword + → stdout: "WAKE\n" + → electron/wakeword.js liest stdout + → IPC intern → showHud() + → BrowserWindow.show() + focus() + → webContents.send('hud-show') + → HUD CSS-Transition startet +``` + +### HUD öffnen (Tastaturkürzel / Tray) +``` +Cmd+Shift+F gedrückt ODER Tray-Icon geklickt + → main.js globalShortcut / tray.on('click') + → showHud() +``` + +### Mac-Befehl aus HUD +``` +HUD JavaScript: await window.foxBridge.macCommand('spotify_play') + → preload.js: ipcRenderer.invoke('mac-command', 'spotify_play') + → main.js IPC Handler + → electron/mac-agent.js: executeMacCommand('spotify_play') + → osascript: tell application "Spotify" to play + → Rückgabewert { success: true, output: '' } → HUD +``` + +### HUD schließen +``` +ESC im HUD ODER foxBridge.hideHud() ODER Tray-Menü "Verstecken" + → ipcRenderer.send('hide-hud') + → main.js → hideHud() + → webContents.send('hud-hide') ← CSS-Transition startet + → setTimeout 600ms → BrowserWindow.hide() +``` + +## BrowserWindow Konfiguration + +```javascript +{ + width: screenWidth, + height: screenHeight, + x: 0, y: 0, + frame: false, + transparent: true, + alwaysOnTop: true, + show: false, + skipTaskbar: true, + hasShadow: false, + webPreferences: { + preload: path.join(__dirname, 'preload.js'), + contextIsolation: true, + nodeIntegration: false, + webSecurity: false, // Remote URL + preload injection + allowRunningInsecureContent: true, + } +} +``` + +`setAlwaysOnTop(true, 'screen-saver')` und `setVisibleOnAllWorkspaces(true)` nach dem Erstellen. + +## window.foxBridge API + +```javascript +window.foxBridge = { + sendMessage(text), // Text an Fox Laravel via IPC + hideHud(), // HUD verstecken + macCommand(cmd), // AppleScript-Befehl ausführen (async, gibt Result zurück) + onFoxAction(callback), // Fox-Aktionen empfangen + onHudShow(callback), // HUD-Show-Event + onHudHide(callback), // HUD-Hide-Event + onStartListening(callback), // Mikrofon-Start-Signal + onPlayAudio(callback), // Audio-URL zum Abspielen +} +``` + +## Mac-Agent Befehle + +Vordefinierte Shortcuts (z.B. `spotify_play`, `mail_open`, `volume_up`) sowie generische Formen: +- `script:` — direktes AppleScript +- `open:` — URL im Standardbrowser öffnen +- `app:` — Mac-App öffnen +- `save:` — Datei auf Desktop schreiben + +## Wakeword (OpenWakeWord) + +- Python 3, `openwakeword`, `pyaudio`, `numpy`, `tflite-runtime` +- Eingebautes Modell: `hey_jarvis` (kein API-Key, kein Training nötig) +- Electron spawnt `python3 python/wakeword.py` als Child-Process +- Auto-Restart nach Absturz (2s Delay) +- Später: eigenes "hey fox" Modell trainierbar + +## Laravel HUD — Anpassungsstellen + +Diese 3 optionalen Punkte müssen in `fox-hud.blade.php` / HUD-JavaScript hinzugefügt werden: + +1. **ESC schließt HUD:** + ```javascript + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') window.foxBridge?.hideHud(); + }); + ``` + +2. **Mac-Aktionen in `onAction()`:** + ```javascript + if (payload.action === 'spotify_play') { + await window.foxBridge?.macCommand('spotify_play'); + return; + } + // ... weitere Mac-Aktionen + ``` + +3. **Nachricht senden (optional, wenn direkt ohne Livewire):** + ```javascript + window.foxBridge?.sendMessage(text); + ``` + +## Deployment + +- Dev: `npm start` (Electron lädt fox.pxo.at/hud direkt) +- LaunchAgent `~/Library/LaunchAgents/at.pxo.fox.plist` für Auto-Start beim Login +- `electron-builder` für DMG-Build — später + +## Abhängigkeiten + +**npm:** +- `electron` ^28 +- `axios` ^1.6 (für sendMessage POST) + +**Python:** +- `openwakeword` +- `pyaudio` +- `numpy` +- `tflite-runtime`