#!/usr/bin/env python3 """ Second Brain CLI - direkte Nutzung ohne externe Abhängigkeiten. Usage: python -m src.cli add "Faktum" --tag wichtig --source user python -m src.cli search "Faktum" python -m src.cli show python -m src.cli confirm python -m src.cli reject python -m src.cli list python -m src.cli stats python -m src.cli export backup.jsonl python -m src.cli graph python -m src.cli heal python -m src.cli neural-train python -m src.cli loop-check "query" "response" python -m src.cli dashboard """ import argparse import json import os import subprocess import sys from pathlib import Path from .store import EngramStore from .engram import Engram, Grounding from .retriever import Retriever from .chroma_store import ChromaStore from .graph_view import generate_graph_html from .neural_scorer import NeuralScorer from .loop_detector import LoopDetector from .error_healer import ErrorHealer DB_PATH = Path(__file__).parent.parent / "data" / "brain.sqlite" CHROMA_PATH = Path(__file__).parent.parent / "data" / "chroma" def get_store(): DB_PATH.parent.mkdir(parents=True, exist_ok=True) return EngramStore(str(DB_PATH)) def get_chroma(): return ChromaStore(str(CHROMA_PATH)) def cmd_add(args): store = get_store() eg = Engram.create( content=" ".join(args.content), source=args.source, tags=args.tag, grounding=Grounding[args.grounding] if args.grounding else Grounding.ASSUMPTION, ) # Grounding-Regel prüfen (Issue #8) validation = eg.validate_grounding() if not validation["valid"] and args.auto_fix: eg.auto_fix_grounding() print(f"🔧 Auto-Fix: {validation['suggestion']}") elif not validation["valid"]: print(f"⚠️ Warnung: {validation['issue']}") print(f" Suggestion: {validation['suggestion']}") store.save(eg) print(f"Created: {eg.id}\n Content: {eg.content[:100]}\n Confidence: {eg.compute_confidence():.2f}") def cmd_search(args): store = get_store() chroma = get_chroma() ret = Retriever(store, chroma) mode = args.mode if mode == "hybrid": results = ret.hybrid_retrieve( " ".join(args.query), limit=args.limit, min_confidence=args.min_confidence, ) elif mode == "semantic": results = ret.semantic_retrieve( " ".join(args.query), limit=args.limit, min_confidence=args.min_confidence, ) else: results = ret.retrieve( " ".join(args.query), limit=args.limit, min_confidence=args.min_confidence, tag_filter=args.tag, ) print(f"\n=== {len(results)} Results ({mode}) ===") for r in results: eg = r["engram"] conf = eg.compute_confidence() marker = "✅" if conf > 0.7 else "⚠️" if conf > 0.4 else "❌" print(f"\n{marker} [{str(eg.id)[:8]}] Score: {conf:.2f} ({r['match_type']})") print(f" {eg.content[:120]}{'...' if len(eg.content) > 120 else ''}") print(f" Tags: {', '.join(eg.metadata.get('tags', []))} | Source: {eg.metadata.get('source')}") print(f" Access: {eg.metadata.get('access_count', 0)} | Reviews: +{eg.correctness.confirmations}/-{eg.correctness.rejections}") def cmd_show(args): store = get_store() eg = store.get(args.id) if not eg: print(f"Not found: {args.id}") return print(json.dumps(eg.to_dict(), indent=2, ensure_ascii=False, default=str)) def cmd_confirm(args): store = get_store() eg = store.get(args.id) if not eg: print(f"Not found: {args.id}") return eg.correctness.confirm(by="user", note=args.note or "Confirmed via CLI") store.save(eg) print(f"✅ Confirmed [{str(eg.id)[:8]}] -> Confidence: {eg.compute_confidence():.2f}") def cmd_reject(args): store = get_store() eg = store.get(args.id) if not eg: print(f"Not found: {args.id}") return eg.correctness.reject(by="user", note=args.note or "Rejected via CLI") store.save(eg) print(f"❌ Rejected [{str(eg.id)[:8]}] -> Confidence: {eg.compute_confidence():.2f}") def cmd_list(args): store = get_store() egs = store.get_all(limit=args.limit) print(f"\n=== {len(egs)} Engrams ===") for eg in egs: conf = eg.compute_confidence() marker = "✅" if conf > 0.7 else "⚠️" if conf > 0.4 else "❌" print(f"{marker} [{str(eg.id)[:8]}] ({conf:.2f}) {eg.content[:60]}{'...' if len(eg.content) > 60 else ''}") def cmd_stats(args): store = get_store() ret = Retriever(store) try: s = ret.stats() except AttributeError: egs = store.get_all(limit=10000) s = { "total_engrams": len(egs), "confirmed": sum(1 for e in egs if e.correctness.confirmed), "unconfirmed": sum(1 for e in egs if not e.correctness.confirmed), "sources": {src: sum(1 for e in egs if e.metadata.get("source") == src) for src in {e.metadata.get("source") for e in egs}}, "db_size_bytes": os.path.getsize(str(DB_PATH)) if os.path.exists(str(DB_PATH)) else 0, } print("\n=== Second Brain Stats ===") print(f" Total Engrams: {s['total_engrams']}") print(f" Confirmed: {s['confirmed']}") print(f" Unconfirmed: {s['unconfirmed']}") print(f" Sources:") for src, count in s.get("sources", {}).items(): print(f" {src}: {count}") print(f" DB Size: {s['db_size_bytes'] / 1024:.1f} KB") def cmd_export(args): store = get_store() count = store.export_jsonl(args.path) print(f"Exported {count} engrams to {args.path}") def cmd_graph(args): store = get_store() path = args.output or str(DB_PATH.parent / "graph_view.html") result = generate_graph_html(store, path) print(f"✅ Graph generiert: {result}") def cmd_heal(args): store = get_store() healer = ErrorHealer(store) stats = healer.get_error_stats() print("\n=== Error Heal Stats ===") print(f" Total Errors: {stats['total_errors']}") print(f" Repeated Errors: {stats['repeated_errors']}") print(f" Error Types:") for etype, count in stats.get("error_types", {}).items(): print(f" {etype}: {count}") if args.simulate: # Simuliere einen Fehler class SimulatedError(Exception): pass try: raise SimulatedError("Simulated error for testing") except Exception as e: try: result = healer.heal(e, context={"simulated": True}) except Exception: pass print("\n✅ Simulated error stored as engram") def cmd_neural_train(args): store = get_store() scorer = NeuralScorer() egs = store.get_all(limit=10000) labeled = [e for e in egs if e.correctness.confirmed or e.correctness.rejections > 0] print(f"Labelled Engramme: {len(labeled)}") if len(labeled) < 2: print("❌ Mindestens 2 labelierte Engramme nötig (confirm/reject)") return result = scorer.train(labeled, epochs=args.epochs) print(f"✅ Training abgeschlossen") print(json.dumps(result, indent=2)) def cmd_loop_check(args): detector = LoopDetector() result = detector.check(args.query, args.response) print(json.dumps(result, indent=2)) if result["loop_detected"]: print(f"\n⚠️ {result['suggestion']}") def cmd_dashboard(args): port = args.port print(f"🚀 Starte Streamlit Dashboard auf Port {port}...") script = Path(__file__).resolve().parent / "app_dashboard.py" subprocess.run([sys.executable, "-m", "streamlit", "run", str(script), "--server.port", str(port)]) def main(): parser = argparse.ArgumentParser(description="Second Brain CLI") sub = parser.add_subparsers(dest="cmd") p_add = sub.add_parser("add", help="Add a new engram") p_add.add_argument("content", nargs="+") p_add.add_argument("--tag", action="append", default=[]) p_add.add_argument("--source", default="user") p_add.add_argument("--grounding", choices=[g.name for g in Grounding]) p_add.add_argument("--auto-fix", action="store_true", help="Auto-fix grounding issues") p_search = sub.add_parser("search", help="Search engrams") p_search.add_argument("query", nargs="+") p_search.add_argument("--limit", type=int, default=5) p_search.add_argument("--min-confidence", type=float, default=0.0) p_search.add_argument("--tag", default=None) p_search.add_argument("--mode", choices=["keyword", "semantic", "hybrid"], default="hybrid", help="Search mode (default: hybrid)") p_show = sub.add_parser("show", help="Show engram details") p_show.add_argument("id") p_confirm = sub.add_parser("confirm", help="Confirm an engram") p_confirm.add_argument("id") p_confirm.add_argument("--note", default="") p_reject = sub.add_parser("reject", help="Reject an engram") p_reject.add_argument("id") p_reject.add_argument("--note", default="") p_list = sub.add_parser("list", help="List recent engrams") p_list.add_argument("--limit", type=int, default=20) p_stats = sub.add_parser("stats", help="Show statistics") p_export = sub.add_parser("export", help="Export to JSONL") p_export.add_argument("path") p_graph = sub.add_parser("graph", help="Generate graph visualization") p_graph.add_argument("--output", default=None, help="Output HTML path") p_heal = sub.add_parser("heal", help="Show error healing stats") p_heal.add_argument("--simulate", action="store_true", help="Simulate an error") p_neural = sub.add_parser("neural-train", help="Train neural scorer") p_neural.add_argument("--epochs", type=int, default=30) p_loop = sub.add_parser("loop-check", help="Check for conversation loops") p_loop.add_argument("query") p_loop.add_argument("response") p_dash = sub.add_parser("dashboard", help="Launch Streamlit dashboard") p_dash.add_argument("--port", type=int, default=8501) args = parser.parse_args() if not args.cmd: parser.print_help() return handlers = { "add": cmd_add, "search": cmd_search, "show": cmd_show, "confirm": cmd_confirm, "reject": cmd_reject, "list": cmd_list, "stats": cmd_stats, "export": cmd_export, "graph": cmd_graph, "heal": cmd_heal, "neural-train": cmd_neural_train, "loop-check": cmd_loop_check, "dashboard": cmd_dashboard, } handler = handlers.get(args.cmd) if handler: handler(args) else: parser.print_help() if __name__ == "__main__": main()