feat: Enhanced analyze - shows overview, scripts, sections, better project matching
This commit is contained in:
+1
-1
@@ -19,7 +19,7 @@
|
|||||||
},
|
},
|
||||||
"agentic-workflow-automation": {
|
"agentic-workflow-automation": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"installedAt": 1774375021538
|
"installedAt": 1774385419847
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+66
-12
@@ -132,6 +132,7 @@ export async function POST(req: NextRequest) {
|
|||||||
try {
|
try {
|
||||||
await execAsync(`rm -rf ${tempPath} 2>/dev/null; ${CLAWHUB_BIN} install ${slug} --dir ${tempPath} 2>&1`);
|
await execAsync(`rm -rf ${tempPath} 2>/dev/null; ${CLAWHUB_BIN} install ${slug} --dir ${tempPath} 2>&1`);
|
||||||
|
|
||||||
|
// Find SKILL.md
|
||||||
const skillMdPath = path.join(tempPath, slug, "SKILL.md");
|
const skillMdPath = path.join(tempPath, slug, "SKILL.md");
|
||||||
let content = "";
|
let content = "";
|
||||||
if (existsSync(skillMdPath)) {
|
if (existsSync(skillMdPath)) {
|
||||||
@@ -141,43 +142,96 @@ export async function POST(req: NextRequest) {
|
|||||||
if (existsSync(altPath)) content = await readFile(altPath, "utf-8");
|
if (existsSync(altPath)) content = await readFile(altPath, "utf-8");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract description from frontmatter
|
||||||
const descMatch = content.match(/description:\s*([^\n]+)/i);
|
const descMatch = content.match(/description:\s*([^\n]+)/i);
|
||||||
const desc = descMatch ? descMatch[1].trim() : "No description available";
|
const desc = descMatch ? descMatch[1].trim() : "No description available";
|
||||||
|
|
||||||
|
// Extract triggers
|
||||||
const triggerMatch = content.match(/triggers?:\s*([^\n]+)/i);
|
const triggerMatch = content.match(/triggers?:\s*([^\n]+)/i);
|
||||||
const triggers = triggerMatch ? triggerMatch[1].trim() : "General use";
|
const triggers = triggerMatch ? triggerMatch[1].trim() : "General use";
|
||||||
|
|
||||||
|
// Extract overview section (everything after first ---)
|
||||||
|
const overviewMatch = content.match(/---\n([\s\S]+?)(?=\n##|\n#|$)/i);
|
||||||
|
let overview = "";
|
||||||
|
if (overviewMatch) {
|
||||||
|
overview = overviewMatch[1].trim();
|
||||||
|
// Clean up frontmatter keys
|
||||||
|
overview = overview.replace(/^[a-z]+:/gim, "").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract all ## sections
|
||||||
|
const sections: Record<string, string> = {};
|
||||||
|
const sectionMatches = content.matchAll(/##\s+(.+?)\n([\s\S]*?)(?=##\s+|$)/gi);
|
||||||
|
for (const match of sectionMatches) {
|
||||||
|
const title = match[1].trim();
|
||||||
|
const body = match[2].trim();
|
||||||
|
if (title.toLowerCase() !== "overview" && body) {
|
||||||
|
sections[title] = body.substring(0, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get scripts info
|
||||||
|
const scriptsDir = path.join(tempPath, slug, "scripts");
|
||||||
|
let scripts: string[] = [];
|
||||||
|
if (existsSync(scriptsDir)) {
|
||||||
|
try {
|
||||||
|
const files = await readdir(scriptsDir);
|
||||||
|
scripts = files.filter(f => !f.startsWith("."));
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
await execAsync(`rm -rf ${tempPath} 2>/dev/null`);
|
await execAsync(`rm -rf ${tempPath} 2>/dev/null`);
|
||||||
|
|
||||||
// Project matching
|
// Project matching - more thorough analysis
|
||||||
const projectsDir = "/root/.openclaw/workspace/projects";
|
const projectsDir = "/root/.openclaw/workspace/projects";
|
||||||
const projectMatches: string[] = [];
|
const projectMatches: Array<{name: string; relevance: string; why: string}> = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const entries = await readdir(projectsDir, { withFileTypes: true });
|
const entries = await readdir(projectsDir, { withFileTypes: true });
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
if (entry.isFile() && (entry.name.endsWith(".md") || entry.name.endsWith(".json"))) {
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
||||||
const projectPath = path.join(projectsDir, entry.name);
|
const projectPath = path.join(projectsDir, entry.name);
|
||||||
const projectContent = await readFile(projectPath, "utf-8");
|
const projectContent = await readFile(projectPath, "utf-8");
|
||||||
const projectName = entry.name.replace(/\.(md|json)$/, "");
|
const projectName = entry.name.replace(/\.md$/, "");
|
||||||
|
|
||||||
const skillKeywords = (desc + " " + triggers).toLowerCase();
|
// Check purpose/goals
|
||||||
|
const purposeMatch = projectContent.match(/purpose:?\s*([^\n]+)/i);
|
||||||
|
const purpose = purposeMatch ? purposeMatch[1].trim() : "";
|
||||||
|
|
||||||
|
// Find keyword matches with skill content
|
||||||
|
const skillContent = (desc + " " + triggers + " " + overview).toLowerCase();
|
||||||
const projectKeywords = projectContent.toLowerCase();
|
const projectKeywords = projectContent.toLowerCase();
|
||||||
|
|
||||||
const overlaps: string[] = [];
|
// Look for specific relevance
|
||||||
const skillWords = skillKeywords.split(/\s+/).filter((w: string) => w.length > 4);
|
const relevantKeywords: string[] = [];
|
||||||
for (const word of skillWords) {
|
const keywordsToCheck = ["automation", "workflow", "ai", "agent", "service", "client", "sales", "marketing", "content", "data", "email", "task", "report", "integration", "api", "web", "voice", "chat"];
|
||||||
if (projectKeywords.includes(word)) overlaps.push(word);
|
|
||||||
|
for (const kw of keywordsToCheck) {
|
||||||
|
if (skillContent.includes(kw) && projectKeywords.includes(kw)) {
|
||||||
|
relevantKeywords.push(kw);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (overlaps.length >= 2) {
|
if (relevantKeywords.length >= 2 || (purpose && relevantKeywords.length >= 1)) {
|
||||||
projectMatches.push(`${projectName}: ${overlaps.slice(0, 3).join(", ")}`);
|
const why = relevantKeywords.slice(0, 4).join(", ");
|
||||||
|
projectMatches.push({
|
||||||
|
name: projectName,
|
||||||
|
relevance: `${relevantKeywords.length} keyword matches`,
|
||||||
|
why: `Useful for: ${why}`
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
return NextResponse.json({ description: desc, triggers: triggers, raw: content.substring(0, 2000), projectMatches });
|
return NextResponse.json({
|
||||||
|
description: desc,
|
||||||
|
triggers: triggers,
|
||||||
|
overview: overview.substring(0, 1000),
|
||||||
|
sections,
|
||||||
|
scripts,
|
||||||
|
projectMatches
|
||||||
|
});
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
return NextResponse.json({ error: e.message, description: "Could not analyze skill" });
|
return NextResponse.json({ error: e.message, description: "Could not analyze skill" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,17 +215,45 @@ export default function ClawHubMarketplace() {
|
|||||||
description: data.description,
|
description: data.description,
|
||||||
triggers: data.triggers
|
triggers: data.triggers
|
||||||
});
|
});
|
||||||
let output = `📋 ${selectedSkill.name}\n\n`;
|
|
||||||
output += `Description: ${data.description || "N/A"}\n\n`;
|
let output = `📋 ${selectedSkill.name}\n`;
|
||||||
output += `Triggers: ${data.triggers || "N/A"}\n\n`;
|
output += `${"─".repeat(40)}\n\n`;
|
||||||
if (data.projectMatches && data.projectMatches.length > 0) {
|
output += `💬 ${data.description || "N/A"}\n\n`;
|
||||||
output += `🎯 Project Matches:\n`;
|
output += `🎯 Triggers: ${data.triggers || "N/A"}\n\n`;
|
||||||
data.projectMatches.forEach((m: string) => {
|
|
||||||
output += ` • ${m}\n`;
|
if (data.overview) {
|
||||||
|
output += `📝 Overview:\n${data.overview.substring(0, 500)}${data.overview.length > 500 ? "..." : ""}\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.scripts && data.scripts.length > 0) {
|
||||||
|
output += `🛠️ Scripts: ${data.scripts.join(", ")}\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.sections) {
|
||||||
|
const relevantSections = Object.entries(data.sections).filter(([k]) =>
|
||||||
|
!["overview", "triggers", "description", "guardrails"].includes(k.toLowerCase())
|
||||||
|
);
|
||||||
|
if (relevantSections.length > 0) {
|
||||||
|
output += `📚 Sections:\n`;
|
||||||
|
relevantSections.forEach(([title, body]) => {
|
||||||
|
output += ` ▸ ${title}\n`;
|
||||||
});
|
});
|
||||||
output += `\n`;
|
output += `\n`;
|
||||||
}
|
}
|
||||||
output += (data.raw ? `📄 SKILL.md Preview:\n${data.raw.substring(0, 500)}...` : "");
|
}
|
||||||
|
|
||||||
|
if (data.projectMatches && data.projectMatches.length > 0) {
|
||||||
|
output += `🎯 Project Matches:\n`;
|
||||||
|
output += `${"─".repeat(40)}\n`;
|
||||||
|
data.projectMatches.forEach((m: {name: string; relevance: string; why: string}) => {
|
||||||
|
output += `\n 📦 ${m.name.toUpperCase()}\n`;
|
||||||
|
output += ` ${m.why}\n`;
|
||||||
|
});
|
||||||
|
output += `\n`;
|
||||||
|
} else {
|
||||||
|
output += `❌ No direct project matches found\n`;
|
||||||
|
}
|
||||||
|
|
||||||
setOutput(output);
|
setOutput(output);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setOutput("Error: " + String(e));
|
setOutput("Error: " + String(e));
|
||||||
|
|||||||
@@ -1,4 +1,16 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"id": "eod-1774382402686",
|
||||||
|
"date": "2026-03-24",
|
||||||
|
"completed": [
|
||||||
|
"See Mission Control for details",
|
||||||
|
"🛡️ SECURITY: 10 advisories found today! Check MC."
|
||||||
|
],
|
||||||
|
"progress": {},
|
||||||
|
"council": {},
|
||||||
|
"tomorrow": [],
|
||||||
|
"created_at": "2026-03-24T20:00:02.686Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "eod-1774296002482",
|
"id": "eod-1774296002482",
|
||||||
"date": "2026-03-23",
|
"date": "2026-03-23",
|
||||||
|
|||||||
Reference in New Issue
Block a user