mirror of
https://github.com/f/awesome-chatgpt-prompts.git
synced 2026-02-12 15:52:47 +00:00
271 lines
7.5 KiB
TypeScript
271 lines
7.5 KiB
TypeScript
/**
|
|
* Seed script to import skills from Anthropic's skills repository
|
|
*
|
|
* Usage:
|
|
* npx tsx scripts/seed-skills.ts [skill-name]
|
|
*
|
|
* Examples:
|
|
* npx tsx scripts/seed-skills.ts pdf
|
|
* npx tsx scripts/seed-skills.ts --all
|
|
*/
|
|
|
|
import { PrismaClient } from "@prisma/client";
|
|
import * as fs from "fs";
|
|
import * as path from "path";
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
// File separator uses ASCII control characters (injection-proof):
|
|
// \x1F (Unit Separator, ASCII 31) marks start
|
|
// \x1E (Record Separator, ASCII 30) marks end
|
|
const FILE_SEPARATOR = (filename: string) => `\x1FFILE:${filename}\x1E`;
|
|
|
|
interface SkillMetadata {
|
|
name: string;
|
|
description: string;
|
|
license?: string;
|
|
}
|
|
|
|
/**
|
|
* Parse YAML frontmatter from SKILL.md
|
|
*/
|
|
function parseFrontmatter(content: string): { metadata: SkillMetadata; body: string } {
|
|
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
|
|
if (!frontmatterMatch) {
|
|
return {
|
|
metadata: { name: "Unknown", description: "" },
|
|
body: content,
|
|
};
|
|
}
|
|
|
|
const [, frontmatter, body] = frontmatterMatch;
|
|
const metadata: SkillMetadata = { name: "Unknown", description: "" };
|
|
|
|
// Simple YAML parsing for frontmatter
|
|
frontmatter.split("\n").forEach((line) => {
|
|
const match = line.match(/^(\w+):\s*(.+)$/);
|
|
if (match) {
|
|
const [, key, value] = match;
|
|
if (key === "name") metadata.name = value.trim();
|
|
if (key === "description") metadata.description = value.trim();
|
|
if (key === "license") metadata.license = value.trim();
|
|
}
|
|
});
|
|
|
|
return { metadata, body };
|
|
}
|
|
|
|
/**
|
|
* Recursively read all files from a directory
|
|
*/
|
|
function readSkillFiles(skillDir: string, basePath: string = ""): Array<{ path: string; content: string }> {
|
|
const files: Array<{ path: string; content: string }> = [];
|
|
const entries = fs.readdirSync(skillDir, { withFileTypes: true });
|
|
|
|
for (const entry of entries) {
|
|
const fullPath = path.join(skillDir, entry.name);
|
|
const relativePath = basePath ? `${basePath}/${entry.name}` : entry.name;
|
|
|
|
if (entry.isDirectory()) {
|
|
files.push(...readSkillFiles(fullPath, relativePath));
|
|
} else if (entry.isFile()) {
|
|
// Skip binary files and hidden files
|
|
if (entry.name.startsWith(".")) continue;
|
|
|
|
try {
|
|
const content = fs.readFileSync(fullPath, "utf-8");
|
|
files.push({ path: relativePath, content });
|
|
} catch (e) {
|
|
console.warn(` Skipping binary file: ${relativePath}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
return files;
|
|
}
|
|
|
|
/**
|
|
* Serialize skill files into multi-file format
|
|
*/
|
|
function serializeSkillFiles(files: Array<{ path: string; content: string }>): string {
|
|
// SKILL.md should be first
|
|
const skillMd = files.find((f) => f.path === "SKILL.md");
|
|
const otherFiles = files.filter((f) => f.path !== "SKILL.md");
|
|
|
|
if (!skillMd) {
|
|
throw new Error("SKILL.md not found");
|
|
}
|
|
|
|
let result = skillMd.content;
|
|
|
|
for (const file of otherFiles) {
|
|
result += `\n${FILE_SEPARATOR(file.path)}\n${file.content}`;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Import a single skill into the database
|
|
*/
|
|
async function importSkill(skillDir: string, authorId: string): Promise<void> {
|
|
const skillName = path.basename(skillDir);
|
|
console.log(`\nImporting skill: ${skillName}`);
|
|
|
|
// Read all files
|
|
const files = readSkillFiles(skillDir);
|
|
console.log(` Found ${files.length} files`);
|
|
|
|
// Find and parse SKILL.md
|
|
const skillMdFile = files.find((f) => f.path === "SKILL.md");
|
|
if (!skillMdFile) {
|
|
console.error(` ERROR: SKILL.md not found in ${skillDir}`);
|
|
return;
|
|
}
|
|
|
|
const { metadata } = parseFrontmatter(skillMdFile.content);
|
|
console.log(` Name: ${metadata.name}`);
|
|
console.log(` Description: ${metadata.description.substring(0, 80)}...`);
|
|
|
|
// Serialize all files
|
|
const content = serializeSkillFiles(files);
|
|
|
|
// Check if skill already exists
|
|
const existing = await prisma.prompt.findFirst({
|
|
where: {
|
|
title: metadata.name,
|
|
type: "SKILL",
|
|
authorId,
|
|
},
|
|
});
|
|
|
|
if (existing) {
|
|
console.log(` Skill "${metadata.name}" already exists, updating...`);
|
|
await prisma.prompt.update({
|
|
where: { id: existing.id },
|
|
data: {
|
|
content,
|
|
description: metadata.description,
|
|
},
|
|
});
|
|
} else {
|
|
// Create new skill
|
|
await prisma.prompt.create({
|
|
data: {
|
|
title: metadata.name,
|
|
description: metadata.description,
|
|
content,
|
|
type: "SKILL",
|
|
authorId,
|
|
isPrivate: false,
|
|
},
|
|
});
|
|
console.log(` Created skill: ${metadata.name}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Main function
|
|
*/
|
|
async function main() {
|
|
const args = process.argv.slice(2);
|
|
|
|
if (args.length === 0) {
|
|
console.log("Usage:");
|
|
console.log(" npx tsx scripts/seed-skills.ts <skill-name> - Import a specific skill");
|
|
console.log(" npx tsx scripts/seed-skills.ts --all - Import all skills");
|
|
console.log(" npx tsx scripts/seed-skills.ts --list - List available skills");
|
|
console.log("\nAvailable skills:");
|
|
|
|
const skillsDir = "/tmp/anthropic-skills/skills";
|
|
if (fs.existsSync(skillsDir)) {
|
|
const skills = fs.readdirSync(skillsDir, { withFileTypes: true })
|
|
.filter((d) => d.isDirectory())
|
|
.map((d) => d.name);
|
|
skills.forEach((s) => console.log(` - ${s}`));
|
|
} else {
|
|
console.log(" (Skills repo not found. Clone it first with: git clone https://github.com/anthropics/skills.git /tmp/anthropic-skills)");
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Find or create admin user for importing
|
|
let author = await prisma.user.findFirst({
|
|
where: { role: "ADMIN" },
|
|
});
|
|
|
|
if (!author) {
|
|
console.log("No admin user found. Creating system user...");
|
|
author = await prisma.user.create({
|
|
data: {
|
|
email: "system@prompts.chat",
|
|
username: "system",
|
|
name: "System",
|
|
role: "ADMIN",
|
|
},
|
|
});
|
|
}
|
|
|
|
console.log(`Using author: ${author.username} (${author.id})`);
|
|
|
|
const skillsBaseDir = "/tmp/anthropic-skills/skills";
|
|
|
|
if (!fs.existsSync(skillsBaseDir)) {
|
|
console.error("Skills directory not found. Please clone the repo first:");
|
|
console.error(" git clone https://github.com/anthropics/skills.git /tmp/anthropic-skills");
|
|
return;
|
|
}
|
|
|
|
if (args[0] === "--all") {
|
|
// Import all skills
|
|
const skillDirs = fs.readdirSync(skillsBaseDir, { withFileTypes: true })
|
|
.filter((d) => d.isDirectory())
|
|
.map((d) => path.join(skillsBaseDir, d.name));
|
|
|
|
console.log(`Found ${skillDirs.length} skills to import`);
|
|
|
|
for (const skillDir of skillDirs) {
|
|
try {
|
|
await importSkill(skillDir, author.id);
|
|
} catch (e) {
|
|
console.error(` ERROR importing ${path.basename(skillDir)}:`, e);
|
|
}
|
|
}
|
|
} else if (args[0] === "--list") {
|
|
// List available skills
|
|
const skills = fs.readdirSync(skillsBaseDir, { withFileTypes: true })
|
|
.filter((d) => d.isDirectory())
|
|
.map((d) => d.name);
|
|
|
|
console.log("Available skills:");
|
|
skills.forEach((s) => console.log(` - ${s}`));
|
|
} else {
|
|
// Import specific skill
|
|
const skillDir = path.join(skillsBaseDir, args[0]);
|
|
|
|
if (!fs.existsSync(skillDir)) {
|
|
console.error(`Skill not found: ${args[0]}`);
|
|
console.log("\nAvailable skills:");
|
|
const skills = fs.readdirSync(skillsBaseDir, { withFileTypes: true })
|
|
.filter((d) => d.isDirectory())
|
|
.map((d) => d.name);
|
|
skills.forEach((s) => console.log(` - ${s}`));
|
|
return;
|
|
}
|
|
|
|
await importSkill(skillDir, author.id);
|
|
}
|
|
|
|
console.log("\nDone!");
|
|
}
|
|
|
|
main()
|
|
.catch((e) => {
|
|
console.error(e);
|
|
process.exit(1);
|
|
})
|
|
.finally(async () => {
|
|
await prisma.$disconnect();
|
|
});
|