2026-02-08 16:21:19 +09:00
import { promises as fs } from "fs"
import { basename } from "path"
import { parseFrontmatter } from "../../shared/frontmatter"
import { sanitizeModelField } from "../../shared/model-sanitizer"
2026-02-09 12:08:15 +09:00
import { resolveSkillPathReferences } from "../../shared/skill-path-resolver"
2026-02-08 16:21:19 +09:00
import type { CommandDefinition } from "../claude-code-command-loader/types"
import { parseAllowedTools } from "./allowed-tools-parser"
import { loadMcpJsonFromDir , parseSkillMcpConfigFromFrontmatter } from "./skill-mcp-config"
import type { SkillScope , SkillMetadata , LoadedSkill , LazyContentLoader } from "./types"
export async function loadSkillFromPath ( options : {
skillPath : string
resolvedPath : string
defaultName : string
scope : SkillScope
namePrefix? : string
} ) : Promise < LoadedSkill | null > {
const namePrefix = options . namePrefix ? ? ""
try {
const content = await fs . readFile ( options . skillPath , "utf-8" )
const { data , body } = parseFrontmatter < SkillMetadata > ( content )
const frontmatterMcp = parseSkillMcpConfigFromFrontmatter ( content )
const mcpJsonMcp = await loadMcpJsonFromDir ( options . resolvedPath )
const mcpConfig = mcpJsonMcp || frontmatterMcp
2026-04-15 10:46:41 +09:00
const baseName = String ( data . name || options . defaultName )
2026-02-08 16:21:19 +09:00
const skillName = namePrefix ? ` ${ namePrefix } / ${ baseName } ` : baseName
const originalDescription = data . description || ""
const isOpencodeSource = options . scope === "opencode" || options . scope === "opencode-project"
const formattedDescription = ` ( ${ options . scope } - Skill) ${ originalDescription } `
2026-02-09 12:08:15 +09:00
const resolvedBody = resolveSkillPathReferences ( body . trim ( ) , options . resolvedPath )
const templateContent = ` <skill-instruction> \ nBase directory for this skill: ${ options . resolvedPath } / \ nFile references (@path) in this skill are relative to this directory. \ n \ n ${ resolvedBody } \ n</skill-instruction> \ n \ n<user-request> \ n $ ARGUMENTS \ n</user-request> `
2026-02-08 16:21:19 +09:00
const eagerLoader : LazyContentLoader = {
loaded : true ,
content : templateContent ,
load : async ( ) = > templateContent ,
}
const definition : CommandDefinition = {
name : skillName ,
description : formattedDescription ,
template : templateContent ,
model : sanitizeModelField ( data . model , isOpencodeSource ? "opencode" : "claude-code" ) ,
agent : data.agent ,
subtask : data.subtask ,
argumentHint : data [ "argument-hint" ] ,
}
return {
name : skillName ,
path : options.skillPath ,
resolvedPath : options.resolvedPath ,
definition ,
scope : options.scope ,
license : data.license ,
compatibility : data.compatibility ,
metadata : data.metadata ,
allowedTools : parseAllowedTools ( data [ "allowed-tools" ] ) ,
mcpConfig ,
lazyContent : eagerLoader ,
}
} catch {
return null
}
}
export function inferSkillNameFromFileName ( filePath : string ) : string {
return basename ( filePath , ".md" )
}