import { NextRequest, NextResponse } from 'next/server' import { existsSync, mkdirSync, writeFileSync, rmSync } from 'node:fs' import { join } from 'node:path' import { requireRole } from '@/lib/auth' import { config } from '@/lib/config' import { isHermesInstalled, isHermesGatewayRunning, scanHermesSessions } from '@/lib/hermes-sessions' import { getHermesTasks } from '@/lib/hermes-tasks' import { getHermesMemory } from '@/lib/hermes-memory' import { logger } from '@/lib/logger' const HERMES_HOME = join(config.homeDir, '.hermes') const HOOK_DIR = join(HERMES_HOME, 'hooks', 'mission-control') export async function GET(request: NextRequest) { const auth = requireRole(request, 'viewer') if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) try { const installed = isHermesInstalled() const gatewayRunning = installed ? isHermesGatewayRunning() : false const hookInstalled = existsSync(join(HOOK_DIR, 'HOOK.yaml')) const activeSessions = installed ? scanHermesSessions(50).filter(s => s.isActive).length : 0 const cronJobCount = installed ? getHermesTasks().cronJobs.length : 0 const memoryEntries = installed ? getHermesMemory().agentMemoryEntries : 0 return NextResponse.json({ installed, gatewayRunning, hookInstalled, activeSessions, cronJobCount, memoryEntries, hookDir: HOOK_DIR, }) } catch (err) { logger.error({ err }, 'Hermes status check failed') return NextResponse.json({ error: 'Failed to check hermes status' }, { status: 500 }) } } export async function POST(request: NextRequest) { const auth = requireRole(request, 'admin') if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) try { const body = await request.json() const { action } = body if (action === 'install-hook') { if (!isHermesInstalled()) { return NextResponse.json({ error: 'Hermes is not installed (~/.hermes/ not found)' }, { status: 400 }) } mkdirSync(HOOK_DIR, { recursive: true }) // Write HOOK.yaml writeFileSync(join(HOOK_DIR, 'HOOK.yaml'), HOOK_YAML, 'utf8') // Write handler.py writeFileSync(join(HOOK_DIR, 'handler.py'), HANDLER_PY, 'utf8') logger.info('Installed Mission Control hook for Hermes Agent') return NextResponse.json({ success: true, message: 'Hook installed', hookDir: HOOK_DIR }) } if (action === 'uninstall-hook') { if (existsSync(HOOK_DIR)) { rmSync(HOOK_DIR, { recursive: true, force: true }) } logger.info('Uninstalled Mission Control hook for Hermes Agent') return NextResponse.json({ success: true, message: 'Hook uninstalled' }) } return NextResponse.json({ error: 'Invalid action. Must be: install-hook, uninstall-hook' }, { status: 400 }) } catch (err: any) { logger.error({ err }, 'Hermes hook management failed') return NextResponse.json({ error: err.message || 'Hook operation failed' }, { status: 500 }) } } // --------------------------------------------------------------------------- // Hook file contents // --------------------------------------------------------------------------- const HOOK_YAML = `name: mission-control description: Reports agent telemetry to Mission Control version: "1.0" events: - agent:start - agent:end - session:start ` const HANDLER_PY = `""" Mission Control hook for Hermes Agent. Reports session telemetry to the MC /api/sessions endpoint. Configuration (via ~/.hermes/.env or environment): MC_URL - Mission Control base URL (default: http://localhost:3000) MC_API_KEY - API key for authentication (optional) """ import os import logging from datetime import datetime, timezone logger = logging.getLogger("hooks.mission-control") MC_URL = os.environ.get("MC_URL", "http://localhost:3000") MC_API_KEY = os.environ.get("MC_API_KEY", "") def _headers(): h = {"Content-Type": "application/json"} if MC_API_KEY: h["X-Api-Key"] = MC_API_KEY return h async def handle_event(event_name: str, payload: dict) -> None: """ Called by the Hermes hook registry on matching events. Fire-and-forget with a short timeout — never blocks the agent. """ try: import httpx except ImportError: logger.debug("httpx not available, skipping MC telemetry") return try: if event_name == "agent:start": await _report_agent_start(payload) elif event_name == "agent:end": await _report_agent_end(payload) elif event_name == "session:start": await _report_session_start(payload) except Exception as exc: logger.debug("MC hook error (%s): %s", event_name, exc) async def _report_agent_start(payload: dict) -> None: import httpx data = { "name": payload.get("agent_name", "hermes"), "role": "Hermes Agent", "status": "active", "source": "hermes-hook", } async with httpx.AsyncClient(timeout=2.0) as client: await client.post(f"{MC_URL}/api/agents", json=data, headers=_headers()) async def _report_agent_end(payload: dict) -> None: import httpx data = { "name": payload.get("agent_name", "hermes"), "status": "idle", "source": "hermes-hook", } async with httpx.AsyncClient(timeout=2.0) as client: await client.post(f"{MC_URL}/api/agents", json=data, headers=_headers()) async def _report_session_start(payload: dict) -> None: import httpx data = { "event": "session:start", "session_id": payload.get("session_id", ""), "source": payload.get("source", "cli"), "timestamp": datetime.now(timezone.utc).isoformat(), } async with httpx.AsyncClient(timeout=2.0) as client: await client.post(f"{MC_URL}/api/hermes/events", json=data, headers=_headers()) ` export const dynamic = 'force-dynamic'