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"
2026-04-10 23:49:53 +00:00
import { Effect } from "effect"
import * as Stream from "effect/Stream"
2026-04-16 03:29:12 +00:00
import { EffectLogger } from "@/effect"
2026-04-16 03:49:51 +00:00
import { Ripgrep } from "../file"
2026-04-14 06:26:23 +00:00
import { Skill } from "../skill"
import { Tool } from "./tool"
2025-12-22 23:24:06 +00:00
2026-04-07 23:48:12 +00:00
const Parameters = z . object ( {
name : z.string ( ) . describe ( "The name of the skill from available_skills" ) ,
} )
2026-04-11 02:36:02 +00:00
export const SkillTool = Tool . define (
2026-04-10 23:49:53 +00:00
"skill" ,
Effect . gen ( function * ( ) {
const skill = yield * Skill . Service
const rg = yield * Ripgrep . Service
2026-04-14 06:26:23 +00:00
2026-04-11 16:33:17 +00:00
return ( ) = >
Effect . gen ( function * ( ) {
const list = yield * skill . available ( ) . pipe ( Effect . provide ( EffectLogger . layer ) )
2025-12-22 23:24:06 +00:00
2026-04-11 16:33:17 +00:00
const description =
list . length === 0
? "Load a specialized skill that provides domain-specific instructions and workflows. No skills are currently available."
: [
"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:" ,
"" ,
Skill . fmt ( list , { verbose : false } ) ,
] . join ( "\n" )
2025-12-22 23:24:06 +00:00
2026-04-11 16:33:17 +00:00
return {
description ,
parameters : Parameters ,
execute : ( params : z.infer < typeof Parameters > , ctx : Tool.Context ) = >
Effect . gen ( function * ( ) {
const info = yield * skill . get ( params . name )
if ( ! info ) {
const all = yield * skill . all ( )
2026-04-14 06:26:23 +00:00
const available = all . map ( ( item ) = > item . name ) . join ( ", " )
2026-04-11 16:33:17 +00:00
throw new Error ( ` Skill " ${ params . name } " not found. Available skills: ${ available || "none" } ` )
}
2026-02-03 15:58:31 +00:00
2026-04-11 16:33:17 +00:00
yield * ctx . ask ( {
permission : "skill" ,
patterns : [ params . name ] ,
always : [ params . name ] ,
metadata : { } ,
} )
2025-12-23 01:14:33 +00:00
2026-04-11 16:33:17 +00:00
const dir = path . dirname ( info . location )
const base = pathToFileURL ( dir ) . href
const limit = 10
2026-04-14 06:26:23 +00:00
const files = yield * rg . files ( { cwd : dir , follow : false , hidden : true , signal : ctx.abort } ) . pipe (
2026-04-11 16:33:17 +00:00
Stream . filter ( ( file ) = > ! file . includes ( "SKILL.md" ) ) ,
Stream . map ( ( file ) = > path . resolve ( dir , file ) ) ,
Stream . take ( limit ) ,
Stream . runCollect ,
Effect . map ( ( chunk ) = > [ . . . chunk ] . map ( ( file ) = > ` <file> ${ file } </file> ` ) . join ( "\n" ) ) ,
)
2026-04-10 23:49:53 +00:00
2026-04-11 16:33:17 +00:00
return {
title : ` Loaded skill: ${ info . name } ` ,
output : [
` <skill_content name=" ${ info . name } "> ` ,
` # Skill: ${ info . name } ` ,
"" ,
info . 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" ) ,
metadata : {
name : info.name ,
dir ,
} ,
}
} ) . pipe ( Effect . orDie ) ,
}
} )
2026-04-10 23:49:53 +00:00
} ) ,
)