Initial commit: Split Macha autonomous system into separate flake

Macha is now a standalone NixOS flake that can be imported into other
systems. This provides:

- Independent versioning
- Easier reusability
- Cleaner separation of concerns
- Better development workflow

Includes:
- Complete autonomous system code
- NixOS module with full configuration options
- Queue-based architecture with priority system
- Chunked map-reduce for large outputs
- ChromaDB knowledge base
- Tool calling system
- Multi-host SSH management
- Gotify notification integration

All capabilities from DESIGN.md are preserved.
This commit is contained in:
Lily Miller
2025-10-06 14:32:37 -06:00
commit 22ba493d9e
30 changed files with 10306 additions and 0 deletions

847
module.nix Normal file
View File

@@ -0,0 +1,847 @@
{ 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 <N> - Discuss action number N with Macha (interactive)"
echo " macha-approve approve <N> - Approve action number N"
echo " macha-approve reject <N> - 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 <title> <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)
'')
];
};
}