@jaypie/tildeskill
Prerequisites: npm install @jaypie/tildeskill
Status: Experimental - APIs may change
Overview
Section titled âOverviewâ@jaypie/tildeskill provides a storage abstraction for skill/vocabulary documents with markdown frontmatter support. Itâs used internally by @jaypie/mcp for serving skill documentation to AI assistants.
Installation
Section titled âInstallationânpm install @jaypie/tildeskillQuick Reference
Section titled âQuick ReferenceâCore Exports
Section titled âCore Exportsâ| Export | Purpose |
|---|---|
createSkillService |
Fabric service factory for skill lookup |
createDynamoDbStore |
DynamoDB store (import from @jaypie/tildeskill/dynamodb) |
createLayeredStore |
Compose multiple stores with namespace prefixes |
createMarkdownStore |
File-based store (reads .md files) |
createMemoryStore |
In-memory store (for testing) |
expandIncludes |
Expand included skills into content |
hashSkill |
sha256 hash of a skill record for change detection |
isValidAlias |
Check if alias is valid |
validateAlias |
Validate and normalize alias (throws on invalid) |
normalizeAlias |
Normalize alias to lowercase |
registerSkillModel |
Register the skill model and its category index |
syncSkills |
Copy one store into another |
| Type | Description |
|---|---|
SkillRecord |
Skill document with alias, content, description, includes, name, nicknames, related, tags |
SkillStore |
Store interface with delete, find, get, getByNickname, list, put, search methods |
DynamoDbStoreOptions |
Options for createDynamoDbStore (category) |
SyncSkillsResult |
Aliases added, removed, unchanged, and updated by syncSkills |
ListFilter |
Filter options for list (namespace, tag) |
LayeredStoreLayer |
Layer definition with namespace and store |
LayeredStoreOptions |
Options for createLayeredStore |
Store Factories
Section titled âStore FactoriesâMarkdown Store
Section titled âMarkdown StoreâReads skill documents from a directory of markdown files:
import { createMarkdownStore } from "@jaypie/tildeskill";
const store = createMarkdownStore({ path: "./skills" });
// Get a specific skillconst skill = await store.get("aws");if (skill) { console.log(skill.alias); // "aws" console.log(skill.description); // From frontmatter console.log(skill.content); // Markdown body console.log(skill.related); // ["errors", "lambda"]}
// List all skillsconst skills = await store.list();skills.forEach(s => console.log(`${s.alias}: ${s.description}`));Memory Store
Section titled âMemory StoreâIn-memory store for testing:
import { createMemoryStore } from "@jaypie/tildeskill";
const store = createMemoryStore([ { alias: "test", content: "# Test\n\nContent", description: "Test skill" }, { alias: "another", content: "# Another", related: ["test"] },]);
// Supports all store operationsconst skill = await store.get("test");await store.put({ alias: "new", content: "# New Skill" });DynamoDB Store
Section titled âDynamoDB Storeâimport { initClient } from "@jaypie/dynamodb";import { createSkillService } from "@jaypie/tildeskill";import { createDynamoDbStore } from "@jaypie/tildeskill/dynamodb";
initClient();const store = createDynamoDbStore({ category: "jaypie" });const skillService = createSkillService(store);@jaypie/dynamodb is an optional peer dependency. Only the
@jaypie/tildeskill/dynamodb subpath imports it, so markdown-only consumers
never load it. The store loads it with a dynamic import on first use, so a
CJS consumer shares the hostâs initialized client. Call initClient() before
using the store.
- Records are
skillentities at APEX scope.categoryis the store namespace;aliasis unqualified. - Ids are deterministic:
uuidv5("<category>:<alias>", SKILL_NAMESPACE).SKILL_NAMESPACEis exported from the subpath and never changes. metadata.hashholdshashSkill(record)(sha256) for change detection.includes,nicknames, andrelatedare stored inmetadata, sincerelatedis reserved for entity references.list,getByNickname, andsearchqueryindexModelCategory.createDynamoDbStorecallsregisterSkillModel(); declare the index in CDK by callingregisterSkillModel()beforegetAllRegisteredIndexes().findkeeps the plural/singular fallback.deletesoft deletes withdeleteEntity. Reads skip archived and deleted entities, and a laterputrestores the same id.
Syncing Stores
Section titled âSyncing Storesâimport { createMarkdownStore, syncSkills } from "@jaypie/tildeskill";
const result = await syncSkills({ from: createMarkdownStore({ path: "./skills" }), to: store,});// { added: ["aws"], removed: ["retired"], unchanged: ["tests"], updated: [] }syncSkills makes to match from. It puts records missing from to
(added) or whose hashSkill differs (updated), skips records whose hash
matches (unchanged), and deletes records missing from from (removed).
Each list holds aliases sorted alphabetically. An empty from with a
non-empty to throws ConfigurationError before writing, since a markdown
store pointed at a missing directory lists nothing. Pass
allowEmptySource: true to empty to on purpose.
Deleting Records
Section titled âDeleting RecordsâEvery store implements delete(alias). It matches the exact alias (no
plural/singular fallback) and returns true when a record was removed and
false when none existed.
| Store | delete behavior |
|---|---|
| memory | Removes the record from the map |
| markdown | Removes <alias>.md; throws BadRequestError for an invalid alias |
| layered | Requires a namespace-qualified alias and delegates to that layer; throws ConfigurationError when unqualified |
| dynamodb | Soft deletes the entity; reads skip it and put restores it |
Include Expansion
Section titled âInclude ExpansionâCompose skills by including other skillsâ content:
import { expandIncludes, createMemoryStore } from "@jaypie/tildeskill";
const store = createMemoryStore([ { alias: "base", content: "Base content" }, { alias: "main", content: "Main content", includes: ["base"] },]);
const record = await store.get("main");const expanded = await expandIncludes(store, record);// expanded = "Base content\n\nMain content"Features:
- Recursively expands nested includes
- Prevents circular references
- Skips missing includes silently
Filtering and Search
Section titled âFiltering and Searchâ// Filter by namespace prefixconst kitSkills = await store.list({ namespace: "kit:" });
// Filter by tagconst cloudSkills = await store.list({ tag: "cloud" });
// Combined filtersconst kitCloudSkills = await store.list({ namespace: "kit:", tag: "cloud" });
// Search across alias, name, description, content, and tagsconst results = await store.search("lambda");
// Lookup by nickname (alternate alias)const skill = await store.getByNickname("amazon");Skill File Format
Section titled âSkill File FormatâSkill files use YAML frontmatter followed by markdown content:
---description: Brief description shown in skill listingsincludes: base-skill, common-utilsname: Display Titlenicknames: alt-name, another-aliasrelated: alias1, alias2, alias3tags: category1, category2---
# Skill Title
Markdown content with documentation, code examples, etc.Frontmatter Fields
Section titled âFrontmatter Fieldsâ| Field | Type | Description |
|---|---|---|
description |
string |
Brief description for listings |
includes |
string |
Comma-separated list of skills to auto-expand |
name |
string |
Display title for the skill |
nicknames |
string |
Comma-separated alternate lookup keys |
related |
string |
Comma-separated list of related skill aliases |
tags |
string |
Comma-separated categorization tags |
All frontmatter fields accept either comma-separated strings or YAML arrays.
Validation Utilities
Section titled âValidation UtilitiesâisValidAlias
Section titled âisValidAliasâCheck if an alias is valid without throwing:
import { isValidAlias } from "@jaypie/tildeskill";
isValidAlias("my-skill"); // trueisValidAlias("my_skill"); // trueisValidAlias("skill123"); // trueisValidAlias("../../etc"); // false (path traversal)isValidAlias(""); // false (empty)isValidAlias("a".repeat(100)); // false (too long)validateAlias
Section titled âvalidateAliasâValidate and normalize, throwing BadRequestError on invalid input:
import { validateAlias } from "@jaypie/tildeskill";
validateAlias("Valid"); // returns "valid" (normalized)validateAlias("MY-SKILL"); // returns "my-skill"validateAlias("../bad"); // throws BadRequestErrornormalizeAlias
Section titled ânormalizeAliasâNormalize alias to lowercase (does not validate):
import { normalizeAlias } from "@jaypie/tildeskill";
normalizeAlias("MY-Skill"); // "my-skill"normalizeAlias("Test_123"); // "test_123"Testing
Section titled âTestingâWith @jaypie/testkit
Section titled âWith @jaypie/testkitâimport { afterEach, beforeEach, describe, expect, it } from "vitest";import { mockTildeskill, restoreTildeskill } from "@jaypie/testkit";
describe("MyComponent", () => { beforeEach(() => { mockTildeskill(); });
afterEach(() => { restoreTildeskill(); });
it("works with mocked store", async () => { // Your tests here });});Direct Memory Store
Section titled âDirect Memory Storeâimport { createMemoryStore } from "@jaypie/tildeskill";
const testStore = createMemoryStore([ { alias: "test", content: "# Test", description: "Test skill" },]);
// Inject into your component/serviceconst service = createService({ store: testStore });Error Handling
Section titled âError Handlingâimport { validateAlias } from "@jaypie/tildeskill";import { BadRequestError } from "@jaypie/errors";
try { validateAlias("../malicious");} catch (error) { if (error instanceof BadRequestError) { console.log("Invalid alias:", error.message); }}Skill Service Factory
Section titled âSkill Service FactoryâcreateSkillService wraps a SkillStore as a fabricService, compatible with MCP servers and Llm.operate toolkits:
import { createSkillService, createMarkdownStore } from "@jaypie/tildeskill";
const store = createMarkdownStore({ path: "./skills" });const skillService = createSkillService(store);
// Get skill content (with automatic expandIncludes)const content = await skillService({ alias: "aws" });
// List all skillsconst index = await skillService({ alias: "index" });// or: await skillService()
// Invalid aliases throw BadRequestError; missing skills throw NotFoundError
// Use with fabricTool for Llm.operateimport { fabricTool } from "@jaypie/fabric/llm";const { tool } = fabricTool({ service: skillService });Layered Stores
Section titled âLayered StoresâCompose multiple stores with namespace prefixes. Earlier layers win for single-result lookups:
import { createLayeredStore, createMarkdownStore } from "@jaypie/tildeskill";
const layered = createLayeredStore({ layers: [ { namespace: "local", store: createMarkdownStore({ path: "./my-skills" }) }, { namespace: "jaypie", store: createMarkdownStore({ path: "./jaypie-skills" }) }, ],});
await layered.get("aws"); // first layer wins â { alias: "local:aws", ... }await layered.get("jaypie:aws"); // targets specific layerawait layered.find("skills"); // exact alias in any layer, then fallbackawait layered.list(); // all layers, prefixed aliasesfind checks every layer for an exact alias before it tries plural/singular fallback in any layer. With local:test and jaypie:tests, find("tests") returns jaypie:tests. Namespace-qualified aliases search only their own layer.
Related
Section titled âRelatedâ- @jaypie/mcp - MCP server using tildeskill
- @jaypie/fabric - Service patterns
- @jaypie/testkit - Testing utilities