import { NextRequest, NextResponse } from "next/server"; import { exec } from "child_process"; import { promisify } from "util"; import { existsSync } from "fs"; import path from "path"; const execAsync = promisify(exec); const CLAWHUB_BIN = "/usr/bin/clawhub"; const SKILLS_BASE = "/root/.openclaw/workspace/agents-skills"; export async function GET(req: NextRequest) { const { searchParams } = new URL(req.url); const action = searchParams.get("action"); const q = searchParams.get("q") || ""; const slug = searchParams.get("slug") || ""; try { if (action === "search") { const { stdout } = await execAsync(`${CLAWHUB_BIN} search "${q}" 2>&1`); const lines = stdout.trim().split("\n"); const skills = lines .filter((line) => line.includes("(")) .map((line) => { const match = line.trim().match(/^(.+?)\s+(.+?)\s+\(([0-9.]+)\)$/); if (match) { return { slug: match[1], name: match[2], score: parseFloat(match[3]) }; } return null; }) .filter(Boolean); return NextResponse.json({ skills }); } if (action === "popular") { const { stdout } = await execAsync(`${CLAWHUB_BIN} search "productivity automation" 2>&1`); const lines = stdout.trim().split("\n"); const skills = lines .filter((line) => line.includes("(")) .map((line) => { const match = line.trim().match(/^(.+?)\s+(.+?)\s+\(([0-9.]+)\)$/); if (match) { return { slug: match[1], name: match[2], score: parseFloat(match[3]) }; } return null; }) .filter(Boolean) .sort((a: any, b: any) => b.score - a.score) .slice(0, 30); return NextResponse.json({ skills }); } if (action === "list") { // Sync Horus repo first try { await execAsync(`cd ${SKILLS_BASE} && git pull origin main 2>&1`); } catch (e) { // Ignore sync errors } const agents = ["horus", "amun", "cleo"]; const installed: Record = {}; for (const agent of agents) { const agentPath = path.join(SKILLS_BASE, agent); if (existsSync(agentPath)) { try { const { stdout } = await execAsync(`ls -1 ${agentPath} 2>/dev/null`); installed[agent] = stdout.trim().split("\n").filter(Boolean); } catch { installed[agent] = []; } } else { installed[agent] = []; } } return NextResponse.json({ installed }); } if (action === "info") { const { stdout } = await execAsync(`${CLAWHUB_BIN} inspect ${slug} 2>&1`); return NextResponse.json({ info: stdout }); } if (action === "sync") { try { const { stdout, stderr } = await execAsync(`cd ${SKILLS_BASE} && git pull origin main 2>&1`); return NextResponse.json({ output: stdout || "Already up to date", error: stderr }); } catch (e: any) { return NextResponse.json({ output: "Sync failed", error: e.message }); } } if (action === "push") { try { const { stdout, stderr } = await execAsync(`cd ${SKILLS_BASE} && git add -A && git commit -m "Update skills" && git push origin main 2>&1`); return NextResponse.json({ output: stdout || "Pushed", error: stderr }); } catch (e: any) { return NextResponse.json({ output: "Push failed", error: e.message }); } } return NextResponse.json({ error: "Unknown action" }, { status: 400 }); } catch (e: any) { return NextResponse.json({ error: e.message || "Failed", }, { status: 500 }); } } export async function POST(req: NextRequest) { try { const body = await req.json(); const { action, slug, agents = ["horus"], name } = body; if (action === "install") { const results: string[] = []; for (const agent of agents) { const agentPath = path.join(SKILLS_BASE, agent, slug); const agentDir = path.join(SKILLS_BASE, agent); // Ensure agent dir exists try { await execAsync(`mkdir -p ${agentDir}`); } catch {} // Install to temp first const tempPath = `/tmp/clawhub-install-${slug}-${Date.now()}`; try { await execAsync(`${CLAWHUB_BIN} install ${slug} --dir ${tempPath} 2>&1`); // Move to agent folder await execAsync(`mv ${tempPath} ${agentPath} 2>/dev/null || mv ${tempPath}/${slug} ${agentPath} 2>/dev/null`); results.push(`✓ ${agent}: installed ${slug}`); } catch (e: any) { results.push(`✗ ${agent}: failed - ${e.message}`); } } return NextResponse.json({ output: results.join("\n") }); } if (action === "delete") { const results: string[] = []; for (const agent of agents) { const agentPath = path.join(SKILLS_BASE, agent, slug); if (existsSync(agentPath)) { await execAsync(`rm -rf ${agentPath}`); results.push(`✓ ${agent}: removed ${slug}`); } else { results.push(`- ${agent}: ${slug} not found`); } } return NextResponse.json({ output: results.join("\n") }); } return NextResponse.json({ error: "Unknown action" }, { status: 400 }); } catch (e: any) { return NextResponse.json({ error: e.message || "Failed", }, { status: 500 }); } }