skill.ts 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. import path from "path"
  2. import z from "zod"
  3. import { Tool } from "./tool"
  4. import { Skill } from "../skill"
  5. import { ConfigMarkdown } from "../config/markdown"
  6. export const SkillTool = Tool.define("skill", async () => {
  7. const skills = await Skill.all()
  8. // Filter skills by agent permissions if agent provided
  9. /*
  10. let accessibleSkills = skills
  11. if (ctx?.agent) {
  12. const permissions = ctx.agent.permission.skill
  13. accessibleSkills = skills.filter((skill) => {
  14. const action = Wildcard.all(skill.name, permissions)
  15. return action !== "deny"
  16. })
  17. }
  18. */
  19. const description =
  20. skills.length === 0
  21. ? "Load a skill to get detailed instructions for a specific task. No skills are currently available."
  22. : [
  23. "Load a skill to get detailed instructions for a specific task.",
  24. "Skills provide specialized knowledge and step-by-step guidance.",
  25. "Use this when a task matches an available skill's description.",
  26. "<available_skills>",
  27. ...skills.flatMap((skill) => [
  28. ` <skill>`,
  29. ` <name>${skill.name}</name>`,
  30. ` <description>${skill.description}</description>`,
  31. ` </skill>`,
  32. ]),
  33. "</available_skills>",
  34. ].join(" ")
  35. return {
  36. description,
  37. parameters: z.object({
  38. name: z
  39. .string()
  40. .describe("The skill identifier from available_skills (e.g., 'code-review' or 'category/helper')"),
  41. }),
  42. async execute(params, ctx) {
  43. const skill = await Skill.get(params.name)
  44. if (!skill) {
  45. const available = await Skill.all().then((x) => Object.keys(x).join(", "))
  46. throw new Error(`Skill "${params.name}" not found. Available skills: ${available || "none"}`)
  47. }
  48. await ctx.ask({
  49. permission: "skill",
  50. patterns: [params.name],
  51. always: [params.name],
  52. metadata: {},
  53. })
  54. // Load and parse skill content
  55. const parsed = await ConfigMarkdown.parse(skill.location)
  56. const dir = path.dirname(skill.location)
  57. // Format output similar to plugin pattern
  58. const output = [`## Skill: ${skill.name}`, "", `**Base directory**: ${dir}`, "", parsed.content.trim()].join("\n")
  59. return {
  60. title: `Loaded skill: ${skill.name}`,
  61. output,
  62. metadata: {
  63. name: skill.name,
  64. dir,
  65. },
  66. }
  67. },
  68. }
  69. })