Phase 19: Unified CLI entry point (maxclaw.py)

- Single 'maxclaw' command for all functionality
- Subcommands: chat, server, tui, config, analytics, dashboard, version
- Nested config subcommands: list, add, remove, use, active, test, export, import, search
- Updated README with unified CLI documentation
- Updated Dockerfile to use maxclaw.py
- Added .gitignore for __pycache__
This commit is contained in:
truetype committed 2026-03-21 08:03:47 +01:00
1 parent 3e986d83f1
commit 0612b290d5
4 files changed
+594 -4

No files matched your search

+1
View File
@@ -0,0 +1 @@
__pycache__/
+1 -1
View File
@@ -33,4 +33,4 @@ ENV MODEL_NAME=gemma2:9b
ENV OLLAMA_BASE_URL=http://host.docker.internal:11434
# Default command
CMD ["python", "main.py"]
CMD ["python", "maxclaw.py"]
+41 -3
View File
@@ -204,9 +204,45 @@ L'interfaccia web include:
## Utilizzo
### Utilizzo Locale
### CLI Unificata (maxclaw)
Esegui l'agente:
Tutte le funzionalità sono accessibili tramite un unico comando `maxclaw`:
```bash
# Avvia l'agent (chat interattiva)
maxclaw
maxclaw chat
# Avvia web server
maxclaw server
maxclaw server --port 3000
# Gestione configurazioni
maxclaw config list # Lista configurazioni
maxclaw config add -n ollama -p ollama -m gemma2:9b -u http://192.168.0.50:11434
maxclaw config use ollama # Attiva configurazione
maxclaw config active # Mostra configurazione attiva
maxclaw config test # Testa connessione
maxclaw config export -f json -o backup
maxclaw config import backup.json
maxclaw config search ollama # Cerca configurazioni
# TUI interattiva
maxclaw tui
# Analytics e monitoraggio
maxclaw analytics # Riepilogo rapido
maxclaw analytics --report # Report completo
maxclaw dashboard # Dashboard performance
# Info
maxclaw version
maxclaw --help
```
### Utilizzo Locale (alternativo)
Esegui l'agente direttamente:
```bash
python main.py
```
@@ -283,7 +319,7 @@ Ollama URL: http://192.168.0.50:11434
docker-compose up -d
# Accedi al terminale
docker exec -it maxclaw-agent python main.py
docker exec -it maxclaw-agent maxclaw
# Oppure esegui direttamente
docker run -it --rm maxclaw-agent
@@ -293,6 +329,8 @@ docker run -it --rm maxclaw-agent
Avvia il server API:
```bash
maxclaw server
# oppure
python api_server.py
```
Executable
+551
View File
@@ -0,0 +1,551 @@
#!/usr/bin/env python3
"""
MaxClaw Agent - Unified CLI Entry Point
Usage:
maxclaw → avvia l'agent (chat interattiva)
maxclaw chat → avvia l'agent
maxclaw server [--port N] → avvia web server
maxclaw tui → apre TUI configurazione
maxclaw config list → lista configurazioni
maxclaw config add → aggiungi configurazione
maxclaw config remove NAME → rimuovi configurazione
maxclaw config use NAME → attiva configurazione
maxclaw config active → mostra configurazione attiva
maxclaw config test [NAME] → testa connessione
maxclaw config export → esporta configurazioni
maxclaw config import FILE → importa configurazioni
maxclaw config search Q → cerca configurazioni
maxclaw analytics → mostra analytics
maxclaw dashboard → mostra dashboard performance
maxclaw version → mostra versione
"""
import sys
import os
import argparse
# Add current directory to path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
VERSION = "1.0.0"
def cmd_chat(args):
"""Avvia l'agent in modalità chat interattiva"""
from main import create_maxclaw_agent, process_command
from langchain_core.messages import HumanMessage
print("🚀 Inizializzo MaxClaw Agent...")
try:
agent = create_maxclaw_agent()
print("✅ Agent inizializzato con successo!")
print("\n🔧 Strumenti disponibili:")
print(" • Provider tools: list models, test connections, set defaults")
print(" • Proxmox tools: manage VMs, snapshots, resources")
print(" • Standard tools: planning, filesystem, shell, subagents")
print("\n💬 Puoi interagire con l'agent.")
print("📌 Comandi speciali (prefissati con /):")
print(" /help - Mostra i comandi disponibili")
print(" /status - Mostra lo stato del sistema")
print(" /config - Gestisci le configurazioni")
print(" /exit - Esci dall'agente")
print("\n💡 Per chattare, scrivi semplicemente il tuo messaggio.")
while True:
try:
user_input = input("\n👤 You: ").strip()
except (EOFError, KeyboardInterrupt):
print("\n👋 Arrivederci!")
break
if not user_input:
continue
# Check if it's a command (starts with /)
if process_command(user_input):
continue
# Otherwise, treat it as a chat message
try:
result = agent.invoke({"messages": [HumanMessage(content=user_input)]})
ai_message = result["messages"][-1]
print(f"\n🤖 MaxClaw: {ai_message.content}")
except Exception as e:
print(f"\n❌ Errore: {str(e)}")
except Exception as e:
print(f"❌ Inizializzazione fallita: {str(e)}")
sys.exit(1)
def cmd_server(args):
"""Avvia il web server"""
import uvicorn
from api_server import app
host = args.host or "0.0.0.0"
port = args.port or 8000
print(f"🌐 Avvio MaxClaw Web Server su http://{host}:{port}")
print(f"📊 Dashboard: http://{host}:{port}/monitoring")
print(f"🛒 Marketplace: http://{host}:{port}/marketplace")
print(f"📡 API Docs: http://{host}:{port}/docs")
print("Premi Ctrl+C per fermare il server\n")
uvicorn.run(app, host=host, port=port)
def cmd_tui(args):
"""Apre la TUI per gestione configurazioni"""
from config_tui import main as tui_main
tui_main()
def cmd_config_list(args):
"""Lista tutte le configurazioni"""
from config_cli import print_configurations
from config_manager import config_manager
configs = config_manager.get_configurations()
if args.search:
from config_cli import filter_configurations
configs = filter_configurations(configs, args.search)
print_configurations(configs, title=f"Configurazioni trovate per '{args.search}'")
else:
print_configurations(configs)
def cmd_config_add(args):
"""Aggiunge una nuova configurazione"""
from config_manager import config_manager
if args.name and args.provider:
# Modalità non-interattiva
config_data = {"provider": args.provider}
if args.model:
config_data["model_name"] = args.model
else:
# Default model per provider
defaults = {
"openai": "gpt-4o",
"ollama": "gemma2:9b",
"openrouter": "anthropic/claude-3.5-sonnet",
"anthropic": "claude-3-5-sonnet-20241022",
"google": "gemini-1.5-pro"
}
config_data["model_name"] = defaults.get(args.provider, "gpt-4o")
if args.api_key:
config_data["api_key"] = args.api_key
if args.base_url:
config_data["base_url"] = args.base_url
try:
config_manager.add_configuration(args.name, config_data)
print(f"✅ Configurazione '{args.name}' aggiunta con successo!")
if args.activate:
config_manager.set_active_configuration(args.name)
print(f"✅ Configurazione '{args.name}' attivata!")
except Exception as e:
print(f"❌ Errore: {e}")
sys.exit(1)
else:
# Modalità interattiva
from config_cli import add_configuration
add_configuration()
def cmd_config_remove(args):
"""Rimuove una configurazione"""
from config_manager import config_manager
name = args.name
if not name:
name = input("Nome configurazione da rimuovere: ").strip()
if not name:
print("❌ Nome richiesto.")
sys.exit(1)
try:
config_manager.remove_configuration(name)
print(f"✅ Configurazione '{name}' rimossa!")
except Exception as e:
print(f"❌ Errore: {e}")
sys.exit(1)
def cmd_config_use(args):
"""Attiva una configurazione"""
from config_manager import config_manager
try:
config_manager.set_active_configuration(args.name)
print(f"✅ Configurazione '{args.name}' attivata!")
except Exception as e:
print(f"❌ Errore: {e}")
sys.exit(1)
def cmd_config_active(args):
"""Mostra la configurazione attiva"""
from config_manager import config_manager
active = config_manager.get_active_configuration()
if active:
config = config_manager.get_configuration(active)
print(f"\n📌 Configurazione attiva: {active}")
print(f" Provider: {config.get('provider', 'unknown')}")
print(f" Model: {config.get('model_name', 'unknown')}")
if config.get('base_url'):
print(f" Base URL: {config.get('base_url')}")
else:
print("⚠️ Nessuna configurazione attiva.")
def cmd_config_test(args):
"""Testa una configurazione"""
from config_manager import config_manager
name = args.name or config_manager.get_active_configuration()
if not name:
print("❌ Nessuna configurazione specificata o attiva.")
sys.exit(1)
config = config_manager.get_configuration(name)
if not config:
print(f"❌ Configurazione '{name}' non trovata.")
sys.exit(1)
print(f"🔍 Test connessione per '{name}'...")
provider = config.get("provider", "")
try:
if provider == "ollama":
import requests
base_url = config.get("base_url", "http://localhost:11434")
resp = requests.get(f"{base_url}/api/tags", timeout=10)
if resp.status_code == 200:
models = resp.json().get("models", [])
print(f"✅ Ollama connesso! {len(models)} modelli disponibili.")
for m in models[:5]:
print(f" • {m['name']}")
else:
print(f"❌ Errore: HTTP {resp.status_code}")
elif provider in ("openai", "openrouter", "anthropic"):
api_key = config.get("api_key", "")
if not api_key:
print("❌ API key non configurata.")
sys.exit(1)
print("✅ API key presente (test completo richiede una chiamata API)")
elif provider == "google":
api_key = config.get("api_key", "")
if not api_key:
print("❌ API key non configurata.")
sys.exit(1)
print("✅ API key presente (test completo richiede una chiamata API)")
else:
print(f"⚠️ Test non supportato per provider '{provider}'")
except Exception as e:
print(f"❌ Errore connessione: {e}")
sys.exit(1)
def cmd_config_export(args):
"""Esporta configurazioni"""
from config_cli import export_configurations
if args.format and args.output:
import json
import yaml
from pathlib import Path
from config_manager import config_manager
configs = config_manager.get_configurations()
if not configs:
print("❌ Nessuna configurazione da esportare.")
return
try:
if args.format == 'json':
export_file = Path(f"{args.output}.json")
with open(export_file, 'w') as f:
json.dump(configs, f, indent=2)
else:
export_file = Path(f"{args.output}.yaml")
with open(export_file, 'w') as f:
yaml.dump(configs, f, default_flow_style=False)
print(f"✅ Configurazioni esportate in {export_file}")
except Exception as e:
print(f"❌ Errore: {e}")
sys.exit(1)
else:
export_configurations()
def cmd_config_import(args):
"""Importa configurazioni"""
if args.file:
import json
import yaml
from pathlib import Path
from config_manager import config_manager
file_path = Path(args.file)
if not file_path.exists():
print(f"❌ File '{args.file}' non trovato.")
sys.exit(1)
try:
with open(file_path, 'r') as f:
if args.file.endswith('.json'):
imported = json.load(f)
elif args.file.endswith(('.yaml', '.yml')):
imported = yaml.safe_load(f)
else:
print("❌ Formato non supportato. Usa .json o .yaml")
sys.exit(1)
if not isinstance(imported, dict):
print("❌ Formato configurazione non valido.")
sys.exit(1)
count = 0
for name, config in imported.items():
try:
config_manager.add_configuration(name, config)
count += 1
print(f"✅ Importata: {name}")
except Exception as e:
print(f"⚠️ '{name}': {e}")
print(f"\n✅ {count} configurazioni importate.")
except Exception as e:
print(f"❌ Errore: {e}")
sys.exit(1)
else:
from config_cli import import_configurations
import_configurations()
def cmd_config_search(args):
"""Cerca configurazioni"""
from config_cli import print_configurations, filter_configurations
from config_manager import config_manager
configs = config_manager.get_configurations()
filtered = filter_configurations(configs, args.query)
print_configurations(filtered, title=f"Risultati per '{args.query}'")
def cmd_analytics(args):
"""Mostra analytics"""
from analytics import AnalyticsEngine
engine = AnalyticsEngine()
if args.report:
report = engine.get_detailed_report()
print(report)
elif args.clear:
engine.clear_old_data()
print("✅ Dati analytics cancellati.")
else:
# Mostra riepilogo rapido
summary = engine.get_usage_summary()
insights = engine.get_insights()
print("\n📊 MaxClaw Analytics")
print("=" * 40)
if summary.get("total_conversations", 0) > 0:
print(f"\n💬 Conversazioni totali: {summary['total_conversations']}")
print(f"📝 Messaggi totali: {summary.get('total_messages', 0)}")
print(f"⏱️ Tempo medio risposta: {summary.get('avg_response_time', 0):.2f}s")
if summary.get("provider_usage"):
print("\n🔌 Utilizzo provider:")
for provider, count in summary["provider_usage"].items():
print(f" • {provider}: {count} volte")
if summary.get("tool_usage"):
print("\n🔧 Utilizzo strumenti:")
for tool, count in sorted(summary["tool_usage"].items(), key=lambda x: -x[1])[:5]:
print(f" • {tool}: {count} volte")
else:
print("\n📭 Nessun dato analytics ancora.")
print(" Usa l'agent per generare dati!")
if insights:
print("\n💡 Insights:")
for insight in insights[:3]:
print(f" • {insight}")
print(f"\n📁 Dati salvati in: {engine.data_dir}")
print(" Usa 'maxclaw analytics --report' per report completo")
def cmd_dashboard(args):
"""Mostra dashboard performance"""
from dashboard import PerformanceDashboard
dashboard = PerformanceDashboard()
if args.clear:
dashboard.clear_old_data()
print("✅ Dati performance cancellati.")
else:
report = dashboard.get_summary()
print("\n📈 MaxClaw Performance Dashboard")
print("=" * 40)
if report.get("total_requests", 0) > 0:
print(f"\n📊 Richieste totali: {report['total_requests']}")
print(f"⏱️ Tempo medio risposta: {report.get('avg_response_time', 0):.2f}s")
print(f"✅ Tasso successo: {report.get('success_rate', 0):.1f}%")
if report.get("slowest_operations"):
print("\n🐌 Operazioni più lente:")
for op in report["slowest_operations"][:3]:
print(f" • {op['name']}: {op['time']:.2f}s")
if report.get("memory_usage"):
mem = report["memory_usage"]
print(f"\n💾 Memoria: {mem.get('current', 0):.1f}MB (picco: {mem.get('peak', 0):.1f}MB)")
else:
print("\n📭 Nessun dato performance ancora.")
print(f"\n📁 Dati salvati in: {dashboard.data_dir}")
def cmd_version(args):
"""Mostra versione"""
print(f"MaxClaw Agent v{VERSION}")
print("Agent harness basato su LangChain Deep Agents")
print("https://github.com/truetype/maxclaw_agent")
def main():
parser = argparse.ArgumentParser(
prog="maxclaw",
description="🚀 MaxClaw Agent - AI Agent harness configurabile",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Esempi:
maxclaw Avvia l'agent (chat interattiva)
maxclaw server --port 3000 Avvia web server su porta 3000
maxclaw config list Lista tutte le configurazioni
maxclaw config add -n ollama -p ollama -m gemma2:9b
maxclaw config use ollama Attiva configurazione 'ollama'
maxclaw config test Testa configurazione attiva
maxclaw analytics Mostra riepilogo analytics
maxclaw analytics --report Genera report completo
"""
)
parser.add_argument("--version", "-v", action="version", version=f"maxclaw {VERSION}")
subparsers = parser.add_subparsers(dest="command", help="Comandi disponibili")
# === CHAT (default) ===
chat_parser = subparsers.add_parser("chat", help="Avvia l'agent in modalità chat")
chat_parser.set_defaults(func=cmd_chat)
# === SERVER ===
server_parser = subparsers.add_parser("server", help="Avvia il web server")
server_parser.add_argument("--port", "-p", type=int, default=8000, help="Porta del server (default: 8000)")
server_parser.add_argument("--host", "-H", default="0.0.0.0", help="Host del server (default: 0.0.0.0)")
server_parser.set_defaults(func=cmd_server)
# === TUI ===
tui_parser = subparsers.add_parser("tui", help="Apre TUI per gestione configurazioni")
tui_parser.set_defaults(func=cmd_tui)
# === CONFIG (nested subcommands) ===
config_parser = subparsers.add_parser("config", help="Gestione configurazioni")
config_subparsers = config_parser.add_subparsers(dest="config_command", help="Sottocomandi config")
# config list
cl = config_subparsers.add_parser("list", help="Lista configurazioni")
cl.add_argument("--search", "-s", help="Filtra per termine di ricerca")
cl.set_defaults(func=cmd_config_list)
# config add
ca = config_subparsers.add_parser("add", help="Aggiungi configurazione")
ca.add_argument("--name", "-n", help="Nome configurazione")
ca.add_argument("--provider", "-p", choices=["openai", "ollama", "openrouter", "anthropic", "google"], help="Provider")
ca.add_argument("--model", "-m", help="Nome modello")
ca.add_argument("--api-key", "-k", help="API key")
ca.add_argument("--base-url", "-u", help="Base URL")
ca.add_argument("--activate", "-a", action="store_true", help="Attiva dopo la creazione")
ca.set_defaults(func=cmd_config_add)
# config remove
cr = config_subparsers.add_parser("remove", help="Rimuovi configurazione")
cr.add_argument("name", help="Nome configurazione da rimuovere")
cr.set_defaults(func=cmd_config_remove)
# config use
cu = config_subparsers.add_parser("use", help="Attiva configurazione")
cu.add_argument("name", help="Nome configurazione da attivare")
cu.set_defaults(func=cmd_config_use)
# config active
cv = config_subparsers.add_parser("active", help="Mostra configurazione attiva")
cv.set_defaults(func=cmd_config_active)
# config test
ct = config_subparsers.add_parser("test", help="Testa connessione configurazione")
ct.add_argument("name", nargs="?", help="Nome configurazione (default: attiva)")
ct.set_defaults(func=cmd_config_test)
# config export
ce = config_subparsers.add_parser("export", help="Esporta configurazioni")
ce.add_argument("--format", "-f", choices=["json", "yaml"], help="Formato export")
ce.add_argument("--output", "-o", help="Nome file output (senza estensione)")
ce.set_defaults(func=cmd_config_export)
# config import
ci = config_subparsers.add_parser("import", help="Importa configurazioni")
ci.add_argument("file", nargs="?", help="File da importare (.json o .yaml)")
ci.set_defaults(func=cmd_config_import)
# config search
cs = config_subparsers.add_parser("search", help="Cerca configurazioni")
cs.add_argument("query", help="Termine di ricerca")
cs.set_defaults(func=cmd_config_search)
# === ANALYTICS ===
analytics_parser = subparsers.add_parser("analytics", help="Mostra analytics")
analytics_parser.add_argument("--report", "-r", action="store_true", help="Genera report completo")
analytics_parser.add_argument("--clear", "-c", action="store_true", help="Cancella dati analytics")
analytics_parser.set_defaults(func=cmd_analytics)
# === DASHBOARD ===
dashboard_parser = subparsers.add_parser("dashboard", help="Mostra dashboard performance")
dashboard_parser.add_argument("--clear", "-c", action="store_true", help="Cancella dati performance")
dashboard_parser.set_defaults(func=cmd_dashboard)
# === VERSION ===
version_parser = subparsers.add_parser("version", help="Mostra versione")
version_parser.set_defaults(func=cmd_version)
# Parse arguments
args = parser.parse_args()
# Se nessun comando, avvia chat (default)
if not args.command:
args.func = cmd_chat
args.func(args)
return
# Esegui il comando
if hasattr(args, 'func'):
args.func(args)
else:
parser.print_help()
if __name__ == "__main__":
main()