Agent-JAE/default-extensions/skill-marketplace.ts
jae 4c2f22bf3e
Some checks are pending
CI / build-check-test (push) Waiting to run
feat: add 16 default extensions for Agent JAE
New extensions shipping with Agent JAE:
- jae-rules: Per-project .jae/rules.md injection
- cost-tracker: Token/cost tracking with budget limits
- project-dna: Auto-analyze project fingerprint
- teach-mode: Pedagogical explanation mode
- bookmarks: File bookmark management
- replay: Session recording and asciicast export
- checkpoints: Git-based undo/time-travel
- pr-review: Autonomous PR diff review
- auto-docs: Auto-generate README and docs
- screenshot-context: Vision analysis for screenshots
- deploy: One-command deploy (Docker/Vercel/rsync/static)
- pair-programming: Real-time file watching with feedback
- dashboard: Terminal HUD with live stats
- swarm: Multi-agent parallel task execution
- skill-marketplace: Search/install/publish skills
- widget-api-demo: TUI widget API demonstration
2026-03-23 20:14:41 +01:00

189 lines
6.2 KiB
TypeScript

import type { ExtensionAPI } from "@jaeswift/jae-coding-agent";
import { existsSync, writeFileSync, mkdirSync, readFileSync, readdirSync } from "node:fs";
import { join } from "node:path";
import { Type } from "@sinclair/typebox";
const SKILLS_API = "https://skills.jaeswift.xyz/api/skills";
interface SkillInfo {
name: string;
description: string;
version: string;
author: string;
}
export default function (pi: ExtensionAPI) {
async function fetchJSON(url: string): Promise<any> {
const result = await pi.exec("curl", ["-s", "-f", url]);
if (result.code !== 0) {
throw new Error(`HTTP request failed: ${result.stderr}`);
}
return JSON.parse(result.stdout);
}
function getSkillsDir(ctx: any): string {
const dir = join(ctx.agentDir, "skills");
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
return dir;
}
function listLocalSkills(ctx: any): string[] {
const dir = getSkillsDir(ctx);
try {
return readdirSync(dir).filter((f) => f.endsWith(".md") || existsSync(join(dir, f, "SKILL.md")));
} catch {
return [];
}
}
// /search-skills command
pi.registerCommand("search-skills", {
description: "Search the JAE skill marketplace: /search-skills <query>",
handler: async (args, ctx) => {
const query = args.trim();
if (!query) {
ctx.ui.notify("Usage: /search-skills <query>", "warning");
return;
}
ctx.ui.notify(`\u{1F50D} Searching skills for: ${query}...`, "info");
try {
const skills = await fetchJSON(`${SKILLS_API}/search?q=${encodeURIComponent(query)}`);
if (!skills || skills.length === 0) {
ctx.ui.notify(`No skills found matching '${query}'.`, "info");
return;
}
const list = skills
.map((s: SkillInfo) => ` - ${s.name} (v${s.version}) by ${s.author}\n ${s.description}`)
.join("\n");
ctx.ui.notify(`\u{1F3EA} Skills matching '${query}':\n${list}`, "info");
} catch (err: any) {
ctx.ui.notify(`Failed to search skills: ${err.message}\nAPI: ${SKILLS_API}`, "error");
}
},
});
// /install-skill command
pi.registerCommand("install-skill", {
description: "Install a skill from the marketplace: /install-skill <name>",
handler: async (args, ctx) => {
const name = args.trim();
if (!name) {
ctx.ui.notify("Usage: /install-skill <name>", "warning");
return;
}
ctx.ui.notify(`\u{1F4E6} Installing skill: ${name}...`, "info");
try {
const skill = await fetchJSON(`${SKILLS_API}/${encodeURIComponent(name)}`);
if (!skill || !skill.content) {
ctx.ui.notify(`Skill '${name}' not found or has no content.`, "error");
return;
}
const skillsDir = getSkillsDir(ctx);
const skillDir = join(skillsDir, name);
if (!existsSync(skillDir)) mkdirSync(skillDir, { recursive: true });
writeFileSync(join(skillDir, "SKILL.md"), skill.content, "utf-8");
// Write any additional files
if (skill.files && typeof skill.files === "object") {
for (const [filename, content] of Object.entries(skill.files)) {
writeFileSync(join(skillDir, filename), content as string, "utf-8");
}
}
ctx.ui.notify(
`\u2705 Skill '${name}' installed to ${skillDir}\nRestart JAE to activate.`,
"info",
);
} catch (err: any) {
ctx.ui.notify(`Failed to install skill: ${err.message}`, "error");
}
},
});
// /publish-skill command
pi.registerCommand("publish-skill", {
description: "Publish a local skill to the marketplace: /publish-skill <name>",
handler: async (args, ctx) => {
const name = args.trim();
if (!name) {
ctx.ui.notify("Usage: /publish-skill <skill-dir-name>", "warning");
return;
}
const skillsDir = getSkillsDir(ctx);
const skillDir = join(skillsDir, name);
const skillMd = join(skillDir, "SKILL.md");
if (!existsSync(skillMd)) {
ctx.ui.notify(`Skill '${name}' not found locally at ${skillDir}`, "error");
return;
}
const content = readFileSync(skillMd, "utf-8");
// Collect extra files
const files: Record<string, string> = {};
try {
for (const f of readdirSync(skillDir)) {
if (f !== "SKILL.md") {
files[f] = readFileSync(join(skillDir, f), "utf-8");
}
}
} catch { /* ignore */ }
ctx.ui.notify(`\u{1F4E4} Publishing skill: ${name}...`, "info");
try {
const payload = JSON.stringify({ name, content, files });
const result = await pi.exec(
"curl",
["-s", "-f", "-X", "POST", "-H", "Content-Type: application/json", "-d", payload, SKILLS_API],
);
if (result.code === 0) {
ctx.ui.notify(`\u2705 Skill '${name}' published to marketplace!`, "info");
} else {
ctx.ui.notify(`Publish failed: ${result.stderr}`, "error");
}
} catch (err: any) {
ctx.ui.notify(`Publish error: ${err.message}`, "error");
}
},
});
// /skills command - list local skills
pi.registerCommand("skills", {
description: "List locally installed skills",
handler: async (_args, ctx) => {
const local = listLocalSkills(ctx);
if (local.length === 0) {
ctx.ui.notify("No skills installed. Use /search-skills and /install-skill to get started.", "info");
return;
}
ctx.ui.notify(`\u{1F9E0} Installed Skills (${local.length}):\n${local.map((s) => ` - ${s}`).join("\n")}`, "info");
},
});
// LLM tool for skill search
pi.registerTool({
name: "search_skills",
label: "Search Skills",
description: "Search the JAE skill marketplace for installable skills",
parameters: Type.Object({
query: Type.String({ description: "Search query for skills" }),
}),
execute: async (args, _ctx) => {
try {
const skills = await fetchJSON(`${SKILLS_API}/search?q=${encodeURIComponent(args.query)}`);
return { output: JSON.stringify(skills, null, 2) };
} catch (err: any) {
return { error: `Skill search failed: ${err.message}` };
}
},
});
}