@@ -2,8 +2,23 @@ import { tool } from "@opencode-ai/plugin"
22import { existsSync , readdirSync , statSync , readlinkSync , readFileSync } from "fs"
33import { homedir } from "os"
44import { join , resolve , basename } from "path"
5+ import { z } from "zod/v4"
56import { parseFrontmatter , resolveCommandsInText } from "../../shared"
6- import type { SkillScope , SkillMetadata , SkillInfo , LoadedSkill } from "./types"
7+ import { SkillFrontmatterSchema } from "./types"
8+ import type { SkillScope , SkillMetadata , SkillInfo , LoadedSkill , SkillFrontmatter } from "./types"
9+
10+ function parseSkillFrontmatter ( data : Record < string , unknown > ) : SkillFrontmatter {
11+ return {
12+ name : typeof data . name === "string" ? data . name : "" ,
13+ description : typeof data . description === "string" ? data . description : "" ,
14+ license : typeof data . license === "string" ? data . license : undefined ,
15+ "allowed-tools" : Array . isArray ( data [ "allowed-tools" ] ) ? data [ "allowed-tools" ] : undefined ,
16+ metadata :
17+ typeof data . metadata === "object" && data . metadata !== null
18+ ? ( data . metadata as Record < string , string > )
19+ : undefined ,
20+ }
21+ }
722
823function discoverSkillsFromDir (
924 skillsDir : string ,
@@ -93,10 +108,14 @@ async function parseSkillMd(skillPath: string): Promise<SkillInfo | null> {
93108 content = await resolveCommandsInText ( content )
94109 const { data, body } = parseFrontmatter ( content )
95110
111+ const frontmatter = parseSkillFrontmatter ( data )
112+
96113 const metadata : SkillMetadata = {
97- name : data . name || basename ( skillPath ) ,
98- description : data . description || "" ,
99- license : data . license ,
114+ name : frontmatter . name || basename ( skillPath ) ,
115+ description : frontmatter . description ,
116+ license : frontmatter . license ,
117+ allowedTools : frontmatter [ "allowed-tools" ] ,
118+ metadata : frontmatter . metadata ,
100119 }
101120
102121 const referencesDir = join ( resolvedPath , "references" )
@@ -118,6 +137,7 @@ async function parseSkillMd(skillPath: string): Promise<SkillInfo | null> {
118137 return {
119138 name : metadata . name ,
120139 path : resolvedPath ,
140+ basePath : resolvedPath ,
121141 metadata,
122142 content : body ,
123143 references,
@@ -202,13 +222,15 @@ async function loadSkillWithReferences(
202222 content = await resolveCommandsInText ( content )
203223 referencesLoaded . push ( { path : ref , content } )
204224 } catch {
225+ // Skip unreadable references
205226 }
206227 }
207228 }
208229
209230 return {
210231 name : skill . name ,
211232 metadata : skill . metadata ,
233+ basePath : skill . basePath ,
212234 body : skill . content ,
213235 referencesLoaded,
214236 }
@@ -234,57 +256,32 @@ function formatLoadedSkills(loadedSkills: LoadedSkill[]): string {
234256 return "No skills loaded."
235257 }
236258
237- const sections : string [ ] = [ "# Loaded Skills\n" ]
238-
239- for ( const skill of loadedSkills ) {
240- sections . push ( `## ${ skill . metadata . name } \n` )
241- sections . push ( `**Description**: ${ skill . metadata . description || "(no description)" } \n` )
242- sections . push ( "### Skill Instructions\n" )
243- sections . push ( skill . body . trim ( ) )
244-
245- if ( skill . referencesLoaded . length > 0 ) {
246- sections . push ( "\n### Loaded References\n" )
247- for ( const ref of skill . referencesLoaded ) {
248- sections . push ( `#### ${ ref . path } \n` )
249- sections . push ( "```" )
250- sections . push ( ref . content . trim ( ) )
251- sections . push ( "```\n" )
252- }
253- }
259+ const skill = loadedSkills [ 0 ]
260+ const sections : string [ ] = [ ]
254261
255- sections . push ( "\n---\n" )
262+ sections . push ( `Base directory for this skill: ${ skill . basePath } /` )
263+ sections . push ( "" )
264+ sections . push ( skill . body . trim ( ) )
265+
266+ if ( skill . referencesLoaded . length > 0 ) {
267+ sections . push ( "\n---\n### Loaded References\n" )
268+ for ( const ref of skill . referencesLoaded ) {
269+ sections . push ( `#### ${ ref . path } \n` )
270+ sections . push ( "```" )
271+ sections . push ( ref . content . trim ( ) )
272+ sections . push ( "```\n" )
273+ }
256274 }
257275
258- const skillNames = loadedSkills . map ( ( s ) => s . metadata . name ) . join ( ", " )
259- sections . push ( `**Skills loaded**: ${ skillNames } ` )
260- sections . push ( `**Total**: ${ loadedSkills . length } skill(s)` )
261- sections . push ( "\nPlease confirm these skills match your needs before proceeding." )
276+ sections . push ( `\n---\n**Launched skill**: ${ skill . metadata . name } ` )
262277
263278 return sections . join ( "\n" )
264279}
265280
266281export const skill = tool ( {
267282 description : `Execute a skill within the main conversation.
268283
269- When users ask you to perform tasks, check if any of the available skills below can help complete the task more effectively. Skills provide specialized capabilities and domain knowledge.
270-
271- How to use skills:
272- - Invoke skills using this tool with the skill name only (no arguments)
273- - When you invoke a skill, the skill's prompt will expand and provide detailed instructions on how to complete the task
274-
275- Important:
276- - Only use skills listed in Available Skills below
277- - Do not invoke a skill that is already running
278-
279- Skills are loaded from:
280- - ~/.claude/skills/ (user scope - global skills)
281- - ./.claude/skills/ (project scope - project-specific skills)
282-
283- Each skill contains:
284- - SKILL.md: Main instructions with YAML frontmatter (name, description)
285- - references/: Documentation files loaded into context as needed
286- - scripts/: Executable code for deterministic operations
287- - assets/: Files used in output (templates, icons, etc.)
284+ When you invoke a skill, the skill's prompt will expand and provide detailed instructions on how to complete the task.
288285
289286Available Skills:
290287${ skillListForDescription } `,
0 commit comments