๐Ÿ— KeyzHub
19Keys ยท community archive
7888 bytes raw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
"""
NEO VOICE ASSISTANT โ€” Jarvis-style voice interface to the REAL Neo agent
Wake word: "Hey Neo" โ†’ Neo responds "Peace God" โ†’ continuous conversation
Routes through OpenClaw agent (same brain as Telegram) with full MCP tools,
MemPalace, memory-lancedb, Shopify โ€” everything.

Usage:
  python3 neo-voice-assistant.py
"""

import os
import sys
import json
import time
import tempfile
import subprocess
import re
import speech_recognition as sr
import httpx

# โ”€โ”€ Config โ”€โ”€
def _env(name, default=None):
    """Env first, then the gitignored .env next to this script. Never hardcode; fail loud."""
    val = os.environ.get(name)
    if val:
        return val
    env_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env")
    if os.path.exists(env_path):
        with open(env_path) as f:
            for line in f:
                line = line.strip()
                if line.startswith(name + "="):
                    return line.split("=", 1)[1].strip()
    return default


ELEVENLABS_KEY = _env("ELEVENLABS_API_KEY")
VOICE_ID = _env("ELEVENLABS_VOICE_ID", "YjyOkKA67HmAqGFoFWMd")  # 19Keys cloned voice
TELEGRAM_TOKEN = _env("NEO_TELEGRAM_BOT_TOKEN")
TELEGRAM_CHAT = _env("NEO_TELEGRAM_CHAT_ID")

if not (ELEVENLABS_KEY and TELEGRAM_TOKEN and TELEGRAM_CHAT):
    sys.exit(
        "ERROR: missing ELEVENLABS_API_KEY / NEO_TELEGRAM_BOT_TOKEN / NEO_TELEGRAM_CHAT_ID "
        "(set in env or the .env at repo root โ€” see .env.example)."
    )

WAKE_WORDS = ["hey neo", "hey neil", "a neo", "hey new", "henio", "hey nio"]
EXIT_WORDS = ["peace god", "peace gods", "goodbye", "shut down", "go to sleep"]


def speak(text):
    """Speak using 19Keys' cloned voice via ElevenLabs."""
    if not text or len(text.strip()) < 2:
        return

    clean = text.replace("**", "").replace("*", "").replace("#", "").replace("`", "")
    clean = re.sub(r'\[.*?\]\(.*?\)', '', clean)  # Remove markdown links
    clean = clean.replace("\n- ", ". ").replace("\n", ". ").strip()
    clean = clean[:800]  # Cap for TTS

    print(f"\n  ๐Ÿ”Š NEO: {text}\n")

    try:
        resp = httpx.post(
            f"https://api.elevenlabs.io/v1/text-to-speech/{VOICE_ID}/stream",
            headers={"xi-api-key": ELEVENLABS_KEY, "Content-Type": "application/json"},
            json={
                "text": clean,
                "model_id": "eleven_turbo_v2_5",
                "voice_settings": {"stability": 0.5, "similarity_boost": 0.85, "style": 0.3},
            },
            timeout=30,
        )
        if resp.status_code == 200:
            with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f:
                f.write(resp.content)
                temp_path = f.name
            subprocess.run(["afplay", temp_path], check=True)
            os.unlink(temp_path)
        else:
            subprocess.run(["say", "-v", "Alex", clean[:200]])
    except Exception as e:
        print(f"  [TTS Error: {e}]")
        subprocess.run(["say", "-v", "Alex", clean[:200]])


