Add author auth, forking, tags, and stats tracking
Introduce token-based author authentication (register/verify API), skill forking with EditGate protection, tag metadata on skills, and download/push stats. Enhanced push scripts with token auth and per-skill filtering. Updated UI with stats, tags, and author info on skill cards.
This commit is contained in:
109
dist/server/chunks/skills_BacVQUiS.mjs
vendored
Normal file
109
dist/server/chunks/skills_BacVQUiS.mjs
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import matter from 'gray-matter';
|
||||
|
||||
const SKILLS_DIR = path.resolve(
|
||||
process.env.SKILLS_DIR || "data/skills"
|
||||
);
|
||||
const SLUG_RE = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
||||
const MAX_SLUG_LENGTH = 64;
|
||||
function isValidSlug(slug) {
|
||||
return slug.length >= 2 && slug.length <= MAX_SLUG_LENGTH && SLUG_RE.test(slug);
|
||||
}
|
||||
function skillPath(slug) {
|
||||
return path.join(SKILLS_DIR, `${slug}.md`);
|
||||
}
|
||||
function parseTools(val) {
|
||||
if (Array.isArray(val)) return val.map(String);
|
||||
if (typeof val === "string") return val.split(",").map((t) => t.trim()).filter(Boolean);
|
||||
return [];
|
||||
}
|
||||
function parseSkill(slug, raw) {
|
||||
const { data, content } = matter(raw);
|
||||
return {
|
||||
slug,
|
||||
name: data.name || slug,
|
||||
description: data.description || "",
|
||||
"allowed-tools": parseTools(data["allowed-tools"] ?? data.allowedTools),
|
||||
"argument-hint": data["argument-hint"] || "",
|
||||
model: data.model || "",
|
||||
"user-invocable": data["user-invocable"] !== false,
|
||||
"disable-model-invocation": Boolean(data["disable-model-invocation"]),
|
||||
context: data.context || "",
|
||||
agent: data.agent || "",
|
||||
author: data.author || "",
|
||||
"author-email": data["author-email"] || "",
|
||||
"fork-of": data["fork-of"] || "",
|
||||
tags: parseTools(data.tags),
|
||||
hooks: typeof data.hooks === "object" && data.hooks !== null ? data.hooks : null,
|
||||
content: content.trim(),
|
||||
raw
|
||||
};
|
||||
}
|
||||
async function listSkills() {
|
||||
await fs.mkdir(SKILLS_DIR, { recursive: true });
|
||||
const files = await fs.readdir(SKILLS_DIR);
|
||||
const skills = [];
|
||||
for (const file of files) {
|
||||
if (!file.endsWith(".md")) continue;
|
||||
const slug = file.replace(/\.md$/, "");
|
||||
const raw = await fs.readFile(path.join(SKILLS_DIR, file), "utf-8");
|
||||
const { content: _, raw: __, ...meta } = parseSkill(slug, raw);
|
||||
skills.push(meta);
|
||||
}
|
||||
return skills.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
async function getAllTags() {
|
||||
const all = await listSkills();
|
||||
return [...new Set(all.flatMap((s) => s.tags))].sort();
|
||||
}
|
||||
async function getForksOf(slug) {
|
||||
const all = await listSkills();
|
||||
return all.filter((s) => s["fork-of"] === slug);
|
||||
}
|
||||
async function getSkill(slug) {
|
||||
try {
|
||||
const raw = await fs.readFile(skillPath(slug), "utf-8");
|
||||
return parseSkill(slug, raw);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
async function createSkill(slug, content) {
|
||||
if (!isValidSlug(slug)) {
|
||||
throw new Error(`Invalid slug: ${slug}`);
|
||||
}
|
||||
await fs.mkdir(SKILLS_DIR, { recursive: true });
|
||||
const dest = skillPath(slug);
|
||||
try {
|
||||
await fs.access(dest);
|
||||
throw new Error(`Skill already exists: ${slug}`);
|
||||
} catch (err) {
|
||||
if (err instanceof Error && err.message.startsWith("Skill already exists")) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
await fs.writeFile(dest, content, "utf-8");
|
||||
return parseSkill(slug, content);
|
||||
}
|
||||
async function updateSkill(slug, content) {
|
||||
const dest = skillPath(slug);
|
||||
try {
|
||||
await fs.access(dest);
|
||||
} catch {
|
||||
throw new Error(`Skill not found: ${slug}`);
|
||||
}
|
||||
await fs.writeFile(dest, content, "utf-8");
|
||||
return parseSkill(slug, content);
|
||||
}
|
||||
async function deleteSkill(slug) {
|
||||
const dest = skillPath(slug);
|
||||
try {
|
||||
await fs.access(dest);
|
||||
} catch {
|
||||
throw new Error(`Skill not found: ${slug}`);
|
||||
}
|
||||
await fs.unlink(dest);
|
||||
}
|
||||
|
||||
export { getAllTags as a, getForksOf as b, createSkill as c, deleteSkill as d, getSkill as g, isValidSlug as i, listSkills as l, updateSkill as u };
|
||||
Reference in New Issue
Block a user