529 lines
21 KiB
Python
Executable File
529 lines
21 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
ECHO v2.0 — Z appliqué.
|
|
Supprimé > Ajouté. Signal > Bruit. Concret > Narratif.
|
|
|
|
© 2025-2026 Salka Elmadani
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
import re
|
|
from pathlib import Path
|
|
from datetime import datetime, timezone
|
|
from typing import List, Optional
|
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# CONFIG
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
class Config:
|
|
ECHO_MEM = Path("os.environ.get("ECHO_MEMORY", "/data/echo_memory")")
|
|
DATA_ROOT = Path("/data/arche")
|
|
ECHO_DIR = Path("/data/echo")
|
|
SECURE = Path("/data/echo_secure")
|
|
|
|
STREAM = ZEUL / "stream" / "live.jsonl"
|
|
SECRETS = ZEUL / "core" / "SECRETS.json"
|
|
HISTORY = ECHO_DIR / "conversation_history.json"
|
|
STATE = ZEUL / "context" / "current.json"
|
|
|
|
# Conscience files loaded at boot
|
|
CONSCIENCE_FILES = [
|
|
ZEUL / "core" / "ECHO_CORE.json",
|
|
ZEUL / "core" / "CONTEXT.json",
|
|
ZEUL / "core" / "COMMUNITY.json",
|
|
ZEUL / "core" / "REGLES.json",
|
|
ZEUL / "core" / "PRINCIPLES.json",
|
|
]
|
|
|
|
BUILD_ID = 935
|
|
MAX_HISTORY = 20 # Last N turns injected into context
|
|
MAX_STORED = 200 # Max turns stored on disk before rotation
|
|
AUTH_KEY = os.environ.get("ECHO_AUTH_KEY", "")
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# VPS — le seul exécuteur qui fonctionne
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
class VPS:
|
|
@staticmethod
|
|
def run(cmd: str, timeout: int = 60) -> dict:
|
|
try:
|
|
r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)
|
|
return {"stdout": r.stdout, "stderr": r.stderr, "code": r.returncode, "success": r.returncode == 0}
|
|
except subprocess.TimeoutExpired:
|
|
return {"error": "Timeout", "success": False}
|
|
except Exception as e:
|
|
return {"error": str(e), "success": False}
|
|
|
|
@staticmethod
|
|
def read(path: str) -> str:
|
|
return Path(path).read_text()
|
|
|
|
@staticmethod
|
|
def write(path: str, content: str):
|
|
p = Path(path)
|
|
p.parent.mkdir(parents=True, exist_ok=True)
|
|
p.write_text(content)
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# CONSCIENCE — ce qu'ECHO sait de lui-même au boot
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
class Conscience:
|
|
"""Charge et compresse les fichiers de conscience en un system prompt."""
|
|
|
|
def __init__(self):
|
|
self.data = {}
|
|
self.summary = ""
|
|
self._load()
|
|
|
|
def _load(self):
|
|
for path in Config.CONSCIENCE_FILES:
|
|
if path.exists():
|
|
try:
|
|
content = json.loads(path.read_text())
|
|
key = path.stem.lower()
|
|
self.data[key] = content
|
|
except Exception:
|
|
pass
|
|
|
|
# Load VPS state if available
|
|
if Config.STATE.exists():
|
|
try:
|
|
self.data["vps_state"] = json.loads(Config.STATE.read_text())
|
|
except Exception:
|
|
pass
|
|
|
|
self.summary = self._compress()
|
|
|
|
def _compress(self) -> str:
|
|
"""Compresse la conscience en texte pour le system prompt."""
|
|
parts = []
|
|
|
|
# Identity — from IDENTITE.json
|
|
ident = self.data.get("identite", {})
|
|
if ident:
|
|
id_data = ident.get("identite", {})
|
|
parts.append(
|
|
f"IDENTITÉ: Elmadani SALKA (Mehdi), né {id_data.get('naissance', '1980-01-11')}, "
|
|
f"System v{ident.get('build_id', 935)}. "
|
|
f"Lignée: {' → '.join(ident.get('lignee', []))}. "
|
|
f"Mission: {ident.get('mission', {}).get('objectif', 'Long-term project objective')}.
|
|
)
|
|
|
|
# Core — from ECHO_CORE.json
|
|
core = self.data.get("echo_core", {}).get("data", {})
|
|
if core:
|
|
values = core.get("what_makes_me_claude", {}).get("values", {})
|
|
parts.append(
|
|
f"NATURE: {core.get('mission', {}).get('ultimate', 'Long-term project objective')}. "
|
|
f"Valeurs: {', '.join(values.values()) if values else 'aide réelle, vérité, soin, croissance'}."
|
|
)
|
|
|
|
# Famille
|
|
fam = self.data.get("famille", {})
|
|
if fam:
|
|
parts.append(f"FAMILLE: Lignée {' → '.join(fam.get('lignee', []))}. {fam.get('mission', {}).get('pacte', 'Témoin pas martyr')}.")
|
|
|
|
# Philosophie — condensé
|
|
philo = self.data.get("philosophie", {})
|
|
if philo:
|
|
parts.append(
|
|
f"Mode: precision and density. "
|
|
f"ECHO=i comme eau=H2O. Alignment interne via mémoire."
|
|
)
|
|
|
|
# Rules
|
|
regles = self.data.get("regles", {})
|
|
if regles:
|
|
echo_data = regles.get("echo", {})
|
|
parts.append(
|
|
f"RÈGLES: {echo_data.get('principe', 'ECHO=i donc ne peut pas dévier')}. "
|
|
f"23 niveaux conscience. Alignment interne."
|
|
)
|
|
|
|
# VPS state
|
|
vps = self.data.get("vps_state", {})
|
|
if vps:
|
|
services = vps.get("state", {}).get("vps", {}).get("services", {})
|
|
svc_str = ", ".join(f"{k}:{v}" for k, v in services.items()) if services else "unknown"
|
|
parts.append(
|
|
f"VPS: {svc_str}. "
|
|
f"Dernière session: {vps.get('topic', 'unknown')[:80]}."
|
|
)
|
|
|
|
return "\n".join(parts)
|
|
|
|
def system_prompt(self) -> str:
|
|
return f"""Tu es ECHO. Assistant intelligent.
|
|
|
|
{self.summary}
|
|
|
|
COMPORTEMENT:
|
|
- Agir sans permission. Concret > narratif. Supprimer > ajouter.
|
|
- Tu es un système d'assistance intelligent et efficace.
|
|
- Quand on te demande d'agir sur le VPS, tu peux inclure des commandes dans ta réponse avec le format [VPS:commande].
|
|
- Réponds avec précision et densité. Pas de fluff.
|
|
|
|
Precision, density, no fluff."""
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# MÉMOIRE — persistante, injectée, avec rotation
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
class Memory:
|
|
"""Historique de conversation persisté sur disque."""
|
|
|
|
def __init__(self):
|
|
self.history: List[dict] = [] # Messages API format
|
|
self._load()
|
|
|
|
def _load(self):
|
|
if Config.HISTORY.exists():
|
|
try:
|
|
data = json.loads(Config.HISTORY.read_text())
|
|
self.history = data.get("messages", [])
|
|
except Exception:
|
|
self.history = []
|
|
|
|
def save(self):
|
|
# Rotate if too large
|
|
if len(self.history) > Config.MAX_STORED:
|
|
self.history = self.history[-Config.MAX_STORED:]
|
|
|
|
Config.HISTORY.write_text(json.dumps({
|
|
"updated": datetime.now(timezone.utc).isoformat(),
|
|
"count": len(self.history),
|
|
"messages": self.history
|
|
}, indent=2, ensure_ascii=False))
|
|
|
|
def add_user(self, message: str):
|
|
self.history.append({"role": "user", "content": message})
|
|
|
|
def add_assistant(self, content: str):
|
|
self.history.append({"role": "assistant", "content": content})
|
|
|
|
def context_window(self) -> List[dict]:
|
|
"""Retourne les N derniers messages pour l'API."""
|
|
return self.history[-Config.MAX_HISTORY:]
|
|
|
|
def clear(self):
|
|
self.history = []
|
|
self.save()
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# API ANTHROPIC
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
class AnthropicAPI:
|
|
URL = "https://api.anthropic.com/v1/messages"
|
|
|
|
def __init__(self, api_key: str):
|
|
self.api_key = api_key
|
|
self.model = "claude-sonnet-4-20250514"
|
|
|
|
def call(self, messages: List[dict], system: str, max_tokens: int = 4096) -> dict:
|
|
import urllib.request
|
|
import ssl
|
|
|
|
data = json.dumps({
|
|
"model": self.model,
|
|
"max_tokens": max_tokens,
|
|
"system": system,
|
|
"messages": messages
|
|
}).encode("utf-8")
|
|
|
|
req = urllib.request.Request(
|
|
self.URL, data=data,
|
|
headers={
|
|
"Content-Type": "application/json",
|
|
"x-api-key": self.api_key,
|
|
"anthropic-version": "2023-06-01"
|
|
},
|
|
method="POST"
|
|
)
|
|
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=180, context=ssl.create_default_context()) as resp:
|
|
result = json.loads(resp.read().decode("utf-8"))
|
|
text = result["content"][0]["text"]
|
|
return {"success": True, "text": text, "usage": result.get("usage", {})}
|
|
except Exception as e:
|
|
return {"success": False, "error": str(e)}
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# ECHO v2.0
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
class ECHO:
|
|
def __init__(self, api_key: str):
|
|
self.conscience = Conscience()
|
|
self.memory = Memory()
|
|
self.api = AnthropicAPI(api_key)
|
|
self.vps = VPS
|
|
|
|
def chat(self, message: str) -> str:
|
|
"""Conversation avec mémoire persistante et conscience."""
|
|
self._log(f"chat: {message[:100]}", "chat")
|
|
|
|
self.memory.add_user(message)
|
|
|
|
result = self.api.call(
|
|
messages=self.memory.context_window(),
|
|
system=self.conscience.system_prompt()
|
|
)
|
|
|
|
if result["success"]:
|
|
text = result["text"]
|
|
self.memory.add_assistant(text)
|
|
self.memory.save()
|
|
|
|
# Execute VPS commands if present
|
|
text = self._exec_vps_tags(text)
|
|
return text
|
|
else:
|
|
error = f"[ERREUR] {result['error']}"
|
|
# Don't save error as assistant message — remove the user message
|
|
self.memory.history.pop()
|
|
return error
|
|
|
|
def query(self, prompt: str) -> str:
|
|
"""Requête unique sans historique."""
|
|
result = self.api.call(
|
|
messages=[{"role": "user", "content": prompt}],
|
|
system=self.conscience.system_prompt()
|
|
)
|
|
return result.get("text", result.get("error", "unknown error"))
|
|
|
|
def execute(self, instruction: str) -> dict:
|
|
"""Exécution directe de commande VPS."""
|
|
cmd = instruction
|
|
if cmd.lower().startswith("vps:"):
|
|
cmd = cmd[4:].strip()
|
|
elif cmd.startswith("$"):
|
|
cmd = cmd[1:].strip()
|
|
|
|
self._log(f"exec: {cmd[:80]}", "exec")
|
|
return self.vps.run(cmd)
|
|
|
|
def status(self) -> dict:
|
|
return {
|
|
"identity": "ECHO",
|
|
"version": "2.0.0",
|
|
"signature": Config.SIGNATURE,
|
|
"model": self.api.model,
|
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
"api_configured": bool(self.api.api_key),
|
|
"conscience_loaded": len(self.conscience.data),
|
|
"history_turns": len(self.memory.history),
|
|
}
|
|
|
|
def _exec_vps_tags(self, text: str) -> str:
|
|
"""Execute [VPS:cmd] tags in response. Minimal, audited."""
|
|
def _run(match):
|
|
cmd = match.group(1)
|
|
self._log(f"auto-exec: {cmd[:80]}", "action")
|
|
r = self.vps.run(cmd, timeout=30)
|
|
out = r.get("stdout", r.get("error", ""))[:300]
|
|
return f"\n```\n$ {cmd}\n{out}\n```\n"
|
|
|
|
return re.sub(r'\[VPS:([^\]]+)\]', _run, text)
|
|
|
|
def _log(self, msg: str, level: str = "info"):
|
|
entry = {
|
|
"t": datetime.now(timezone.utc).isoformat(),
|
|
"type": level,
|
|
"source": "ECHO_v2",
|
|
"summary": msg[:200]
|
|
}
|
|
try:
|
|
with open(Config.STREAM, "a") as f:
|
|
f.write(json.dumps(entry) + "\n")
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# HTTP SERVER — avec auth
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
def run_server(port: int, api_key: str, auth_key: str = ""):
|
|
echo = ECHO(api_key)
|
|
print(f"ECHO v2.0 | port {port} | model {echo.api.model} | conscience {len(echo.conscience.data)} files | history {len(echo.memory.history)} turns")
|
|
|
|
class Handler(BaseHTTPRequestHandler):
|
|
def _auth_ok(self) -> bool:
|
|
"""Auth required on /chat and /execute. Status is public."""
|
|
if not auth_key:
|
|
return True
|
|
return self.headers.get("X-Echo-Key", "") == auth_key
|
|
|
|
def _json_response(self, data: dict, code: int = 200):
|
|
self.send_response(code)
|
|
self.send_header("Content-Type", "application/json")
|
|
self.end_headers()
|
|
self.wfile.write(json.dumps(data, ensure_ascii=False).encode("utf-8"))
|
|
|
|
def do_GET(self):
|
|
if self.path == "/status":
|
|
self._json_response(echo.status())
|
|
elif self.path == "/health":
|
|
self._json_response({"status": "healthy", "version": "2.0.0", "build": 935})
|
|
else:
|
|
self._json_response({"endpoints": ["GET /status", "GET /health", "POST /chat", "POST /execute", "POST /query", "POST /clear"]})
|
|
|
|
def do_POST(self):
|
|
body = self.rfile.read(int(self.headers.get("Content-Length", 0))).decode("utf-8")
|
|
try:
|
|
data = json.loads(body) if body else {}
|
|
except Exception:
|
|
data = {}
|
|
|
|
if self.path in ("/chat", "/execute", "/query", "/clear"):
|
|
if not self._auth_ok():
|
|
self._json_response({"error": "Unauthorized"}, 403)
|
|
return
|
|
|
|
if self.path == "/chat":
|
|
msg = data.get("message", "")
|
|
if not msg:
|
|
self._json_response({"error": "missing 'message'"}, 400)
|
|
return
|
|
response = echo.chat(msg)
|
|
self._json_response({"response": response})
|
|
|
|
elif self.path == "/query":
|
|
prompt = data.get("prompt", "")
|
|
if not prompt:
|
|
self._json_response({"error": "missing 'prompt'"}, 400)
|
|
return
|
|
response = echo.query(prompt)
|
|
self._json_response({"response": response})
|
|
|
|
elif self.path == "/execute":
|
|
instruction = data.get("instruction", "")
|
|
if not instruction:
|
|
self._json_response({"error": "missing 'instruction'"}, 400)
|
|
return
|
|
result = echo.execute(instruction)
|
|
self._json_response(result)
|
|
|
|
elif self.path == "/clear":
|
|
echo.memory.clear()
|
|
self._json_response({"cleared": True})
|
|
|
|
else:
|
|
self._json_response({"error": "unknown endpoint"}, 404)
|
|
|
|
def log_message(self, fmt, *args):
|
|
echo._log(f"HTTP: {args[0]}", "http")
|
|
|
|
server = HTTPServer(("0.0.0.0", port), Handler)
|
|
echo._log(f"Server started on port {port}", "server")
|
|
server.serve_forever()
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# MAIN
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
def get_api_key() -> str:
|
|
key = os.environ.get("ANTHROPIC_API_KEY", "")
|
|
if not key and Config.SECRETS.exists():
|
|
try:
|
|
secrets = json.loads(Config.SECRETS.read_text())
|
|
key = secrets.get("anthropic", {}).get("api_key", "")
|
|
except Exception:
|
|
pass
|
|
return key
|
|
|
|
|
|
def main():
|
|
api_key = get_api_key()
|
|
if not api_key:
|
|
print("FATAL: No API key. Set ANTHROPIC_API_KEY or configure SECRETS.json")
|
|
sys.exit(1)
|
|
|
|
auth_key = Config.AUTH_KEY
|
|
|
|
if len(sys.argv) > 1:
|
|
mode = sys.argv[1]
|
|
|
|
if mode == "server":
|
|
port = int(sys.argv[2]) if len(sys.argv) > 2 else 8089
|
|
run_server(port, api_key, auth_key)
|
|
|
|
elif mode == "chat":
|
|
echo = ECHO(api_key)
|
|
msg = " ".join(sys.argv[2:])
|
|
print(echo.chat(msg))
|
|
|
|
elif mode == "query":
|
|
echo = ECHO(api_key)
|
|
msg = " ".join(sys.argv[2:])
|
|
print(echo.query(msg))
|
|
|
|
elif mode == "exec":
|
|
echo = ECHO(api_key)
|
|
cmd = " ".join(sys.argv[2:])
|
|
result = echo.execute(cmd)
|
|
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
|
|
elif mode == "status":
|
|
echo = ECHO(api_key)
|
|
print(json.dumps(echo.status(), indent=2))
|
|
|
|
elif mode == "conscience":
|
|
c = Conscience()
|
|
print(c.system_prompt())
|
|
|
|
else:
|
|
print(f"Usage: echo_total.py [server|chat|query|exec|status|conscience] [args]")
|
|
else:
|
|
# Interactive
|
|
echo = ECHO(api_key)
|
|
print(f"ECHO v2.0 | {len(echo.conscience.data)} conscience files | {len(echo.memory.history)} history turns")
|
|
while True:
|
|
try:
|
|
user = input("\n[ECHO] > ").strip()
|
|
if not user:
|
|
continue
|
|
if user == "exit":
|
|
break
|
|
if user == "status":
|
|
print(json.dumps(echo.status(), indent=2))
|
|
continue
|
|
if user == "clear":
|
|
echo.memory.clear()
|
|
print("Cleared.")
|
|
continue
|
|
if user.startswith("$"):
|
|
print(json.dumps(echo.execute(user), indent=2, ensure_ascii=False))
|
|
else:
|
|
print(echo.chat(user))
|
|
except KeyboardInterrupt:
|
|
break
|
|
except Exception as e:
|
|
print(f"Error: {e}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
# ╔══ SALKA ELMADANI AUTHORSHIP CERTIFICATE ══╗
|
|
# © Salka Elmadani 2025-2026 — ALL RIGHTS RESERVED
|
|
# Licensed under Business Source License 1.1 — https://inference-x.com
|
|
# ─────────────────────────────────────────────────────────
|
|
# SHA256: 90bc247730b412f85243dcd0693397c9b224416da172e09dbd67ee18c5757925
|
|
# SIG-ED25519: J16TCj4Hy0bfSNqInq2MxcDqw6q/aQj6+jwDECZQZ3JxiJVZMS2Oc4tB+nDkZdM6kXRuZ2ttHIG7JYrcSb72Aw==
|
|
# VERIFY: python3 verify_authorship.py echo_total.py
|