def ask_neo_via_openclaw(message):
    """Send message through the REAL Neo OpenClaw agent and get response."""

    # Send to Neo via Telegram (the actual Neo agent picks it up)
    try:
        send_resp = httpx.post(
            f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/sendMessage",
            json={"chat_id": TELEGRAM_CHAT, "text": f"[VOICE] {message}"},
            timeout=10,
        )
        sent_msg_id = send_resp.json().get("result", {}).get("message_id", 0)
    except Exception as e:
        return f"Couldn't reach Neo. {e}"

    # Poll for Neo's response (he replies in the same chat)
    print("  โณ [Neo is thinking...]")
    start = time.time()
    last_update_id = 0

    for attempt in range(20):  # Up to 40 seconds
        time.sleep(2)

        try:
            resp = httpx.get(
                f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/getUpdates",
                params={"offset": -3, "limit": 3, "timeout": 1},
                timeout=10,
            )
            updates = resp.json().get("result", [])

            for update in reversed(updates):
                msg = update.get("message", {})
                # Look for bot replies after our sent message
                msg_id = msg.get("message_id", 0)
                from_bot = msg.get("from", {}).get("is_bot", False)
                text = msg.get("text", "")

                if from_bot and msg_id > sent_msg_id and text:
                    elapsed = time.time() - start
                    print(f"  โœ“ [Neo responded in {elapsed:.1f}s]")
                    return text

        except Exception:
            pass

        if time.time() - start > 40:
            break

    return "I'm still processing that. Check Telegram for my full response."


def listen(recognizer, mic, timeout=None, phrase_limit=10):
    """Listen for speech and return transcription."""
    try:
        with mic as source:
            audio = recognizer.listen(source, timeout=timeout, phrase_time_limit=phrase_limit)
        text = recognizer.recognize_google(audio).lower()
        return text
    except sr.WaitTimeoutError:
        return None
    except sr.UnknownValueError:
        return None
    except Exception as e:
        print(f"  [Listen error: {e}]")
        return None


def main():
    recognizer = sr.Recognizer()
    recognizer.energy_threshold = 300
    recognizer.dynamic_energy_threshold = True

    # Find MacBook Pro microphone
    mic_index = None
    for i, name in enumerate(sr.Microphone.list_microphone_names()):
        if "MacBook" in name or "Built-in" in name:
            mic_index = i
            break
    mic = sr.Microphone(device_index=mic_index) if mic_index is not None else sr.Microphone()

    print(f"\n  [Using mic: {sr.Microphone.list_microphone_names()[mic_index or 0]}]")
    print("\n  [Calibrating...]")
    with mic as source:
        recognizer.adjust_for_ambient_noise(source, duration=2)

    print("\n" + "=" * 50)
    print("  โšก NEO VOICE ASSISTANT โšก")
    print("  Powered by OpenClaw Agent (full Neo brain)")
    print("  Say 'Hey Neo' to activate")
    print("  Continuous conversation until 'Peace God'")
    print("=" * 50 + "\n")

    while True:
        # โ”€โ”€ PHASE 1: Wait for wake word โ”€โ”€
        print("  ๐Ÿ‘‚ [Waiting for 'Hey Neo'...]")
        text = listen(recognizer, mic, timeout=None, phrase_limit=4)

        if text is None:
            continue

        if not any(wake in text for wake in WAKE_WORDS):
            continue

        # โ”€โ”€ PHASE 2: Activated โ”€โ”€
        print("\n  โšก [ACTIVATED]")
        speak("Peace god. What's on your mind?")

        # โ”€โ”€ PHASE 3: Continuous conversation through real Neo โ”€โ”€
        silence_count = 0
        while True:
            print("  ๐ŸŽค [Listening...]")
            text = listen(recognizer, mic, timeout=8, phrase_limit=20)

            if text is None:
                silence_count += 1
                if silence_count >= 3:
                    speak("I'm here when you need me.")
                    break
                continue

            silence_count = 0
            print(f"\n  ๐Ÿ—ฃ  YOU: {text}")

            # Check for exit
            if any(exit_word in text for exit_word in EXIT_WORDS):
                speak("Peace god. The kingdom stays running.")
                break

            # Check for re-engage
            if any(wake in text for wake in WAKE_WORDS):
                speak("I'm right here. What you need?")
                continue

            # โ”€โ”€ Route through REAL Neo agent via Telegram โ”€โ”€
            response = ask_neo_via_openclaw(text)
            speak(response)

        print("\n  ๐Ÿ’ค [Session ended โ€” back to wake word mode]\n")


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\n\n  Neo Voice Assistant offline. Peace god.\n")