Initial commit: Natiris AI Agent Orchestration System

This commit is contained in:
Arch Agent
2026-03-01 14:28:26 +01:00
commit 3b5f6ba83d
3127 changed files with 86184 additions and 0 deletions

251
tui/natiris_tui.py Executable file
View File

@@ -0,0 +1,251 @@
#!/usr/bin/env python3
"""
Natiris TUI Text User Interface für den Companion
Features:
- Realtime State Anzeige
- Chat Interface (simuliert)
- Admin-Befehle
- Quick Actions
"""
import curses
import json
import os
import sys
import time
from datetime import datetime, timezone
PATHS = {
"state": os.path.expanduser("~/natiris/core/natiris_full_state.json"),
"config": os.path.expanduser("~/natiris/config/character_genesis.json"),
"ollama": os.path.expanduser("~/natiris/bridges/ollama_response.json"),
"log": os.path.expanduser("~/natiris/admin/admin_log.json"),
}
class NatirisTUI:
def __init__(self, stdscr):
self.stdscr = stdscr
self.running = True
self.mode = "STATE" # STATE, CHAT, ADMIN
self.user_input = ""
self.log_lines = []
self.last_update = 0
self.state = {}
self.response = ""
# Init
curses.curs_set(0)
curses.start_color()
curses.use_default_colors()
curses.init_pair(1, curses.COLOR_CYAN, -1)
curses.init_pair(2, curses.COLOR_GREEN, -1)
curses.init_pair(3, curses.COLOR_YELLOW, -1)
curses.init_pair(4, curses.COLOR_RED, -1)
curses.init_pair(5, curses.COLOR_WHITE, -1)
self.stdscr.nodelay(True)
# Initial load
self.update_state()
def update_state(self):
try:
with open(PATHS["state"]) as f:
self.state = json.load(f)
except Exception:
self.state = {}
try:
with open(PATHS["ollama"]) as f:
resp = json.load(f)
self.response = resp.get("response", "")[:120]
except Exception:
self.response = ""
self.last_update = time.time()
def draw_header(self):
h, w = self.stdscr.getmaxyx()
self.stdscr.attron(curses.color_pair(1) | curses.A_BOLD)
self.stdscr.addstr(0, 2, " NATIRIS COMPANION v4.x ")
self.stdscr.attroff(curses.color_pair(1) | curses.A_BOLD)
self.stdscr.addstr(1, 2, f" Uptime: {int(time.time() - os.path.getmtime(PATHS['state']) if os.path.exists(PATHS['state']) else time.time())}s ")
self.stdscr.addstr(2, 2, f" Mode: {self.mode} | {datetime.now(timezone.utc).strftime('%H:%M:%S')} ")
def draw_state_panel(self, top, left, height, width):
box_top = top
box_left = left
box_height = height
box_width = width
# Box frame
self.stdscr.attron(curses.color_pair(5))
for y in range(box_top, box_top + box_height):
self.stdscr.addstr(y, box_left, "")
self.stdscr.addstr(y, box_left + box_width - 1, "")
self.stdscr.addstr(box_top, box_left, "")
self.stdscr.addstr(box_top, box_left + box_width - 1, "")
self.stdscr.addstr(box_top + box_height - 1, box_left, "")
self.stdscr.addstr(box_top + box_height - 1, box_left + box_width - 1, "")
self.stdscr.addstr(box_top, box_left + 1, "" * (box_width - 2))
self.stdscr.addstr(box_top + box_height - 1, box_left + 1, "" * (box_width - 2))
self.stdscr.attroff(curses.color_pair(5))
# Title
self.stdscr.attron(curses.color_pair(2) | curses.A_BOLD)
self.stdscr.addstr(box_top, box_left + 2, " CORE STATE ")
self.stdscr.attroff(curses.color_pair(2) | curses.A_BOLD)
state = self.state.get("modules", {})
core = self.state.get("core_state", {})
lines = [
f" Mood: {core.get('mood', '?')}/10",
f" Loneliness: {core.get('loneliness', '?')}/10",
f" Anxiety: {core.get('anxiety', '?')}/10",
f" Bonded: {core.get('bonded_to') or 'none'}",
f" Exclusivity: {state.get('Bond', {}).get('exclusivity_active', False)}",
f" Tone: {state.get('Expression', {}).get('tone', 'neutral')}",
f" Response: {self.response}",
]
for i, line in enumerate(lines):
self.stdscr.addstr(box_top + 2 + i, box_left + 2, line[:box_width - 4])
def draw_chat_panel(self, top, left, height, width):
box_top = top
box_left = left
box_height = height
box_width = width
self.stdscr.attron(curses.color_pair(5))
for y in range(box_top, box_top + box_height):
self.stdscr.addstr(y, box_left, "")
self.stdscr.addstr(y, box_left + box_width - 1, "")
self.stdscr.addstr(box_top, box_left, "")
self.stdscr.addstr(box_top, box_left + box_width - 1, "")
self.stdscr.addstr(box_top + box_height - 1, box_left, "")
self.stdscr.addstr(box_top + box_height - 1, box_left + box_width - 1, "")
self.stdscr.addstr(box_top, box_left + 1, "" * (box_width - 2))
self.stdscr.addstr(box_top + box_height - 1, box_left + 1, "" * (box_width - 2))
self.stdscr.attroff(curses.color_pair(5))
self.stdscr.attron(curses.color_pair(3) | curses.A_BOLD)
self.stdscr.addstr(box_top, box_left + 2, " CHAT OUTPUT ")
self.stdscr.attroff(curses.color_pair(3) | curses.A_BOLD)
if self.mode == "CHAT":
self.stdscr.addstr(box_top + box_height - 1, box_left + 2, f"> {self.user_input}_")
else:
self.stdscr.addstr(box_top + box_height - 1, box_left + 2, " [Press 'c' for chat, 'a' for admin] ")
def draw_admin_panel(self, top, left, height, width):
box_top = top
box_left = left
box_height = height
box_width = width
self.stdscr.attron(curses.color_pair(5))
for y in range(box_top, box_top + box_height):
self.stdscr.addstr(y, box_left, "")
self.stdscr.addstr(y, box_left + box_width - 1, "")
self.stdscr.addstr(box_top, box_left, "")
self.stdscr.addstr(box_top, box_left + box_width - 1, "")
self.stdscr.addstr(box_top + box_height - 1, box_left, "")
self.stdscr.addstr(box_top + box_height - 1, box_left + box_width - 1, "")
self.stdscr.addstr(box_top, box_left + 1, "" * (box_width - 2))
self.stdscr.addstr(box_top + box_height - 1, box_left + 1, "" * (box_width - 2))
self.stdscr.attroff(curses.color_pair(5))
self.stdscr.attron(curses.color_pair(4) | curses.A_BOLD)
self.stdscr.addstr(box_top, box_left + 2, " ADMIN ")
self.stdscr.attroff(curses.color_pair(4) | curses.A_BOLD)
help_lines = [
" Commands:",
" s = status",
" e = emotion reset",
" m <val> = mood set",
" t <val> = trust set",
" r = bond reset",
" d = debug",
]
for i, line in enumerate(help_lines):
self.stdscr.addstr(box_top + 2 + i, box_left + 2, line[:box_width - 4])
def handle_input(self):
try:
key = self.stdscr.getch()
if key == -1:
return
if key == ord('q'):
self.running = False
elif key == ord('c'):
self.mode = "CHAT"
elif key == ord('a'):
self.mode = "ADMIN"
elif key == ord('s') and self.mode == "ADMIN":
os.system("cd ~/natiris/admin && python3 AdminInterface.py status")
self.update_state()
elif key == ord('e') and self.mode == "ADMIN":
os.system("cd ~/natiris/admin && python3 AdminInterface.py emotion reset")
self.update_state()
elif key == ord('r') and self.mode == "ADMIN":
os.system("cd ~/natiris/admin && python3 AdminInterface.py bond reset")
self.update_state()
elif key == ord('t') and self.mode == "ADMIN":
os.system("cd ~/natiris/admin && python3 AdminInterface.py trust set 7")
self.update_state()
elif key == ord('m') and self.mode == "ADMIN":
os.system("cd ~/natiris/admin && python3 AdminInterface.py mood set 7.5")
self.update_state()
elif key == curses.KEY_BACKSPACE or key == 127 or key == 8:
if self.mode == "CHAT" and self.user_input:
self.user_input = self.user_input[:-1]
elif key >= 32 and key < 127:
if self.mode == "CHAT":
self.user_input += chr(key)
except Exception:
pass
def run(self):
while self.running:
try:
# Update every 2s
if time.time() - self.last_update > 2:
self.update_state()
self.stdscr.clear()
h, w = self.stdscr.getmaxyx()
# Header
self.draw_header()
# Panels
panel_height = (h - 4) // 2
panel_width = w // 2 - 4
self.draw_state_panel(4, 2, panel_height, w // 2)
if self.mode == "ADMIN":
self.draw_admin_panel(4 + panel_height, 2, panel_height + 2, w // 2)
self.draw_chat_panel(4 + panel_height, w // 2 + 2, panel_height + 2, w // 2 - 2)
else:
self.draw_chat_panel(4, w // 2 + 2, h - 6, w // 2 - 4)
self.handle_input()
self.stdscr.refresh()
except KeyboardInterrupt:
self.running = False
def main():
try:
curses.wrapper(NatirisTUI)
except Exception as e:
print(f"TUI error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()