erosolar-cli
Version:
Unified AI agent framework for the command line - Multi-provider support with schema-driven tools, code intelligence, and transparent reasoning
178 lines • 7.12 kB
JavaScript
export function createSkillTools(options) {
const repository = options.repository;
return [
{
name: 'ListSkills',
description: 'List Claude Code compatible skills discovered in the workspace, ~/.claude/skills, and ~/.erosolar/skills directories.',
parameters: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Optional substring to filter skills by name, namespace, or description.',
},
refresh_cache: {
type: 'boolean',
description: 'When true, force a re-scan of skill directories before listing.',
},
},
},
handler: async (args) => {
if (args && typeof args['refresh_cache'] === 'boolean' && args['refresh_cache']) {
repository.refresh();
}
const query = typeof args?.['query'] === 'string' ? args['query'].trim().toLowerCase() : '';
const skills = repository
.listSkills()
.filter((skill) => skillMatches(skill, query))
.sort((a, b) => a.name.localeCompare(b.name));
if (!skills.length) {
return query
? `No skills matched "${query}". Add SKILL.md files under skills/ or ~/.claude/skills and rerun the command.`
: 'No skills found. Create a skills/ directory with SKILL.md files or import Claude Code plugin skills.';
}
const lines = [];
lines.push(`Discovered ${skills.length} skill${skills.length === 1 ? '' : 's'}:`);
for (const skill of skills) {
lines.push(formatSkillSummary(skill));
}
return lines.join('\n');
},
},
{
name: 'Skill',
description: 'Load a Claude Skill package by name, slug, or path. Returns metadata, documentation body, and optional resource listings.',
parameters: {
type: 'object',
properties: {
skill: {
type: 'string',
description: 'Skill name, slug (kebab-case), namespace-qualified id (e.g. plugin-dev:skill-development), or path to SKILL.md.',
},
sections: {
type: 'array',
description: 'Optional list of sections to include. Defaults to all sections.',
items: {
type: 'string',
enum: ['metadata', 'body', 'references', 'scripts', 'assets'],
},
},
refresh_cache: {
type: 'boolean',
description: 'When true, force a re-scan of skills before loading.',
},
},
required: ['skill'],
},
handler: async (args) => {
if (args && typeof args['refresh_cache'] === 'boolean' && args['refresh_cache']) {
repository.refresh();
}
const identifier = String(args?.['skill'] ?? '').trim();
if (!identifier) {
return 'Skill identifier is required.';
}
const skill = repository.getSkill(identifier);
if (!skill) {
return `Skill "${identifier}" not found. Run ListSkills to inspect available skills.`;
}
const sections = normalizeSections(args?.['sections']);
// Use simplified format for basic Skill type
const output = formatBasicSkillDetail(skill, sections);
return output || `Skill "${skill.name}" has no content.`;
},
},
];
}
function skillMatches(skill, query) {
if (!query) {
return true;
}
const haystack = [
skill.id,
skill.slug,
skill.name,
skill.description,
skill.namespace ?? '',
skill.relativeLocation ?? '',
]
.join(' ')
.toLowerCase();
return haystack.includes(query);
}
function normalizeSections(value) {
if (!Array.isArray(value) || !value.length) {
return new Set(['metadata', 'body', 'references', 'scripts', 'assets']);
}
const normalized = new Set();
for (const entry of value) {
if (typeof entry !== 'string') {
continue;
}
const key = entry.trim().toLowerCase();
if (key === 'metadata' || key === 'body' || key === 'references' || key === 'scripts' || key === 'assets') {
normalized.add(key);
}
}
if (!normalized.size) {
normalized.add('metadata');
normalized.add('body');
}
return normalized;
}
function formatSkillSummary(skill) {
const namespace = skill.namespace ? `${skill.namespace}:` : '';
const label = `${namespace}${skill.slug}`;
const location = skill.relativeLocation ?? skill.location;
const resourceStatus = [
`Body ${skill.hasBody ? '✅' : '—'}`,
`References ${skill.hasReferences ? '✅' : '—'}`,
`Scripts ${skill.hasScripts ? '✅' : '—'}`,
`Assets ${skill.hasAssets ? '✅' : '—'}`,
].join(' | ');
const lines = [
`- ${label} — ${skill.description}`,
` Source: ${skill.sourceLabel} • Path: ${location}`,
` ${resourceStatus}`,
];
return lines.join('\n');
}
/**
* Simplified skill detail formatter for the basic Skill type from skillRepository
*/
function formatBasicSkillDetail(skill, sections) {
const lines = [];
lines.push(`# Skill: ${skill.name}`);
lines.push(`ID: ${skill.id}`);
lines.push('');
if (sections.has('metadata')) {
lines.push('## Metadata');
lines.push(`- Description: ${skill.description}`);
lines.push(`- Category: ${skill.category}`);
lines.push(`- Tags: ${skill.tags.join(', ')}`);
lines.push('');
}
if (sections.has('body')) {
lines.push('## Implementation');
lines.push(`Pattern: ${skill.implementation.pattern}`);
if (skill.implementation.template) {
lines.push('');
lines.push('Template:');
lines.push(skill.implementation.template);
}
lines.push('');
}
if (sections.has('references') && skill.examples.length > 0) {
lines.push('## Examples');
for (const example of skill.examples) {
lines.push(`### ${example.name}`);
lines.push(example.description);
lines.push('```');
lines.push(example.code);
lines.push('```');
lines.push('');
}
}
return lines.join('\n').trim();
}
//# sourceMappingURL=skillTools.js.map