import { NextRequest, NextResponse } from "next/server"; import { exec } from "child_process"; import { promisify } from "util"; import { readFile, writeFile, mkdir } from "fs/promises"; import { existsSync } from "fs"; import path from "path"; const execAsync = promisify(exec); const SKILLS_DIR = "/root/.openclaw/workspace/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 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+([^\s]+)\s+\(([0-9.]+)\)$/); if (match) { return { slug: match[1], name: match[2], score: parseFloat(match[3]) }; } const parts = line.trim().split(/\s+/); return { slug: parts[0] || "", name: parts[1] || parts[0] || "", score: parseFloat(parts[parts.length - 1]?.replace(/[()]/g, "") || "0") || 0, }; }); return NextResponse.json({ skills }); } if (action === "popular") { // Get featured/popular - show top skills sorted by score // Use a common query that returns high-quality skills const { stdout } = await execAsync(`clawhub search "productivity automation" 2>&1`); const lines = stdout.trim().split("\n"); const skills = lines .filter((line) => line.includes("(")) .map((line) => { const parts = line.trim().split(/\s+/); return { slug: parts[0] || "", name: parts[1] || parts[0] || "", score: parseFloat(parts[parts.length - 1]?.replace(/[()]/g, "") || "0") || 0, }; }) .sort((a, b) => b.score - a.score) .slice(0, 20); return NextResponse.json({ skills }); } if (action === "info") { // Get skill info using inspect const { stdout } = await execAsync(`clawhub inspect ${slug} 2>&1`); return NextResponse.json({ info: stdout }); } return NextResponse.json({ error: "Unknown action" }, { status: 400 }); } catch (e: unknown) { const error = e as Error & { stdout?: string }; return NextResponse.json({ error: error.message || "Failed", details: error.stdout || "", }, { status: 500 }); } } export async function POST(req: NextRequest) { try { const body = await req.json(); const { action, slug, name } = body; if (action === "install") { const { stdout, stderr } = await execAsync( `cd /root/.openclaw/workspace && clawhub install ${slug} 2>&1` ); return NextResponse.json({ output: stdout || stderr || "Installed successfully" }); } if (action === "analyze") { // Download and analyze skill code const skillPath = path.join(SKILLS_DIR, slug); // Install to temp location for analysis await execAsync(`cd /tmp && rm -rf ${slug} 2>/dev/null; clawhub install ${slug} --dir /tmp/${slug} 2>&1`); // Read the SKILL.md const skillMdPath = path.join("/tmp", slug, "SKILL.md"); let analysis = ""; if (existsSync(skillMdPath)) { const content = await readFile(skillMdPath, "utf-8"); analysis = `## Skill Analysis: ${slug}\n\n`; analysis += `### Purpose\n${content.split("---")[2] || "Analysis unavailable"}\n\n`; analysis += `### Triggers\nWhat activates this skill?\n\n`; analysis += `### Actions\nWhat does it do?\n\n`; analysis += `### Recommendations for Custom Version\n`; analysis += `- Keep the core trigger logic\n`; analysis += `- Customize for Haitham's workflow\n`; analysis += `- Add personal touch\n`; } else { analysis = `Could not analyze ${slug} - skill structure not found`; } return NextResponse.json({ output: analysis }); } if (action === "clone") { // Create a custom version of the skill const skillPath = path.join(SKILLS_DIR, slug); const newSlug = slug.replace(/[^a-z0-9-]/gi, "-") + "-custom"; const newPath = path.join(SKILLS_DIR, newSlug); // Install original await execAsync(`cd /tmp && rm -rf ${slug} 2>/dev/null; clawhub install ${slug} --dir /tmp/${slug} 2>&1`); // Create new version await mkdir(newPath, { recursive: true }); const skillMdPath = path.join("/tmp", slug, "SKILL.md"); const skillMd = existsSync(skillMdPath) ? await readFile(skillMdPath, "utf-8") : `# ${name || slug} Custom\n\nCustom version for Haitham.\n`; // Create customized SKILL.md const customSkillMd = skillMd.replace( /name:[^\n]+\n/, `name: ${name || slug} Custom\n` ).replace( /description:[^\n]+\n/, `description: Custom version built for Haitham's workflow\n` ); await writeFile(path.join(newPath, "SKILL.md"), customSkillMd); // Copy other files if they exist const srcFiles = ["/tmp/script.sh", "/tmp/prompts.md"]; for (const f of srcFiles) { if (existsSync(f)) { await writeFile(path.join(newPath, path.basename(f)), await readFile(f, "utf-8")); } } return NextResponse.json({ output: `Created custom skill at: ${newPath}\nSlug: ${newSlug}\n\nEdit the SKILL.md to customize further!` }); } return NextResponse.json({ error: "Unknown action" }, { status: 400 }); } catch (e: unknown) { const error = e as Error & { stdout?: string; stderr?: string }; return NextResponse.json({ error: error.message || "Failed", details: error.stdout || error.stderr || "", }, { status: 500 }); } }