179 lines
5.4 KiB
JavaScript
179 lines
5.4 KiB
JavaScript
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const AGENCY_DIR = path.resolve('D:/q/Bailongma/agency-agents-zh');
|
||
|
||
class RoleTemplates {
|
||
constructor() {
|
||
this.roles = new Map(); // roleId -> role definition
|
||
this.byCategory = new Map(); // category -> [roleId, ...]
|
||
this.byKeyword = new Map(); // keyword -> [roleId, ...]
|
||
this.initialized = false;
|
||
}
|
||
|
||
async init() {
|
||
if (this.initialized) return;
|
||
this._scanDirectory(AGENCY_DIR);
|
||
this.initialized = true;
|
||
console.log(`[RoleTemplates] Loaded ${this.roles.size} roles in ${this.byCategory.size} categories`);
|
||
}
|
||
|
||
_scanDirectory(dir, category = null) {
|
||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||
for (const entry of entries) {
|
||
const fullPath = path.join(dir, entry.name);
|
||
if (entry.isDirectory()) {
|
||
const subCategory = category ? `${category}/${entry.name}` : entry.name;
|
||
if (!entry.name.startsWith('.')) {
|
||
this._scanDirectory(fullPath, subCategory);
|
||
}
|
||
} else if (entry.isFile() && entry.name.endsWith('.md') && entry.name !== 'README.md') {
|
||
// Skip non-role files
|
||
const skipFiles = ['CATALOG.md', 'UPSTREAM.md', 'CONTRIBUTING.md', 'LICENSE',
|
||
'AGENT-LIST.md', 'EXECUTIVE-BRIEF.md', 'QUICKSTART.md', 'nexus-strategy.md'];
|
||
if (skipFiles.includes(entry.name)) continue;
|
||
if (entry.name.startsWith('bug') || entry.name.startsWith('feature') || entry.name.startsWith('new_agent') || entry.name.startsWith('PULL_REQUEST')) continue;
|
||
|
||
this._parseRoleFile(fullPath, category);
|
||
}
|
||
}
|
||
}
|
||
|
||
_parseRoleFile(filePath, category) {
|
||
try {
|
||
const content = fs.readFileSync(filePath, 'utf-8');
|
||
const roleId = path.basename(filePath, '.md');
|
||
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
||
|
||
let name = roleId;
|
||
let description = '';
|
||
let emoji = '🤖';
|
||
let color = 'gray';
|
||
let body = content;
|
||
|
||
if (match) {
|
||
const frontMatter = match[1];
|
||
body = match[2].trim();
|
||
const nameMatch = frontMatter.match(/^name:\s*(.+)$/m);
|
||
if (nameMatch) name = nameMatch[1].trim();
|
||
const descMatch = frontMatter.match(/^description:\s*(.+)$/m);
|
||
if (descMatch) description = descMatch[1].trim();
|
||
const emojiMatch = frontMatter.match(/^emoji:\s*(.+)$/m);
|
||
if (emojiMatch) emoji = emojiMatch[1].trim();
|
||
const colorMatch = frontMatter.match(/^color:\s*(.+)$/m);
|
||
if (colorMatch) color = colorMatch[1].trim();
|
||
}
|
||
|
||
const role = {
|
||
id: roleId,
|
||
name,
|
||
description,
|
||
emoji,
|
||
color,
|
||
category: category || 'uncategorized',
|
||
body,
|
||
filePath,
|
||
fullPrompt: body // The full markdown body serves as the system prompt template
|
||
};
|
||
|
||
this.roles.set(roleId, role);
|
||
|
||
// Index by category
|
||
const cat = category || 'uncategorized';
|
||
if (!this.byCategory.has(cat)) this.byCategory.set(cat, []);
|
||
this.byCategory.get(cat).push(roleId);
|
||
|
||
// Index keywords from name and description
|
||
const keywords = [...new Set(
|
||
(name + ' ' + description + ' ' + roleId)
|
||
.toLowerCase()
|
||
.split(/[\s,,、()()\/\\\-_]+/)
|
||
.filter(k => k.length > 1)
|
||
)];
|
||
for (const kw of keywords) {
|
||
if (!this.byKeyword.has(kw)) this.byKeyword.set(kw, new Set());
|
||
this.byKeyword.get(kw).add(roleId);
|
||
}
|
||
} catch (e) {
|
||
console.error(`[RoleTemplates] Error parsing ${filePath}: ${e.message}`);
|
||
}
|
||
}
|
||
|
||
getTemplate(roleId) {
|
||
const role = this.roles.get(roleId);
|
||
if (!role) return null;
|
||
return {
|
||
id: role.id,
|
||
name: role.name,
|
||
description: role.description,
|
||
emoji: role.emoji,
|
||
color: role.color,
|
||
category: role.category,
|
||
prompt: role.fullPrompt
|
||
};
|
||
}
|
||
|
||
searchRoles(query) {
|
||
const q = query.toLowerCase();
|
||
const results = [];
|
||
for (const role of this.roles.values()) {
|
||
if (role.name.toLowerCase().includes(q) ||
|
||
role.description.toLowerCase().includes(q) ||
|
||
role.id.toLowerCase().includes(q) ||
|
||
role.category.toLowerCase().includes(q)) {
|
||
results.push({
|
||
id: role.id,
|
||
name: role.name,
|
||
emoji: role.emoji,
|
||
category: role.category,
|
||
description: role.description
|
||
});
|
||
}
|
||
}
|
||
return results;
|
||
}
|
||
|
||
searchByKeywords(keywords) {
|
||
const matched = new Set();
|
||
for (const kw of keywords.map(k => k.toLowerCase()).filter(k => k.length > 1)) {
|
||
const ids = this.byKeyword.get(kw);
|
||
if (ids) ids.forEach(id => matched.add(id));
|
||
}
|
||
return [...matched].map(id => ({
|
||
id,
|
||
name: this.roles.get(id).name,
|
||
emoji: this.roles.get(id).emoji,
|
||
category: this.roles.get(id).category,
|
||
description: this.roles.get(id).description
|
||
}));
|
||
}
|
||
|
||
getCategories() {
|
||
const result = [];
|
||
for (const [cat, roleIds] of this.byCategory) {
|
||
result.push({
|
||
category: cat,
|
||
count: roleIds.length,
|
||
roles: roleIds.map(id => ({
|
||
id,
|
||
name: this.roles.get(id).name,
|
||
emoji: this.roles.get(id).emoji
|
||
}))
|
||
});
|
||
}
|
||
return result.sort((a, b) => b.count - a.count);
|
||
}
|
||
|
||
getCategoryRoles(category) {
|
||
const ids = this.byCategory.get(category);
|
||
if (!ids) return [];
|
||
return ids.map(id => this.getTemplate(id));
|
||
}
|
||
|
||
getRoleCount() {
|
||
return this.roles.size;
|
||
}
|
||
}
|
||
|
||
module.exports = RoleTemplates;
|