2025-12-22 23:24:06 +00:00
import path from "path"
2026-02-03 15:58:31 +00:00
import { pathToFileURL } from "url"
2025-12-22 23:24:06 +00:00
import z from "zod"
import { Tool } from "./tool"
import { Skill } from "../skill"
2026-02-03 15:58:31 +00:00
import { Ripgrep } from "../file/ripgrep"
import { iife } from "@/util/iife"
2025-12-22 23:24:06 +00:00
2026-01-06 18:01:37 +00:00
export const SkillTool = Tool . define ( "skill" , async ( ctx ) = > {
2026-03-11 15:24:55 +00:00
const list = await Skill . available ( ctx ? . agent )
2025-12-22 23:24:06 +00:00
2026-01-01 22:54:11 +00:00
const description =
2026-03-11 15:24:55 +00:00
list . length === 0
2026-02-03 15:58:31 +00:00
? "Load a specialized skill that provides domain-specific instructions and workflows. No skills are currently available."
2026-01-01 22:54:11 +00:00
: [
2026-02-03 15:58:31 +00:00
"Load a specialized skill that provides domain-specific instructions and workflows." ,
"" ,
"When you recognize that a task matches one of the available skills listed below, use this tool to load the full skill instructions." ,
"" ,
"The skill will inject detailed instructions, workflows, and access to bundled resources (scripts, references, templates) into the conversation context." ,
"" ,
'Tool output includes a `<skill_content name="...">` block with the loaded content.' ,
"" ,
"The following skills provide specialized sets of instructions for particular tasks" ,
"Invoke this tool to load a skill when a task matches one of the available skills listed below:" ,
"" ,
2026-03-11 21:03:15 +00:00
Skill . fmt ( list , { verbose : false } ) ,
2026-02-03 15:58:31 +00:00
] . join ( "\n" )
2025-12-22 23:24:06 +00:00
2026-03-11 15:24:55 +00:00
const examples = list
2026-01-21 17:08:04 +00:00
. map ( ( skill ) = > ` ' ${ skill . name } ' ` )
. slice ( 0 , 3 )
. join ( ", " )
const hint = examples . length > 0 ? ` (e.g., ${ examples } , ...) ` : ""
const parameters = z . object ( {
2026-02-03 15:58:31 +00:00
name : z.string ( ) . describe ( ` The name of the skill from available_skills ${ hint } ` ) ,
2026-01-21 17:08:04 +00:00
} )
2026-01-01 22:54:11 +00:00
return {
description ,
2026-01-06 18:01:37 +00:00
parameters ,
async execute ( params : z.infer < typeof parameters > , ctx ) {
2026-01-01 22:54:11 +00:00
const skill = await Skill . get ( params . name )
2025-12-22 23:24:06 +00:00
2026-01-01 22:54:11 +00:00
if ( ! skill ) {
2026-03-11 15:24:55 +00:00
const available = await Skill . all ( ) . then ( ( x ) = > x . map ( ( skill ) = > skill . name ) . join ( ", " ) )
2026-01-01 22:54:11 +00:00
throw new Error ( ` Skill " ${ params . name } " not found. Available skills: ${ available || "none" } ` )
}
2025-12-22 23:24:06 +00:00
2026-01-01 22:54:11 +00:00
await ctx . ask ( {
permission : "skill" ,
patterns : [ params . name ] ,
always : [ params . name ] ,
metadata : { } ,
} )
2026-02-03 15:58:31 +00:00
2026-01-01 22:54:11 +00:00
const dir = path . dirname ( skill . location )
2026-02-03 15:58:31 +00:00
const base = pathToFileURL ( dir ) . href
2025-12-23 01:14:33 +00:00
2026-02-03 15:58:31 +00:00
const limit = 10
const files = await iife ( async ( ) = > {
const arr = [ ]
for await ( const file of Ripgrep . files ( {
cwd : dir ,
follow : false ,
hidden : true ,
signal : ctx.abort ,
} ) ) {
if ( file . includes ( "SKILL.md" ) ) {
continue
}
arr . push ( path . resolve ( dir , file ) )
if ( arr . length >= limit ) {
break
}
}
return arr
} ) . then ( ( f ) = > f . map ( ( file ) = > ` <file> ${ file } </file> ` ) . join ( "\n" ) )
2025-12-23 01:14:33 +00:00
2026-01-01 22:54:11 +00:00
return {
title : ` Loaded skill: ${ skill . name } ` ,
2026-02-03 15:58:31 +00:00
output : [
` <skill_content name=" ${ skill . name } "> ` ,
` # Skill: ${ skill . name } ` ,
"" ,
skill . content . trim ( ) ,
"" ,
` Base directory for this skill: ${ base } ` ,
"Relative paths in this skill (e.g., scripts/, reference/) are relative to this base directory." ,
"Note: file list is sampled." ,
"" ,
"<skill_files>" ,
files ,
"</skill_files>" ,
"</skill_content>" ,
] . join ( "\n" ) ,
2026-01-01 22:54:11 +00:00
metadata : {
name : skill.name ,
dir ,
} ,
}
} ,
}
} )