import chalk from "chalk" import { createInterface } from "readline" import { listSkills, getSkillDetails, installSkill, publishSkill } from "../marketplace/client" import { loadConfig } from "../auth/config" import type { AppConfig } from "../auth/config" export async function marketplaceCommand(action: string, args: string[]): Promise { const config = loadConfig() const apiKey = config.apiKey as string | undefined if (!apiKey) { console.log(chalk.red('Not logged in. Run "llmtune login" first.')) process.exit(1) } switch (action) { case "search": case "list": await searchSkills(config, args[0] ?? "") break case "info": if (!args[0]) { console.log(chalk.yellow("Usage: llmtune skills info ")) break } await showSkillInfo(config, args[0]) break case "install": if (!args[0]) { console.log(chalk.yellow("Usage: llmtune skills install ")) break } await doInstallSkill(config, args[0]) break case "publish": if (!args[0]) { console.log(chalk.yellow("Usage: llmtune skills publish ")) break } await doPublishSkill(config, args[0]) break default: console.log(chalk.yellow(`Unknown action: ${action}`)) console.log(chalk.dim("Available: search, info, install, publish")) } } async function searchSkills(config: AppConfig, query: string): Promise { console.log(chalk.cyan(`\nSearching skills${query ? `: ${query}` : ""}...\n`)) const result = await listSkills(config, { search: query || undefined }) const skills = result.skills if (skills.length === 0) { console.log(chalk.dim("No skills found.")) return } for (const skill of skills) { const trust = chalk.dim(`[${skill.trustLevel}]`) const installs = skill.installs ? chalk.dim(`(${skill.installs} installs)`) : "" console.log(` ${chalk.bold(skill.name)} ${trust} ${installs}`) console.log(` ${chalk.dim(skill.description)}`) if (skill.author) { console.log(` ${chalk.dim(`by ${skill.author}`)}`) } console.log() } console.log( chalk.dim( `${skills.length} of ${result.total} skills shown. Use ${chalk.bold("llmtune skills install ")} to install.`, ), ) } async function showSkillInfo(config: AppConfig, name: string): Promise { try { const skill = await getSkillDetails(config, name) console.log(chalk.bold(`\n${skill.name}`)) console.log(chalk.dim(skill.description)) console.log() console.log(` Trust: ${skill.trustLevel}`) console.log(` Author: ${skill.author ?? "unknown"}`) if (skill.installs !== undefined) console.log(` Installs: ${skill.installs}`) if (skill.rating !== undefined) { const stars = "\u2605".repeat(Math.round(skill.rating)) + "\u2606".repeat(5 - Math.round(skill.rating)) console.log(` Rating: ${stars} (${skill.rating.toFixed(1)})`) } if (skill.allowedTools.length > 0) { console.log(` Tools: ${skill.allowedTools.join(", ")}`) } if (skill.tags && skill.tags.length > 0) { console.log(` Tags: ${skill.tags.join(", ")}`) } console.log() } catch (err: unknown) { const msg = err instanceof Error ? err.message : String(err) console.log(chalk.red(`Error: ${msg}`)) } } async function doInstallSkill(config: AppConfig, name: string): Promise { console.log(chalk.cyan(`Installing skill: ${name}...`)) try { const skillPath = await installSkill(config, name) console.log(chalk.green(`\nSkill "${name}" installed to: ${skillPath}`)) console.log(chalk.dim(`Run /${name} in a chat session to use it.`)) } catch (err: unknown) { const msg = err instanceof Error ? err.message : String(err) console.log(chalk.red(`\nFailed to install: ${msg}`)) } } async function doPublishSkill(config: AppConfig, skillDir: string): Promise { const fs = await import("fs/promises") const path = await import("path") const mdPath = path.join(skillDir, "SKILL.md") let content: string try { content = await fs.readFile(mdPath, "utf-8") } catch { console.log(chalk.red(`No SKILL.md found in: ${skillDir}`)) return } const name = path.basename(path.resolve(skillDir)) const description = content.split("\n").find((l) => l.trim() && !l.trim().startsWith("#") && !l.trim().startsWith("---"))?.trim() ?? `Skill: ${name}` console.log(chalk.cyan(`\nPublishing skill: ${name}`)) console.log(chalk.dim(`Path: ${skillDir}`)) console.log(chalk.dim(`Description: ${description.slice(0, 80)}`)) console.log() const rl = createInterface({ input: process.stdin, output: process.stdout }) const answer = await new Promise((resolve) => { rl.question(chalk.yellow("Publish this skill? [y/N] "), (ans) => { rl.close() resolve(ans.trim().toLowerCase()) }) }) if (answer !== "y" && answer !== "yes") { console.log(chalk.dim("Cancelled.")) return } try { const result = await publishSkill(config, { name, description, content, trustLevel: "community", allowedTools: [], }) if (result.published) { console.log(chalk.green(`\nSkill "${result.skillName}" published successfully!`)) console.log(chalk.dim("It will appear in the marketplace after review.")) } else { console.log(chalk.red(`\nFailed to publish skill "${name}".`)) } } catch (err: unknown) { const msg = err instanceof Error ? err.message : String(err) console.log(chalk.red(`\nFailed to publish: ${msg}`)) } } export async function listSkillsCommand(): Promise { await marketplaceCommand("list", []) } export async function installSkillCommand(name: string): Promise { await marketplaceCommand("install", [name]) } export async function publishSkillCommand(dir: string): Promise { await marketplaceCommand("publish", [dir]) } export async function signSkillCommand(dir: string): Promise { const { signSkill, generateKeyPair } = await import("../skills/signing/signer") const keys = generateKeyPair() const result = signSkill(dir, keys.privateKey, keys.publicKey) console.log(chalk.green(`\nSkill signed: ${dir}`)) console.log(chalk.dim(`Signature: ${result.signature}`)) console.log(chalk.dim(`Public key: ${result.publicKey}`)) console.log(chalk.yellow("\nSave your private key securely. You'll need it to sign future versions.")) }