#!/usr/bin/env python3 """ shango_cli.py — Interface unifiée Shango """ import argparse, json, subprocess, sys from pathlib import Path SHANGO_DIR = Path("/root/.shango") VERSION = "0.1.0-shango" def cmd_status(): """État du mesh Shango + Tailscale.""" print("═══ SHANGO MESH STATUS ═══") print(f"Version: {VERSION}") # Tailscale try: out = subprocess.check_output(["tailscale", "status"], text=True, timeout=5) lines = out.strip().split("\n") print(f"Tailscale peers: {len(lines)}") for l in lines[:3]: print(f" {l}") if len(lines) > 3: print(f" ... +{len(lines)-3} autres") except: print("Tailscale: NON CONNECTE") # Shango state state_file = SHANGO_DIR / "mesh_state.json" if state_file.exists(): state = json.load(open(state_file)) print(f"Shango node: {state.get('node_id', 'unknown')}") print(f"Health: {state.get('health', 0):.0%}") print(f"Peers connus: {state.get('peers_count', 0)}") else: print("Shango: pas encore initialisé. Lance: shango-cli singularize") # Services Ivoire services = { "hermes": "singularite_workspace:8080", "erp": "odoo_web:8069", "ai": "ollama:11434", "chat": "open-webui:8080", "status": "uptime-kuma:3001", "git": "gitea:3000", } print("\nServices Ivoire:") for name, addr in services.items(): url = f"https://{name}.ivoire-monade.shop" print(f" {name:10} → {url}") def cmd_heal(): """Auto-réparation Shango.""" sys.path.insert(0, "/root/shango/shango-daemon") from shango_daemon import ShangoNode node = ShangoNode() result = node.heal() print(json.dumps(result, indent=2)) def cmd_singularize(): """Singularise les identités.""" sys.path.insert(0, "/root/shango/shango-daemon") from shango_daemon import ShangoNode node = ShangoNode() result = node.singularize(["hermes", "erp", "ai", "status"]) print(json.dumps(result, indent=2)) print("\n[SINGULARIZE] Hermes + Odoo + Tailscale + ivoire-monade.shop unifiés.") def cmd_mcp_scan(path: str): """Scan un projet pour générer un serveur MCP.""" sys.path.insert(0, "/root/shango/mcp-builder") from mcp_builder import main as mcp_main sys.argv = ["mcp-builder", path] mcp_main() def cmd_glm_check(): """Vérifie les déps GLM training.""" sys.path.insert(0, "/root/shango/glm-trainer") from glm_trainer import check_deps check_deps() def cmd_maieutic(subcmd: str = "ask", qid: str = None, choice: int = None, text: str = ""): """Moteur maieutique Socratique.""" sys.path.insert(0, "/root/shango/shango-maieutic") from shango_maieutic import MaieuticEngine engine = MaieuticEngine() if subcmd == "ask": result = engine.ask() if result["status"] == "question": print(f"\n[{result['dimension'].upper()}] {result['context']}") print(f"\n? {result['question']}") for i, opt in enumerate(result["options"]): print(f" [{i}] {opt}") print(f"\nOdù possibles: {', '.join(result['odu_map'])}") print(f"\n{result['progress']}") print(f"\nPour répondre: shango maieutic answer --qid {result['qid']} --choice <0-3>") else: print(result["message"]) elif subcmd == "answer": if not qid or choice is None: print("Usage: shango maieutic answer --qid --choice <0-3> [--text 'libre']") return result = engine.answer(qid, choice, text) print(json.dumps(result, indent=2)) elif subcmd == "profile": print(engine.get_profile_summary()) def cmd_forge(args): """Forge multi-runtime wrapper.""" sys.path.insert(0, "/root/shango/ivoire-forge") from shango_forge import detect_runtime, build_docker, run_container, gitea_push, RUNTIMES from pathlib import Path src = Path(args.dir) if args.subcmd == "detect": rt = detect_runtime(src) print(json.dumps({"runtime": rt, "source": str(src)}, indent=2)) elif args.subcmd == "build": rt = args.runtime or detect_runtime(src) if not rt: print("Runtime non detecte. Utilise --runtime.") return tag = build_docker(src, rt, args.tag) print(json.dumps({"image": tag, "runtime": rt})) elif args.subcmd == "run": ports = {} for p in args.port: h, c = p.split(":"); ports[h] = c envs = {} for e in args.env: k, v = e.split("=", 1); envs[k] = v cid = run_container(args.tag, args.name, ports, envs) print(json.dumps({"container_id": cid, "name": args.name})) elif args.subcmd == "gitea-push": if not args.repo: print("--repo obligatoire") return gitea_push(src, args.repo) elif args.subcmd == "status": import shutil for name, meta in RUNTIMES.items(): ok = shutil.which(meta["cmd"]) is not None print(f" {name:12} {'OK' if ok else 'ABSENT'} ({meta['docker']})") def main(): parser = argparse.ArgumentParser(prog="shango", description="Shango Mesh CLI") sub = parser.add_subparsers(dest="command") sub.add_parser("status", help="État du mesh") sub.add_parser("heal", help="Auto-réparer") sub.add_parser("singularize", help="Unifier identités") p_mcp = sub.add_parser("mcp-scan", help="Générer serveur MCP depuis code") p_mcp.add_argument("path", nargs="?", default=".") sub.add_parser("glm-check", help="Vérifier deps GLM") p_mai = sub.add_parser("maieutic", help="Pose une question maïeutique") p_mai.add_argument("subcmd", nargs="?", default="ask", choices=["ask", "answer", "profile"]) p_mai.add_argument("--qid", default=None) p_mai.add_argument("--choice", type=int, default=None) p_mai.add_argument("--text", default="") p_forge = sub.add_parser("forge", help="Forge multi-runtime: build/run/detect/push") p_forge.add_argument("subcmd", nargs="?", default="status", choices=["detect","build","run","gitea-push","status"]) p_forge.add_argument("--dir", default=".", help="Répertoire source") p_forge.add_argument("--tag", default="shango/forge:latest") p_forge.add_argument("--runtime", choices=["go","rust","node","python3","web3","pytorch","react"]) p_forge.add_argument("--name", default="forge-app") p_forge.add_argument("--port", action="append", default=[], help="host:container") p_forge.add_argument("--env", action="append", default=[], help="KEY=VAL") p_forge.add_argument("--repo", default="") args = parser.parse_args() if args.command == "status": cmd_status() elif args.command == "heal": cmd_heal() elif args.command == "singularize": cmd_singularize() elif args.command == "mcp-scan": cmd_mcp_scan(args.path) elif args.command == "glm-check": cmd_glm_check() elif args.command == "maieutic": cmd_maieutic(args.subcmd, args.qid, args.choice, args.text) elif args.command == "forge": cmd_forge(args) else: parser.print_help() if __name__ == "__main__": main()