feat: Add analyze and Make My Version buttons

- Analyze shows skill description and preview
- Make My Version creates custom skill without installing from ClawHub
This commit is contained in:
2026-03-24 18:43:42 +01:00
parent 5ece3001a3
commit 2ee9b22cec
2 changed files with 148 additions and 9 deletions
+81 -1
View File
@@ -1,7 +1,8 @@
import { NextRequest, NextResponse } from "next/server";
import { exec } from "child_process";
import { promisify } from "util";
import { existsSync } from "fs";
import { existsSync, readFileSync } from "fs";
import { readFile, writeFile } from "fs/promises";
import path from "path";
const execAsync = promisify(exec);
@@ -82,6 +83,85 @@ export async function GET(req: NextRequest) {
return NextResponse.json({ info: stdout });
}
if (action === "analyze") {
try {
// Install to temp for analysis
const tempPath = `/tmp/clawhub-analyze-${slug}`;
await execAsync(`rm -rf ${tempPath} 2>/dev/null; ${CLAWHUB_BIN} install ${slug} --dir ${tempPath} 2>&1`);
// Read SKILL.md
const skillMdPath = path.join(tempPath, slug, "SKILL.md");
let content = "";
if (existsSync(skillMdPath)) {
content = await readFile(skillMdPath, "utf-8");
} else {
// Try without slug subfolder
const altPath = path.join(tempPath, "SKILL.md");
if (existsSync(altPath)) {
content = await readFile(altPath, "utf-8");
}
}
// Extract description
const descMatch = content.match(/description:\s*([^\n]+)/i);
const desc = descMatch ? descMatch[1].trim() : "No description available";
// Extract triggers
const triggerMatch = content.match(/triggers?:\s*([^\n]+)/i);
const triggers = triggerMatch ? triggerMatch[1].trim() : "General use";
// Clean up
await execAsync(`rm -rf ${tempPath} 2>/dev/null`);
return NextResponse.json({
description: desc,
triggers: triggers,
raw: content.substring(0, 2000)
});
} catch (e: any) {
return NextResponse.json({ error: e.message, description: "Could not analyze skill" });
}
}
if (action === "clone") {
try {
// Install to temp
const tempPath = `/tmp/clawhub-clone-${slug}`;
await execAsync(`rm -rf ${tempPath} 2>/dev/null; ${CLAWHUB_BIN} install ${slug} --dir ${tempPath} 2>&1`);
// Find the skill folder
const entries = await readdir(tempPath);
const skillFolder = entries.find(e => e.includes(slug.replace(/-/g, '')));
const sourcePath = path.join(tempPath, skillFolder || slug);
// Create custom version
const customSlug = `${slug}-custom`;
const destPath = path.join(SKILLS_BASE, "horus", customSlug);
// Copy files
await execAsync(`cp -r ${sourcePath} ${destPath} 2>&1`);
// Modify SKILL.md
const skillMdPath = path.join(destPath, "SKILL.md");
if (existsSync(skillMdPath)) {
let content = await readFile(skillMdPath, "utf-8");
content = content
.replace(/name:\s*([^\n]+)/i, `name: $1 (Custom)`)
.replace(/description:\s*([^\n]+)/i, `description: Custom version for Haitham's workflow`);
await writeFile(skillMdPath, content);
}
// Clean up
await execAsync(`rm -rf ${tempPath} 2>/dev/null`);
return NextResponse.json({
output: `Created custom skill: ${customSlug}\nPath: ${destPath}\n\nEdit SKILL.md to customize further!`
});
} catch (e: any) {
return NextResponse.json({ error: e.message });
}
}
if (action === "sync") {
try {
const { stdout, stderr } = await execAsync(`cd ${SKILLS_BASE} && git pull origin main 2>&1`);