{ config, lib, pkgs, ... }: with lib; let cfg = config.services.macha-autonomous; # Python environment with all dependencies pythonEnv = pkgs.python3.withPackages (ps: with ps; [ requests psutil chromadb ]); # Main autonomous system package macha-autonomous = pkgs.writeScriptBin "macha-autonomous" '' #!${pythonEnv}/bin/python3 import sys sys.path.insert(0, "${./.}") from orchestrator import main main() ''; # Config file configFile = pkgs.writeText "macha-autonomous-config.json" (builtins.toJSON { check_interval = cfg.checkInterval; autonomy_level = cfg.autonomyLevel; ollama_host = cfg.ollamaHost; model = cfg.model; config_repo = cfg.configRepo; config_branch = cfg.configBranch; }); in { options.services.macha-autonomous = { enable = mkEnableOption "Macha autonomous system maintenance"; autonomyLevel = mkOption { type = types.enum [ "observe" "suggest" "auto-safe" "auto-full" ]; default = "suggest"; description = '' Level of autonomy for the system: - observe: Only monitor and log, no actions - suggest: Propose actions, require manual approval - auto-safe: Auto-execute low-risk actions (restarts, cleanup) - auto-full: Full autonomy with safety limits (still requires approval for high-risk) ''; }; checkInterval = mkOption { type = types.int; default = 300; description = "Interval in seconds between system checks"; }; ollamaHost = mkOption { type = types.str; default = "http://localhost:11434"; description = "Ollama API host"; }; model = mkOption { type = types.str; default = "llama3.1:70b"; description = "LLM model to use for reasoning"; }; user = mkOption { type = types.str; default = "macha"; description = "User to run the autonomous system as"; }; group = mkOption { type = types.str; default = "macha"; description = "Group to run the autonomous system as"; }; gotifyUrl = mkOption { type = types.str; default = ""; example = "http://rhiannon:8181"; description = "Gotify server URL for notifications (empty to disable)"; }; gotifyToken = mkOption { type = types.str; default = ""; description = "Gotify application token for notifications"; }; remoteSystems = mkOption { type = types.listOf types.str; default = []; example = [ "rhiannon" "alexander" ]; description = "List of remote NixOS systems to monitor and maintain"; }; configRepo = mkOption { type = types.str; default = if config.programs.nh.flake != null then config.programs.nh.flake else "git+https://git.coven.systems/lily/nixos-servers"; description = "URL of the NixOS configuration repository (auto-detected from programs.nh.flake if available)"; }; configBranch = mkOption { type = types.str; default = "main"; description = "Branch of the NixOS configuration repository"; }; }; config = mkIf cfg.enable { # Create user and group users.users.${cfg.user} = { isSystemUser = true; group = cfg.group; uid = 2501; description = "Macha autonomous system maintenance"; home = "/var/lib/macha"; createHome = true; }; users.groups.${cfg.group} = {}; # Git configuration for credential storage programs.git = { enable = true; config = { credential.helper = "store"; }; }; # Ollama service for AI inference services.ollama = { enable = true; acceleration = "rocm"; host = "0.0.0.0"; port = 11434; environmentVariables = { "OLLAMA_DEBUG" = "1"; "OLLAMA_KEEP_ALIVE" = "600"; "OLLAMA_NEW_ENGINE" = "true"; "OLLAMA_CONTEXT_LENGTH" = "131072"; }; openFirewall = false; # Keep internal only loadModels = [ "qwen3" "gpt-oss" "gemma3" "gpt-oss:20b" "qwen3:4b-instruct-2507-fp16" "qwen3:8b-fp16" "mistral:7b" "chroma/all-minilm-l6-v2-f32:latest" ]; }; # ChromaDB service for vector storage services.chromadb = { enable = true; port = 8000; dbpath = "/var/lib/chromadb"; }; # Give the user permissions it needs security.sudo.extraRules = [{ users = [ cfg.user ]; commands = [ # Local system management { command = "${pkgs.systemd}/bin/systemctl restart *"; options = [ "NOPASSWD" ]; } { command = "${pkgs.systemd}/bin/systemctl status *"; options = [ "NOPASSWD" ]; } { command = "${pkgs.systemd}/bin/journalctl *"; options = [ "NOPASSWD" ]; } { command = "${pkgs.nix}/bin/nix-collect-garbage *"; options = [ "NOPASSWD" ]; } # Remote system access (uses existing root SSH keys) { command = "${pkgs.openssh}/bin/ssh *"; options = [ "NOPASSWD" ]; } { command = "${pkgs.openssh}/bin/scp *"; options = [ "NOPASSWD" ]; } { command = "${pkgs.nixos-rebuild}/bin/nixos-rebuild *"; options = [ "NOPASSWD" ]; } ]; }]; # Config file environment.etc."macha-autonomous/config.json".source = configFile; # State directory and queue directories (world-writable queues for multi-user access) # Using 'z' to set permissions even if directory exists systemd.tmpfiles.rules = [ "d /var/lib/macha 0755 ${cfg.user} ${cfg.group} -" "z /var/lib/macha 0755 ${cfg.user} ${cfg.group} -" # Ensure permissions are set "d /var/lib/macha/queues 0777 ${cfg.user} ${cfg.group} -" "d /var/lib/macha/queues/ollama 0777 ${cfg.user} ${cfg.group} -" "d /var/lib/macha/queues/ollama/pending 0777 ${cfg.user} ${cfg.group} -" "d /var/lib/macha/queues/ollama/processing 0777 ${cfg.user} ${cfg.group} -" "d /var/lib/macha/queues/ollama/completed 0777 ${cfg.user} ${cfg.group} -" "d /var/lib/macha/queues/ollama/failed 0777 ${cfg.user} ${cfg.group} -" "d /var/lib/macha/tool_cache 0777 ${cfg.user} ${cfg.group} -" ]; # Systemd service systemd.services.macha-autonomous = { description = "Macha Autonomous System Maintenance"; after = [ "network.target" "ollama.service" ]; wants = [ "ollama.service" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "simple"; User = cfg.user; Group = cfg.group; WorkingDirectory = "/var/lib/macha"; ExecStart = "${macha-autonomous}/bin/macha-autonomous --mode continuous --autonomy ${cfg.autonomyLevel} --interval ${toString cfg.checkInterval}"; Restart = "on-failure"; RestartSec = "30s"; # Security hardening PrivateTmp = true; NoNewPrivileges = false; # Need privileges for sudo ProtectSystem = "strict"; ProtectHome = true; ReadWritePaths = [ "/var/lib/macha" "/var/lib/macha/tool_cache" "/var/lib/macha/queues" ]; # Resource limits MemoryLimit = "1G"; CPUQuota = "50%"; }; environment = { PYTHONPATH = toString ./.; GOTIFY_URL = cfg.gotifyUrl; GOTIFY_TOKEN = cfg.gotifyToken; CHROMA_ENV_FILE = ""; # Prevent ChromaDB from trying to read .env files ANONYMIZED_TELEMETRY = "False"; # Disable ChromaDB telemetry }; path = [ pkgs.git ]; # Make git available for config parsing }; # Ollama Queue Worker Service (serializes all Ollama requests) systemd.services.ollama-queue-worker = { description = "Macha Ollama Queue Worker"; after = [ "network.target" "ollama.service" ]; wants = [ "ollama.service" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "simple"; User = cfg.user; Group = cfg.group; WorkingDirectory = "/var/lib/macha"; ExecStart = "${pythonEnv}/bin/python3 ${./.}/ollama_worker.py"; Restart = "on-failure"; RestartSec = "10s"; # Security hardening PrivateTmp = true; NoNewPrivileges = true; ProtectSystem = "strict"; ProtectHome = true; ReadWritePaths = [ "/var/lib/macha/queues" "/var/lib/macha/tool_cache" ]; # Resource limits MemoryLimit = "512M"; CPUQuota = "25%"; }; environment = { PYTHONPATH = toString ./.; CHROMA_ENV_FILE = ""; ANONYMIZED_TELEMETRY = "False"; }; }; # CLI tools for manual control and system packages environment.systemPackages = with pkgs; [ macha-autonomous # Python packages for ChromaDB python313 python313Packages.pip python313Packages.chromadb.pythonModule # Tool to check approval queue (pkgs.writeScriptBin "macha-approve" '' #!${pkgs.bash}/bin/bash if [ "$1" == "list" ]; then sudo -u ${cfg.user} ${pythonEnv}/bin/python3 ${./.}/executor.py queue elif [ "$1" == "discuss" ] && [ -n "$2" ]; then ACTION_ID="$2" echo "===================================================================" echo "Interactive Discussion with Macha about Action #$ACTION_ID" echo "===================================================================" echo "" # Initial explanation sudo -u ${cfg.user} ${pkgs.coreutils}/bin/env CHROMA_ENV_FILE="" ANONYMIZED_TELEMETRY="False" ${pythonEnv}/bin/python3 ${./.}/conversation.py --discuss "$ACTION_ID" echo "" echo "===================================================================" echo "You can now ask follow-up questions about this action." echo "Type 'approve' to approve it, 'reject' to reject it, or 'exit' to quit." echo "===================================================================" # Interactive loop while true; do echo "" echo -n "You: " read -r USER_INPUT # Check for special commands if [ "$USER_INPUT" = "exit" ] || [ "$USER_INPUT" = "quit" ] || [ -z "$USER_INPUT" ]; then echo "Exiting discussion." break elif [ "$USER_INPUT" = "approve" ]; then echo "Approving action #$ACTION_ID..." sudo -u ${cfg.user} ${pythonEnv}/bin/python3 ${./.}/executor.py approve "$ACTION_ID" break elif [ "$USER_INPUT" = "reject" ]; then echo "Rejecting and removing action #$ACTION_ID from queue..." sudo -u ${cfg.user} ${pythonEnv}/bin/python3 ${./.}/executor.py reject "$ACTION_ID" break fi # Ask Macha the follow-up question in context of the action echo "" echo -n "Macha: " sudo -u ${cfg.user} ${pkgs.coreutils}/bin/env CHROMA_ENV_FILE="" ANONYMIZED_TELEMETRY="False" ${pythonEnv}/bin/python3 ${./.}/conversation.py --discuss "$ACTION_ID" --follow-up "$USER_INPUT" echo "" done elif [ "$1" == "approve" ] && [ -n "$2" ]; then sudo -u ${cfg.user} ${pythonEnv}/bin/python3 ${./.}/executor.py approve "$2" elif [ "$1" == "reject" ] && [ -n "$2" ]; then sudo -u ${cfg.user} ${pythonEnv}/bin/python3 ${./.}/executor.py reject "$2" else echo "Usage:" echo " macha-approve list - Show pending actions" echo " macha-approve discuss - Discuss action number N with Macha (interactive)" echo " macha-approve approve - Approve action number N" echo " macha-approve reject - Reject and remove action number N from queue" fi '') # Tool to run manual check (pkgs.writeScriptBin "macha-check" '' #!${pkgs.bash}/bin/bash sudo -u ${cfg.user} sh -c 'cd /var/lib/macha && CHROMA_ENV_FILE="" ANONYMIZED_TELEMETRY="False" ${macha-autonomous}/bin/macha-autonomous --mode once --autonomy ${cfg.autonomyLevel}' '') # Tool to view logs (pkgs.writeScriptBin "macha-logs" '' #!${pkgs.bash}/bin/bash case "$1" in orchestrator) sudo tail -f /var/lib/macha/orchestrator.log ;; decisions) sudo tail -f /var/lib/macha/decisions.jsonl ;; actions) sudo tail -f /var/lib/macha/actions.jsonl ;; service) journalctl -u macha-autonomous.service -f ;; *) echo "Usage: macha-logs [orchestrator|decisions|actions|service]" ;; esac '') # Tool to send test notification (pkgs.writeScriptBin "macha-notify" '' #!${pkgs.bash}/bin/bash if [ -z "$1" ] || [ -z "$2" ]; then echo "Usage: macha-notify <message> [priority]" echo "Example: macha-notify 'Test' 'This is a test' 5" echo "Priorities: 2 (low), 5 (medium), 8 (high)" exit 1 fi export GOTIFY_URL="${cfg.gotifyUrl}" export GOTIFY_TOKEN="${cfg.gotifyToken}" ${pythonEnv}/bin/python3 ${./.}/notifier.py "$1" "$2" "''${3:-5}" '') # Tool to query config files (pkgs.writeScriptBin "macha-configs" '' #!${pkgs.bash}/bin/bash export PYTHONPATH=${toString ./.} export CHROMA_ENV_FILE="" export ANONYMIZED_TELEMETRY="False" if [ $# -eq 0 ]; then echo "Usage: macha-configs <search-query> [system-name]" echo "Examples:" echo " macha-configs gotify" echo " macha-configs 'journald configuration'" echo " macha-configs ollama macha.coven.systems" exit 1 fi QUERY="$1" SYSTEM="''${2:-}" ${pythonEnv}/bin/python3 -c " from context_db import ContextDatabase import sys db = ContextDatabase() query = sys.argv[1] system = sys.argv[2] if len(sys.argv) > 2 else None print(f'Searching for: {query}') if system: print(f'Filtered to system: {system}') print('='*60) configs = db.query_config_files(query, system=system, n_results=5) if not configs: print('No matching configuration files found.') else: for i, cfg in enumerate(configs, 1): print(f\"\\n{i}. {cfg['path']} (relevance: {cfg['relevance']:.1%})\") print(f\" Category: {cfg['metadata']['category']}\") print(' Preview:') preview = cfg['content'][:300].replace('\\n', '\\n ') print(f' {preview}') if len(cfg['content']) > 300: print(' ... (use macha-configs-read to see full file)') " "$QUERY" "$SYSTEM" '') # Interactive chat tool (runs as invoking user, not as macha-autonomous) (pkgs.writeScriptBin "macha-chat" '' #!${pkgs.bash}/bin/bash export PYTHONPATH=${toString ./.} export CHROMA_ENV_FILE="" export ANONYMIZED_TELEMETRY="False" # Run as the current user, not as macha-autonomous # This allows the chat to execute privileged commands with the user's permissions ${pythonEnv}/bin/python3 ${./.}/chat.py '') # Tool to read full config file (pkgs.writeScriptBin "macha-configs-read" '' #!${pkgs.bash}/bin/bash export PYTHONPATH=${toString ./.} export CHROMA_ENV_FILE="" export ANONYMIZED_TELEMETRY="False" if [ $# -eq 0 ]; then echo "Usage: macha-configs-read <file-path>" echo "Example: macha-configs-read apps/gotify.nix" exit 1 fi ${pythonEnv}/bin/python3 -c " from context_db import ContextDatabase import sys db = ContextDatabase() file_path = sys.argv[1] cfg = db.get_config_file(file_path) if not cfg: print(f'Config file not found: {file_path}') sys.exit(1) print(f'File: {cfg[\"path\"]}') print(f'Category: {cfg[\"metadata\"][\"category\"]}') print('='*60) print(cfg['content']) " "$1" '') # Tool to view system registry (pkgs.writeScriptBin "macha-systems" '' #!${pkgs.bash}/bin/bash export PYTHONPATH=${toString ./.} export CHROMA_ENV_FILE="" export ANONYMIZED_TELEMETRY="False" ${pythonEnv}/bin/python3 -c " from context_db import ContextDatabase import json db = ContextDatabase() systems = db.get_all_systems() print('Registered Systems:') print('='*60) for system in systems: os_type = system.get('os_type', 'unknown').upper() print(f\"\\n{system['hostname']} ({system['type']}) [{os_type}]\") print(f\" Config Repo: {system.get('config_repo') or '(not set)'}\") print(f\" Branch: {system.get('config_branch', 'unknown')}\") if system.get('services'): print(f\" Services: {', '.join(system['services'][:10])}\") if len(system['services']) > 10: print(f\" ... and {len(system['services']) - 10} more\") if system.get('capabilities'): print(f\" Capabilities: {', '.join(system['capabilities'])}\") print('='*60) " '') # Tool to ask Macha questions (pkgs.writeScriptBin "macha-ask" '' #!${pkgs.bash}/bin/bash if [ $# -eq 0 ]; then echo "Usage: macha-ask <your question>" echo "Example: macha-ask Why did you recommend restarting that service?" exit 1 fi sudo -u ${cfg.user} ${pkgs.coreutils}/bin/env CHROMA_ENV_FILE="" ANONYMIZED_TELEMETRY="False" ${pythonEnv}/bin/python3 ${./.}/conversation.py "$@" '') # Issue tracking CLI (pkgs.writeScriptBin "macha-issues" '' #!${pythonEnv}/bin/python3 import sys import os os.environ["CHROMA_ENV_FILE"] = "" os.environ["ANONYMIZED_TELEMETRY"] = "False" sys.path.insert(0, "${./.}") from context_db import ContextDatabase from issue_tracker import IssueTracker from datetime import datetime import json db = ContextDatabase() tracker = IssueTracker(db) def list_issues(show_all=False): """List issues""" if show_all: issues = tracker.list_issues() else: issues = tracker.list_issues(status="open") if not issues: print("No issues found") return print("="*70) print(f"ISSUES: {len(issues)}") print("="*70) for issue in issues: issue_id = issue['issue_id'][:8] age_hours = (datetime.utcnow() - datetime.fromisoformat(issue['created_at'])).total_seconds() / 3600 inv_count = len(issue.get('investigations', [])) action_count = len(issue.get('actions', [])) print(f"\n[{issue_id}] {issue['title']}") print(f" Host: {issue['hostname']}") print(f" Status: {issue['status'].upper()} | Severity: {issue['severity'].upper()}") print(f" Age: {age_hours:.1f}h | Activity: {inv_count} investigations, {action_count} actions") print(f" Source: {issue['source']}") if issue.get('resolution'): print(f" Resolution: {issue['resolution']}") def show_issue(issue_id): """Show detailed issue information""" # Find issue by partial ID all_issues = tracker.list_issues() matching = [i for i in all_issues if i['issue_id'].startswith(issue_id)] if not matching: print(f"Issue {issue_id} not found") return issue = matching[0] full_id = issue['issue_id'] print("="*70) print(f"ISSUE: {issue['title']}") print("="*70) print(f"ID: {full_id}") print(f"Host: {issue['hostname']}") print(f"Status: {issue['status'].upper()}") print(f"Severity: {issue['severity'].upper()}") print(f"Source: {issue['source']}") print(f"Created: {issue['created_at']}") print(f"Updated: {issue['updated_at']}") print(f"\nDescription:\n{issue['description']}") investigations = issue.get('investigations', []) if investigations: print(f"\n{'─'*70}") print(f"INVESTIGATIONS ({len(investigations)}):") for i, inv in enumerate(investigations, 1): print(f"\n [{i}] {inv.get('timestamp', 'N/A')}") print(f" Diagnosis: {inv.get('diagnosis', 'N/A')}") print(f" Commands: {', '.join(inv.get('commands', []))}") print(f" Success: {inv.get('success', False)}") if inv.get('output'): print(f" Output: {inv['output'][:200]}...") actions = issue.get('actions', []) if actions: print(f"\n{'─'*70}") print(f"ACTIONS ({len(actions)}):") for i, action in enumerate(actions, 1): print(f"\n [{i}] {action.get('timestamp', 'N/A')}") print(f" Action: {action.get('proposed_action', 'N/A')}") print(f" Risk: {action.get('risk_level', 'N/A').upper()}") print(f" Commands: {', '.join(action.get('commands', []))}") print(f" Success: {action.get('success', False)}") if issue.get('resolution'): print(f"\n{'─'*70}") print(f"RESOLUTION:") print(f" {issue['resolution']}") print("="*70) def create_issue(description): """Create a new issue manually""" import socket hostname = f"{socket.gethostname()}.coven.systems" issue_id = tracker.create_issue( hostname=hostname, title=description[:100], description=description, severity="medium", source="user-reported" ) print(f"Created issue: {issue_id[:8]}") print(f"Title: {description[:100]}") def resolve_issue(issue_id, resolution="Manually resolved"): """Mark an issue as resolved""" # Find issue by partial ID all_issues = tracker.list_issues() matching = [i for i in all_issues if i['issue_id'].startswith(issue_id)] if not matching: print(f"Issue {issue_id} not found") return full_id = matching[0]['issue_id'] success = tracker.resolve_issue(full_id, resolution) if success: print(f"Resolved issue {issue_id[:8]}") else: print(f"Failed to resolve issue {issue_id}") def close_issue(issue_id): """Archive a resolved issue""" # Find issue by partial ID all_issues = tracker.list_issues() matching = [i for i in all_issues if i['issue_id'].startswith(issue_id)] if not matching: print(f"Issue {issue_id} not found") return full_id = matching[0]['issue_id'] if matching[0]['status'] != 'resolved': print(f"Issue {issue_id} must be resolved before closing") print(f"Use: macha-issues resolve {issue_id}") return success = tracker.close_issue(full_id) if success: print(f"Closed and archived issue {issue_id[:8]}") else: print(f"Failed to close issue {issue_id}") # Main CLI if len(sys.argv) < 2: print("Usage: macha-issues <command> [options]") print("") print("Commands:") print(" list List open issues") print(" list --all List all issues (including resolved/closed)") print(" show <id> Show detailed issue information") print(" create <desc> Create a new issue manually") print(" resolve <id> Mark issue as resolved") print(" close <id> Archive a resolved issue") sys.exit(1) command = sys.argv[1] if command == "list": show_all = "--all" in sys.argv list_issues(show_all) elif command == "show" and len(sys.argv) >= 3: show_issue(sys.argv[2]) elif command == "create" and len(sys.argv) >= 3: description = " ".join(sys.argv[2:]) create_issue(description) elif command == "resolve" and len(sys.argv) >= 3: resolution = " ".join(sys.argv[3:]) if len(sys.argv) > 3 else "Manually resolved" resolve_issue(sys.argv[2], resolution) elif command == "close" and len(sys.argv) >= 3: close_issue(sys.argv[2]) else: print(f"Unknown command: {command}") sys.exit(1) '') # Knowledge base CLI (pkgs.writeScriptBin "macha-knowledge" '' #!${pythonEnv}/bin/python3 import sys import os os.environ["CHROMA_ENV_FILE"] = "" os.environ["ANONYMIZED_TELEMETRY"] = "False" sys.path.insert(0, "${./.}") from context_db import ContextDatabase db = ContextDatabase() def list_topics(category=None): """List all knowledge topics""" topics = db.list_knowledge_topics(category) if not topics: print("No knowledge topics found.") return print(f"{'='*70}") if category: print(f"KNOWLEDGE TOPICS ({category.upper()}):") else: print(f"KNOWLEDGE TOPICS:") print(f"{'='*70}") for topic in topics: print(f" • {topic}") print(f"{'='*70}") def show_topic(topic): """Show all knowledge for a topic""" items = db.get_knowledge_by_topic(topic) if not items: print(f"No knowledge found for topic: {topic}") return print(f"{'='*70}") print(f"KNOWLEDGE: {topic}") print(f"{'='*70}\n") for item in items: print(f"ID: {item['id'][:8]}...") print(f"Category: {item['category']}") print(f"Source: {item['source']}") print(f"Confidence: {item['confidence']}") print(f"Created: {item['created_at']}") print(f"Times Referenced: {item['times_referenced']}") if item.get('tags'): print(f"Tags: {', '.join(item['tags'])}") print(f"\nKnowledge:") print(f" {item['knowledge']}\n") print(f"{'-'*70}\n") def search_knowledge(query, category=None): """Search knowledge base""" items = db.query_knowledge(query, category=category, limit=10) if not items: print(f"No knowledge found matching: {query}") return print(f"{'='*70}") print(f"SEARCH RESULTS: {query}") if category: print(f"Category Filter: {category}") print(f"{'='*70}\n") for i, item in enumerate(items, 1): print(f"[{i}] {item['topic']}") print(f" Category: {item['category']} | Confidence: {item['confidence']}") print(f" {item['knowledge'][:150]}...") print() def add_knowledge(topic, knowledge, category="general"): """Add new knowledge""" kid = db.store_knowledge( topic=topic, knowledge=knowledge, category=category, source="user-provided", confidence="high" ) if kid: print(f"✓ Added knowledge for topic: {topic}") print(f" ID: {kid[:8]}...") else: print(f"✗ Failed to add knowledge") def seed_initial(): """Seed initial knowledge""" print("Seeding initial knowledge from seed_knowledge.py...") exec(open("${./.}/seed_knowledge.py").read()) # Main CLI if len(sys.argv) < 2: print("Usage: macha-knowledge <command> [options]") print("") print("Commands:") print(" list List all knowledge topics") print(" list <category> List topics in category") print(" show <topic> Show all knowledge for a topic") print(" search <query> Search knowledge base") print(" search <query> <cat> Search in specific category") print(" add <topic> <text> Add new knowledge") print(" seed Seed initial knowledge") print("") print("Categories: command, pattern, troubleshooting, performance, general") sys.exit(1) command = sys.argv[1] if command == "list": category = sys.argv[2] if len(sys.argv) >= 3 else None list_topics(category) elif command == "show" and len(sys.argv) >= 3: show_topic(sys.argv[2]) elif command == "search" and len(sys.argv) >= 3: query = sys.argv[2] category = sys.argv[3] if len(sys.argv) >= 4 else None search_knowledge(query, category) elif command == "add" and len(sys.argv) >= 4: topic = sys.argv[2] knowledge = " ".join(sys.argv[3:]) add_knowledge(topic, knowledge) elif command == "seed": seed_initial() else: print(f"Unknown command: {command}") sys.exit(1) '') ]; }; }