From 48a452b994325a0f978e665a936bad5a83f52002 Mon Sep 17 00:00:00 2001 From: Horus AI Date: Tue, 24 Mar 2026 19:00:30 +0100 Subject: [PATCH] fix: ClawHub - move actions to POST, increase output font size --- .clawhub/lock.json | 6 +- app/api/clawhub/route.ts | 166 ++++++++++----------------- app/mission-control/clawhub/page.tsx | 4 +- 3 files changed, 68 insertions(+), 108 deletions(-) diff --git a/.clawhub/lock.json b/.clawhub/lock.json index fe9380a..519b856 100644 --- a/.clawhub/lock.json +++ b/.clawhub/lock.json @@ -3,7 +3,7 @@ "skills": { "automation-workflows": { "version": "0.1.0", - "installedAt": 1774374565991 + "installedAt": 1774375118226 }, "competitive-intelligence-market-research": { "version": "1.0.0", @@ -16,6 +16,10 @@ "n8n-workflow-automation": { "version": "1.0.0", "installedAt": 1774374686820 + }, + "agentic-workflow-automation": { + "version": "0.1.0", + "installedAt": 1774375021538 } } } diff --git a/app/api/clawhub/route.ts b/app/api/clawhub/route.ts index 0caac8b..519dd8c 100644 --- a/app/api/clawhub/route.ts +++ b/app/api/clawhub/route.ts @@ -1,8 +1,8 @@ import { NextRequest, NextResponse } from "next/server"; import { exec } from "child_process"; import { promisify } from "util"; -import { existsSync, readFileSync } from "fs"; -import { readFile, writeFile } from "fs/promises"; +import { existsSync } from "fs"; +import { readFile, writeFile, readdir } from "fs/promises"; import path from "path"; const execAsync = promisify(exec); @@ -55,9 +55,7 @@ export async function GET(req: NextRequest) { // Sync Horus repo first try { await execAsync(`cd ${SKILLS_BASE} && git pull origin main 2>&1`); - } catch (e) { - // Ignore sync errors - } + } catch {} const agents = ["horus", "amun", "cleo"]; const installed: Record = {}; @@ -83,62 +81,93 @@ export async function GET(req: NextRequest) { return NextResponse.json({ info: stdout }); } + 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); + + try { await execAsync(`mkdir -p ${agentDir}`); } catch {} + + const tempPath = `/tmp/clawhub-install-${slug}-${Date.now()}`; + try { + await execAsync(`${CLAWHUB_BIN} install ${slug} --dir ${tempPath} 2>&1`); + 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") }); + } + if (action === "analyze") { + const tempPath = `/tmp/clawhub-analyze-${slug}-${Date.now()}`; 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"); - } + 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`); - // Now analyze project fit + // Project matching const projectsDir = "/root/.openclaw/workspace/projects"; const projectMatches: string[] = []; try { - const { readdirSync } = await import("fs"); - const { readFile: readFileAsync } = await import("fs/promises"); - - const entries = readdirSync(projectsDir, { withFileTypes: true }); + const entries = await readdir(projectsDir, { withFileTypes: true }); for (const entry of entries) { if (entry.isFile() && (entry.name.endsWith(".md") || entry.name.endsWith(".json"))) { const projectPath = path.join(projectsDir, entry.name); - const projectContent = await readFileAsync(projectPath, "utf-8"); + const projectContent = await readFile(projectPath, "utf-8"); const projectName = entry.name.replace(/\.(md|json)$/, ""); - // Simple keyword matching const skillKeywords = (desc + " " + triggers).toLowerCase(); const projectKeywords = projectContent.toLowerCase(); - // Check for keyword overlaps const overlaps: string[] = []; - const skillWords = skillKeywords.split(/\s+/).filter(w => w.length > 4); + const skillWords = skillKeywords.split(/\s+/).filter((w: string) => w.length > 4); for (const word of skillWords) { - if (projectKeywords.includes(word)) { - overlaps.push(word); - } + if (projectKeywords.includes(word)) overlaps.push(word); } if (overlaps.length >= 2) { @@ -148,36 +177,26 @@ export async function GET(req: NextRequest) { } } catch {} - return NextResponse.json({ - description: desc, - triggers: triggers, - raw: content.substring(0, 2000), - projectMatches - }); + return NextResponse.json({ description: desc, triggers: triggers, raw: content.substring(0, 2000), projectMatches }); } catch (e: any) { return NextResponse.json({ error: e.message, description: "Could not analyze skill" }); } } if (action === "clone") { + const tempPath = `/tmp/clawhub-clone-${slug}-${Date.now()}`; 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 skillFolder = entries.find((e: string) => 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"); @@ -187,12 +206,9 @@ export async function GET(req: NextRequest) { 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!` - }); + 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 }); } @@ -218,66 +234,6 @@ export async function GET(req: NextRequest) { 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 }); + return NextResponse.json({ error: e.message || "Failed" }, { status: 500 }); } } diff --git a/app/mission-control/clawhub/page.tsx b/app/mission-control/clawhub/page.tsx index 6f31693..b419561 100644 --- a/app/mission-control/clawhub/page.tsx +++ b/app/mission-control/clawhub/page.tsx @@ -539,8 +539,8 @@ export default function ClawHubMarketplace() { {/* Output */} {output && (
-

Output

-
+                      

Output

+
                         {output}