diff --git a/jarvis.py b/jarvis.py new file mode 100644 index 0000000..28316bf --- /dev/null +++ b/jarvis.py @@ -0,0 +1,212 @@ +import os +import re +import sqlite3 +import asyncio +import subprocess +import openai +from google import genai +from google.genai import types +from datetime import datetime +from pathlib import Path +from dotenv import load_dotenv + +# --- PFADE & SETUP --- +BASE_DIR = Path(__file__).resolve().parent +CONFIG_DIR = BASE_DIR / "config" +DATA_DIR = BASE_DIR / "data" +WORKSPACE_DIR = BASE_DIR / "workspace" + +ENV_FILE = CONFIG_DIR / ".env" +load_dotenv(ENV_FILE) + +DB_PATH = DATA_DIR / "cluster.db" +NOTES_FILE = WORKSPACE_DIR / "NOTIZEN.md" +TODO_FILE = WORKSPACE_DIR / "TODO.md" + +WEB_USER_NAME = os.getenv("WEB_USER_NAME", "Meik") + +# Ordner & Dateien anlegen +for d in [WORKSPACE_DIR, DATA_DIR, CONFIG_DIR]: + d.mkdir(parents=True, exist_ok=True) + +for f in [NOTES_FILE, TODO_FILE]: + if not f.exists(): + f.write_text(f"# {f.name}\nHier fängt dein Gedächtnis an, J.A.R.V.I.S.\n", encoding="utf-8") + +# --- KI KONFIGURATION --- +AI_PROVIDER = os.getenv("AI_PROVIDER", "google").lower() +OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "") +GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY", "") +NVIDIA_API_KEY = os.getenv("NVIDIA_API_KEY", "") +OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://127.0.0.1:11434/v1") + +GOOGLE_MODEL = os.getenv("GOOGLE_MODEL", "gemini-2.5-flash") +OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o") +OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "llama3") +NVIDIA_MODEL = os.getenv("NVIDIA_MODEL", "moonshotai/kimi-k2.5") + +# --- DATENBANK --- +def init_db(): + conn = sqlite3.connect(DB_PATH) + conn.execute(''' + CREATE TABLE IF NOT EXISTS nodes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT, ip TEXT UNIQUE, user TEXT, sudo_password TEXT, + os TEXT DEFAULT 'Unbekannt', arch TEXT DEFAULT 'Unbekannt', + docker_installed INTEGER DEFAULT 0, status TEXT + ) + ''') + conn.commit() + conn.close() + +init_db() + +def get_db(): + conn = sqlite3.connect(DB_PATH) + conn.row_factory = sqlite3.Row + return conn + +def get_system_prompt(): + prompt_path = CONFIG_DIR / "system_prompt.txt" + prompt = prompt_path.read_text(encoding="utf-8") if prompt_path.exists() else f"Hallo {WEB_USER_NAME}, ich bin J.A.R.V.I.S." + + prompt = prompt.replace("{user_name}", WEB_USER_NAME) + prompt = prompt.replace("{workspace_dir}", str(WORKSPACE_DIR)) + prompt = prompt.replace("{notes_file}", str(NOTES_FILE)) + prompt = prompt.replace("{todo_file}", str(TODO_FILE)) + + conn = get_db() + nodes = conn.execute('SELECT * FROM nodes').fetchall() + conn.close() + + node_info = "" + for n in nodes: + node_info += f"- Name: {n['name']}, IP: {n['ip']}, User: {n['user']}\n" + + return prompt.replace("{node_info}", node_info) + +# --- KI KOMMUNIKATION --- +async def get_ai_response(user_msg, system_prompt, history_list): + try: + if AI_PROVIDER in ["openai", "ollama", "nvidia"]: + messages = [{"role": "system", "content": system_prompt}] + history_list + + if AI_PROVIDER == "ollama": + url = OLLAMA_BASE_URL if OLLAMA_BASE_URL.endswith('/v1') else OLLAMA_BASE_URL.rstrip('/') + '/v1' + key, model_to_use = "ollama", OLLAMA_MODEL + elif AI_PROVIDER == "nvidia": + url, key, model_to_use = "https://integrate.api.nvidia.com/v1", NVIDIA_API_KEY, NVIDIA_MODEL + else: + url, key, model_to_use = None, OPENAI_API_KEY, OPENAI_MODEL + + client = openai.AsyncOpenAI(base_url=url, api_key=key) + response = await client.chat.completions.create(model=model_to_use, messages=messages) + return response.choices[0].message.content + + elif AI_PROVIDER == "google": + client = genai.Client(api_key=GOOGLE_API_KEY) + google_history = [ + types.Content(role="user" if msg["role"] == "user" else "model", parts=[types.Part.from_text(text=msg["content"])]) + for msg in history_list[:-1] + ] + chat = client.chats.create( + model=GOOGLE_MODEL, + config=types.GenerateContentConfig(system_instruction=system_prompt), + history=google_history + ) + return chat.send_message(user_msg).text + + except Exception as e: + return f"Fehler bei der KI-Anfrage: {e}" + +# --- BEFEHLSAUSFÜHRUNG (Lokal & Remote) --- +async def run_task(target, cmd): + print(f"\n⚙️ J.A.R.V.I.S. führt aus auf [{target}]: {cmd}") + + try: + if target.lower() == "localhost" or target == "127.0.0.1": + # Lokale Ausführung (für Notizen, Workspace etc.) + proc = await asyncio.create_subprocess_shell(cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT) + else: + # Remote Ausführung via SSH + conn = get_db() + n = conn.execute('SELECT * FROM nodes WHERE ip=? OR name=?', (target, target)).fetchone() + conn.close() + + if not n: + return f"⚠️ Node '{target}' nicht in der DB gefunden." + + ssh_cmd = f"ssh -o StrictHostKeyChecking=no -o LogLevel=ERROR {n['user']}@{n['ip']} '{cmd}'" + proc = await asyncio.create_subprocess_shell(ssh_cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT) + + stdout, _ = await proc.communicate() + output = stdout.decode('utf-8', errors='ignore').strip() + print(f"💻 Output:\n{output or '✅ Erfolgreich ausgeführt.'}\n") + return output + except Exception as e: + err = f"❌ Fehler: {e}" + print(err) + return err + +# --- MODULARE I/O SCHNITTSTELLEN (Für spätere Sprachsteuerung) --- +async def listen_to_user(): + # Später: Hier Mikrofon-Aufnahme und Vosk-Transkription einbauen + return await asyncio.to_thread(input, "\nDu: ") + +async def speak_to_user(text): + # Später: Hier Piper TTS einbauen, um den Text vorzulesen + print(f"\n🤖 J.A.R.V.I.S.:\n{text}") + +# --- HAUPT-LOOP --- +async def main_chat_loop(): + print("====================================================") + print("🤖 J.A.R.V.I.S. Terminal Interface geladen.") + print(f"Provider: {AI_PROVIDER.upper()}") + print("Tippe 'exit', um zu beenden.") + print("====================================================") + + chat_history = [] + + while True: + user_msg = await listen_to_user() + + if user_msg.lower().strip() in ['exit', 'quit']: + print("J.A.R.V.I.S. geht offline. Auf Wiedersehen!") + break + if not user_msg.strip(): + continue + + now = datetime.now().strftime("%d.%m.%Y %H:%M") + chat_history.append({"role": "user", "content": user_msg, "timestamp": now}) + + print("...", end="\r") # Kleiner Ladeindikator + + system_prompt = get_system_prompt() + ai_response = await get_ai_response(user_msg, system_prompt, chat_history) + + # XML-Befehle extrahieren und aus dem sichtbaren Text entfernen + commands = re.findall(r'(.*?)', ai_response, re.I | re.S) + clean_msg = re.sub(r'.*?', '', ai_response, flags=re.I | re.S).strip() + + if clean_msg: + await speak_to_user(clean_msg) + chat_history.append({"role": "assistant", "content": clean_msg, "timestamp": now}) + + if commands: + for target, cmd in commands: + output = await run_task(target.strip(), cmd.strip()) + sys_now = datetime.now().strftime("%d.%m.%Y %H:%M") + chat_history.append({ + "role": "user", + "content": f"[SYSTEM] Befehl '{cmd}' auf {target} abgeschlossen. Output:\n{output}", + "timestamp": sys_now + }) + + if len(chat_history) > 20: + chat_history = chat_history[-20:] + +if __name__ == "__main__": + try: + asyncio.run(main_chat_loop()) + except KeyboardInterrupt: + print("\nJ.A.R.V.I.S. hart beendet.") \ No newline at end of file