npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@magnolia/skill-loader

v0.1.3

Published

A Node.js library for loading, filtering, and managing AI prompt skills

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-loader

Quick 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(): void

Priority-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 tag
  • excludedTags: 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 dev

License

See LICENSE.txt for license information.

Support