import path from "path" import { pathToFileURL } from "url" import { Effect, Schema } from "effect" import * as Stream from "effect/Stream" import { Ripgrep } from "../file/ripgrep" import { Skill } from "../skill" import * as Tool from "./tool" import DESCRIPTION from "./skill.txt" export const Parameters = Schema.Struct({ name: Schema.String.annotate({ description: "The name of the skill from available_skills" }), }) export const SkillTool = Tool.define( "skill", Effect.gen(function* () { const skill = yield* Skill.Service const rg = yield* Ripgrep.Service return { description: DESCRIPTION, parameters: Parameters, execute: (params: Schema.Schema.Type, ctx: Tool.Context) => Effect.gen(function* () { const info = yield* skill.get(params.name) if (!info) { const all = yield* skill.all() const available = all.map((item) => item.name).join(", ") throw new Error(`Skill "${params.name}" not found. Available skills: ${available || "none"}`) } yield* ctx.ask({ permission: "skill", patterns: [params.name], always: [params.name], metadata: {}, }) const dir = path.dirname(info.location) const base = pathToFileURL(dir).href const limit = 10 const files = yield* rg.files({ cwd: dir, follow: false, hidden: true, signal: ctx.abort }).pipe( 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}`).join("\n")), ) return { title: `Loaded skill: ${info.name}`, output: [ ``, `# 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.", "", "", files, "", "", ].join("\n"), metadata: { name: info.name, dir, }, } }).pipe(Effect.orDie), } }), )