feat: Add sync/push buttons to ClawHub UI
This commit is contained in:
@@ -56,6 +56,13 @@ export async function GET(req: NextRequest) {
|
|||||||
const agents = ["horus", "amun", "cleo"];
|
const agents = ["horus", "amun", "cleo"];
|
||||||
const installed: Record<string, string[]> = {};
|
const installed: Record<string, string[]> = {};
|
||||||
|
|
||||||
|
// Sync Horus repo first
|
||||||
|
try {
|
||||||
|
await execAsync(`cd ${SKILLS_BASE} && git pull origin main 2>&1`);
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
for (const agent of agents) {
|
||||||
|
|
||||||
for (const agent of agents) {
|
for (const agent of agents) {
|
||||||
const agentPath = path.join(SKILLS_BASE, agent);
|
const agentPath = path.join(SKILLS_BASE, agent);
|
||||||
if (existsSync(agentPath)) {
|
if (existsSync(agentPath)) {
|
||||||
@@ -79,6 +86,24 @@ export async function GET(req: NextRequest) {
|
|||||||
return NextResponse.json({ info: stdout });
|
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 });
|
return NextResponse.json({ error: "Unknown action" }, { status: 400 });
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
const error = e as Error & { stdout?: string; message?: string };
|
const error = e as Error & { stdout?: string; message?: string };
|
||||||
|
|||||||
@@ -23,9 +23,10 @@ export default function ClawHubMarketplace() {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [selectedSkill, setSelectedSkill] = useState<Skill | null>(null);
|
const [selectedSkill, setSelectedSkill] = useState<Skill | null>(null);
|
||||||
const [selectedAgents, setSelectedAgents] = useState<string[]>(["horus"]);
|
const [selectedAgents, setSelectedAgents] = useState<string[]>(["horus"]);
|
||||||
const [action, setAction] = useState<"install" | "delete" | null>(null);
|
const [action, setAction] = useState<"install" | "delete" | "sync" | "push" | null>(null);
|
||||||
const [output, setOutput] = useState("");
|
const [output, setOutput] = useState("");
|
||||||
const [installedSkills, setInstalledSkills] = useState<Record<string, string[]>>({});
|
const [installedSkills, setInstalledSkills] = useState<Record<string, string[]>>({});
|
||||||
|
const [syncing, setSyncing] = useState(false);
|
||||||
|
|
||||||
const fetchInstalled = useCallback(async () => {
|
const fetchInstalled = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -120,6 +121,43 @@ export default function ClawHubMarketplace() {
|
|||||||
setAction(null);
|
setAction(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSync = async () => {
|
||||||
|
setSyncing(true);
|
||||||
|
setAction("sync");
|
||||||
|
setOutput("Syncing with GitHub...\n");
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/clawhub", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ action: "sync" }),
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
setOutput(data.output || data.error || "Sync complete");
|
||||||
|
fetchInstalled();
|
||||||
|
} catch (e) {
|
||||||
|
setOutput("Error: " + String(e));
|
||||||
|
}
|
||||||
|
setAction(null);
|
||||||
|
setSyncing(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePush = async () => {
|
||||||
|
setAction("push");
|
||||||
|
setOutput("Pushing to GitHub...\n");
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/clawhub", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ action: "push" }),
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
setOutput(data.output || data.error || "Push complete");
|
||||||
|
} catch (e) {
|
||||||
|
setOutput("Error: " + String(e));
|
||||||
|
}
|
||||||
|
setAction(null);
|
||||||
|
};
|
||||||
|
|
||||||
const isInstalled = (slug: string) => {
|
const isInstalled = (slug: string) => {
|
||||||
return AGENTS.some((agent) => installedSkills[agent]?.includes(slug));
|
return AGENTS.some((agent) => installedSkills[agent]?.includes(slug));
|
||||||
};
|
};
|
||||||
@@ -138,13 +176,28 @@ export default function ClawHubMarketplace() {
|
|||||||
<div className="bg-slate-800 rounded-xl p-4 mb-6 border border-slate-700">
|
<div className="bg-slate-800 rounded-xl p-4 mb-6 border border-slate-700">
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<h2 className="font-semibold text-lg">Agent Skills Status</h2>
|
<h2 className="font-semibold text-lg">Agent Skills Status</h2>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
onClick={handleSync}
|
||||||
|
disabled={syncing}
|
||||||
|
className="text-sm bg-blue-700 hover:bg-blue-600 disabled:bg-blue-800 px-3 py-1 rounded transition flex items-center gap-1"
|
||||||
|
>
|
||||||
|
{syncing ? "⏳" : "🔄"} Sync
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handlePush}
|
||||||
|
className="text-sm bg-green-700 hover:bg-green-600 px-3 py-1 rounded transition"
|
||||||
|
>
|
||||||
|
⬆️ Push
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={fetchInstalled}
|
onClick={fetchInstalled}
|
||||||
className="text-sm bg-slate-700 hover:bg-slate-600 px-3 py-1 rounded transition"
|
className="text-sm bg-slate-700 hover:bg-slate-600 px-3 py-1 rounded transition"
|
||||||
>
|
>
|
||||||
🔄 Refresh
|
↻ Refresh
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div className="grid grid-cols-3 gap-4">
|
<div className="grid grid-cols-3 gap-4">
|
||||||
{AGENTS.map((agent) => (
|
{AGENTS.map((agent) => (
|
||||||
<div key={agent} className="bg-slate-700 rounded-lg p-3">
|
<div key={agent} className="bg-slate-700 rounded-lg p-3">
|
||||||
|
|||||||
Reference in New Issue
Block a user