@magnolia/skill-loader
v0.1.3
Published
A Node.js library for loading, filtering, and managing AI prompt skills
Maintainers
Readme
@magnolia/skill-loader
A Node.js library for loading, filtering, and managing AI prompt "skills" for use with LLMs.
Overview
Skills are structured prompts with metadata that can be conditionally included based on context (target platform, version constraints, detected features, etc.). This library provides a flexible, type-safe way to manage these prompts across different tools and applications.
Installation
npm install @magnolia/skill-loaderQuick Start
Using SkillLoader (Low-level API)
import { SkillLoader } from '@magnolia/skill-loader';
// Create a loader with a directory of skill files
const loader = new SkillLoader()
.addDirectory('./skills');
// Load skills filtered by context
const skills = await loader.load({
target: 'freemarker',
version: '6.3.0',
detectedFieldTypes: ['textField', 'damLinkField'],
});
// Get combined prompt content
const prompt = await loader.getPrompt({
target: 'freemarker',
});Using SkillManager (High-level API with Priority Management)
import { SkillManager } from '@magnolia/skill-loader';
// Create a manager with priority-based directory loading
const manager = new SkillManager({
directories: [
{ path: './plugin-skills', priority: 1 }, // Lowest priority
{ path: '~/.app/skills', priority: 10 }, // Medium priority
{ path: './project/skills', priority: 100 }, // Highest priority
],
cacheTtl: 60000, // 1 minute cache
});
// Register additional directories dynamically
manager.registerSkillDirectory('./custom-skills', 50);
// Load and use skills
const prompt = await manager.getPrompt({ target: 'freemarker' });Skill File Format
Skills are Markdown files with YAML frontmatter:
---
name: my-skill
description: A helpful skill for FreeMarker templates
targets: [freemarker]
version: ">=6.2"
tags: [ftl, generation]
requires: [damLinkField]
priority: 100
---
# My Skill
Your prompt content here...Frontmatter Fields
| Field | Type | Description |
|---------------|----------|-------------------------------------------------------|
| name | string | Unique identifier (defaults to filename) |
| description | string | Human-readable description |
| targets | string[] | Target platforms (e.g., freemarker, spa, react) |
| version | string | Semver constraint (e.g., >=6.2, ^6.3.0) |
| tags | string[] | Tags for categorization and filtering |
| requires | string[] | Field types that must be detected for inclusion |
| priority | number | Sort order (higher = earlier in output) |
API
SkillManager (Recommended)
The SkillManager provides a high-level API for managing skills from multiple directories with configurable priority-based loading. This is the recommended approach for applications that need to manage skills from multiple sources.
Constructor
const manager = new SkillManager(options?)
interface SkillManagerOptions {
directories?: SkillDirectory[]; // Directories to load, sorted by priority
enabled?: boolean; // Enable/disable skill loading (default: true)
cacheTtl?: number; // Cache TTL in milliseconds (default: 60000)
customFilter?: (skill, context) => boolean; // Custom filter function
customSort?: (a, b) => number; // Custom sort function
}
interface SkillDirectory {
path: string; // Absolute or relative path to skills directory
priority?: number; // Priority (higher = loaded later = overrides earlier)
}Methods
// Register a directory with optional priority
manager.registerSkillDirectory(path: string, priority?: number): void
// Load all skills without filtering
const all = await manager.loadAll(): Promise<Skill[]>
// Load skills filtered by context
const filtered = await manager.load(context: SkillContext): Promise<Skill[]>
// Get a single skill by name
const skill = await manager.get(name: string): Promise<Skill | undefined>
// Get combined prompt from filtered skills
const prompt = await manager.getPrompt(
context: SkillContext,
options?: PromptOptions
): Promise<string>
// List all skills with metadata
const list = await manager.listSkills(): Promise<SkillInfo[]>
// Get registered directories with priorities
const dirs = manager.getDirectories(): Array<{ path: string; priority: number }>
// Get statistics
const stats = manager.getStats(): {
totalDirectories: number;
enabledDirectories: number;
enabled: boolean;
}
// Clear cache to force reload
manager.clearCache(): voidPriority-Based Loading
Skills are loaded in priority order (lowest to highest). When multiple directories contain skills with the same name, the skill from the higher priority directory wins.
Example:
const manager = new SkillManager({
directories: [
{ path: './built-in-skills', priority: 1 }, // Base skills
{ path: './plugin-skills', priority: 10 }, // Plugin overrides
{ path: './user-skills', priority: 100 }, // User overrides
{ path: './project-skills', priority: 1000 }, // Project-specific (highest)
],
});
// If all directories have a skill named "dialog-generation.prompt.md",
// the one from './project-skills' will be used (highest priority)Dynamic Registration
const manager = new SkillManager();
// Register directories dynamically
manager.registerSkillDirectory('./core-skills', 1);
manager.registerSkillDirectory('./plugin-a-skills', 10);
manager.registerSkillDirectory('./plugin-b-skills', 11);
manager.registerSkillDirectory('./user-skills', 100);
// Re-registering the same path updates its priority
manager.registerSkillDirectory('./user-skills', 200);Disabling Skill Loading
const manager = new SkillManager({ enabled: false });
// All methods return empty results when disabled
await manager.loadAll(); // Returns []
await manager.getPrompt({}); // Returns ""SkillLoader (Low-level API)
The SkillLoader provides direct control over skill sources and loading. Use this for simple use cases or when you need fine-grained control.
const loader = new SkillLoader(options?)
.addDirectory(path, pattern?) // Add a directory source
.addFile(path) // Add a single file
.addSkills(skills[]) // Add inline skills
.addUrl(url, headers?) // Add a remote URL source
// Load all skills
const all = await loader.loadAll();
// Load with filtering
const filtered = await loader.load(context);
// Get a single skill
const skill = await loader.get('skill-name');
// Get combined prompt
const prompt = await loader.getPrompt(context, options?);
// Clear cache
loader.clearCache();Context Filtering
interface SkillContext {
target?: string; // Platform: 'freemarker', 'spa', 'react', etc.
version?: string; // Version to match against constraints
detectedFieldTypes?: string[]; // Field types present in current context
requiredTags?: string[]; // Skills must have at least one of these
excludedTags?: string[]; // Skills with these tags are excluded
}Standalone Functions
import {
parseSkillFile,
createSkill,
filterSkills,
loadSkillsFromDirectory,
} from '@magnolia/skill-loader';
// Parse a skill file
const skill = parseSkillFile(content, filename);
// Create a skill programmatically
const skill = createSkill('name', 'content', { targets: ['spa'] });
// Filter skills
const filtered = filterSkills(skills, context);
// Load from directory
const skills = await loadSkillsFromDirectory('./skills', '*.prompt.md');Filtering Logic
Target Matching
Skills with targets are included only if the context target matches (case-insensitive). Skills without targets are always included.
Version Matching
Skills with version are included only if the context version satisfies the semver constraint. Uses the semver library.
Requires Matching
Skills with requires are only included when at least one required field type is in detectedFieldTypes.
Tag Filtering
requiredTags: Skills must have at least one matching tagexcludedTags: Skills with any matching tag are excluded
Use Cases
When to Use SkillManager
Use SkillManager when you need:
- Multiple skill sources with override capabilities (plugins, user configs, project-specific)
- Priority-based loading where later sources override earlier ones
- Dynamic registration of skill directories at runtime
- Centralized management of skills across an application
- Easy enable/disable of skill loading
Example: Plugin System
const manager = new SkillManager({
directories: [
{ path: '~/.myapp/skills', priority: 100 },
{ path: './project/skills', priority: 1000 },
],
});
// Plugins register their skills dynamically
plugins.forEach((plugin, index) => {
if (plugin.skillsDir) {
manager.registerSkillDirectory(plugin.skillsDir, 1 + index);
}
});When to Use SkillLoader
Use SkillLoader when you need:
- Simple, single-source skill loading
- Fine-grained control over sources (files, URLs, inline)
- Custom loading logic without priority management
- Lightweight skill loading without extra abstractions
Example: Simple CLI Tool
const loader = new SkillLoader()
.addDirectory('./skills')
.addFile('./custom-skill.md');
const prompt = await loader.getPrompt({ target: 'react' });Development
# Install dependencies
npm install
# Build
npm run build
# Run tests
npm test
# Watch mode
npm run devLicense
See LICENSE.txt for license information.
Support
- GitLab Issues Currently Internal Access Only
- Magnolia Documentation
