Class: Kward::Skills::Registry

Inherits:
Object
  • Object
show all
Defined in:
lib/kward/skills/registry.rb

Instance Method Summary collapse

Constructor Details

#initialize(config_dir:, skill_class:, max_file_bytes:, markdown_parser:, inside_directory:) ⇒ Registry

Returns a new instance of Registry.



6
7
8
9
10
11
12
# File 'lib/kward/skills/registry.rb', line 6

def initialize(config_dir:, skill_class:, max_file_bytes:, markdown_parser:, inside_directory:)
  @config_dir = config_dir
  @skill_class = skill_class
  @max_file_bytes = max_file_bytes
  @markdown_parser = markdown_parser
  @inside_directory = inside_directory
end

Instance Method Details

#read_skill_file(name, relative_path = nil) ⇒ Object



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/kward/skills/registry.rb', line 36

def read_skill_file(name, relative_path = nil)
  skill = skills.find { |candidate| candidate.name == name.to_s }
  return "Error: unknown skill: #{name}" unless skill

  path = relative_path.to_s.empty? ? "SKILL.md" : relative_path.to_s
  return "Error: skill path must be relative" if Pathname.new(path).absolute?

  base = File.realpath(skill.folder)
  target = File.expand_path(path, base)
  real_target = File.realpath(target)
  unless @inside_directory.call(real_target, base)
    return "Error: skill path outside skill folder: #{path}"
  end
  return "Error: skill path is not a file: #{path}" unless File.file?(real_target)

  size = File.size(real_target)
  return "Error: skill file too large: #{path} (#{size} bytes)" if size > @max_file_bytes

  File.read(real_target)
rescue Errno::ENOENT
  "Error: skill file not found: #{path}"
rescue StandardError => e
  "Error: could not read skill file #{path}: #{e.message}"
end

#skillsObject



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/kward/skills/registry.rb', line 14

def skills
  skills_root = File.join(@config_dir, "skills")
  return [] unless Dir.exist?(skills_root)

  seen = {}
  Dir.glob(File.join(skills_root, "*", "SKILL.md")).sort.filter_map do |path|
    skill = parse_skill(path)
    next unless skill

    if seen[skill.name]
      warn "Warning: skipping duplicate Kward skill #{skill.name.inspect}: #{path}"
      next
    end

    seen[skill.name] = true
    skill
  end
rescue StandardError => e
  warn "Warning: skipping Kward skills in #{skills_root}: #{e.message}"
  []
end