ruby_llm-skills 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 676c58cfd47ff3e3bbf9a8eeac47658c56f2537d189eef7f2743740b8c44afb8
4
- data.tar.gz: b54d6857a8340b9aab46a37e6385296acd6190987ef816172f96b265a4378d72
3
+ metadata.gz: '048ac6f6a76a7fd48994d208799b967754b25ecc3959acd56d44f5ab707f9f8d'
4
+ data.tar.gz: 268827da6d73359d9451b159b1abe68dea7881419df6b473cceeb2d8c74bc073
5
5
  SHA512:
6
- metadata.gz: 22da0df8891691181c0db718da07e3b44ad1ca1cbd04d3991350e5f7fff9996390493a5f047833ff978b8798f447172cfc7f7ddbdffe1871054e61cea40709f0
7
- data.tar.gz: 0a48577bdb85af0d6a1216857fbdb9e4389a2121f271bdc48cff4f35324be2854563b8934d62064667fd1b829959e3e595c05d8be7bd5475b7d6797a36cf992d
6
+ metadata.gz: febe501ab666f7e1cdf06e043ab44305ea065ffe561508b6fdcb9a959a90eabad8d2cb9fcc90384536eaf0871f73f2e52c3c9e93b8d883fbe8823274f547f0f3
7
+ data.tar.gz: 73226c3f1eb42ba6073dbfdcccb9f334896e078ca04ab1814260feb4218d432a83cf92817a32bb6c1abe847cf7fcd0098259aa2bab5886ce933d2c258d4ae8f1
data/CHANGELOG.md CHANGED
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.0] - 2026-02-17
9
+
10
+ ### Added
11
+
12
+ - `RubyLLM::Agent` integration via `RubyLLM::Skills::AgentExtensions`
13
+ - Class-level `skills` DSL on agent subclasses with source, `only:`, and proc support
14
+ - Instance-level `with_skills` for runtime agent skill configuration
15
+ - Agent-specific unit and integration test coverage
16
+ - Compatibility tests for delegate fallback behavior
17
+
18
+ ### Changed
19
+
20
+ - Tightened `ruby_llm` dependency from open lower bound to `~> 1.12`
21
+ - Added explicit runtime compatibility checks for required `RubyLLM::Agent` hooks
22
+ - Documented `agent.with_skills(...)` replacement semantics in README
23
+
24
+ ### Fixed
25
+
26
+ - Dynamic skill blocks resolving to `nil` or `[]` no longer silently load default skills
27
+ - Skill source normalization and validation now prevent nested/invalid source runtime crashes
28
+ - Delegate fallback now supports common delegation options (`prefix`, `allow_nil`, `private`)
29
+
8
30
  ## [0.1.0] - 2025-01-15
9
31
 
10
32
  ### Added
data/README.md CHANGED
@@ -31,6 +31,26 @@ chat.with_skills("app/skills", "app/commands") # multiple paths
31
31
  chat.with_skills("app/skills", user.skills) # with database records
32
32
  ```
33
33
 
34
+ ### With RubyLLM::Agent (v1.12+)
35
+
36
+ ```ruby
37
+ class SupportAgent < RubyLLM::Agent
38
+ model "gpt-5-nano"
39
+ instructions "You are a support assistant."
40
+ skills "app/skills", only: [:faq, :troubleshooting]
41
+ end
42
+
43
+ chat = SupportAgent.chat
44
+ chat.ask("How do I reset my password?")
45
+
46
+ agent = SupportAgent.new
47
+ agent.with_skills("extra/skills")
48
+ agent.ask("What can you help with?")
49
+ ```
50
+
51
+ `agent.with_skills(...)` replaces the current skill tool configuration.
52
+ To combine sources, pass all sources in a single `skills`/`with_skills` call.
53
+
34
54
  ## Creating Skills
35
55
 
36
56
  ```
@@ -96,6 +116,37 @@ Default path auto-configured to `Rails.root/app/skills`.
96
116
  rails generate skill pdf-report --description "Generate PDF reports"
97
117
  ```
98
118
 
119
+ ## Development
120
+
121
+ ### Setup
122
+
123
+ ```bash
124
+ bin/setup
125
+ ```
126
+
127
+ ### Running Tests
128
+
129
+ ```bash
130
+ bundle exec rake test # Unit tests (151 tests)
131
+ bundle exec rake test_rails # Rails integration tests (25+ tests)
132
+ bundle exec rake test_all # Both
133
+ bundle exec rake # Tests + linting
134
+ ```
135
+
136
+ ### Dummy Rails App
137
+
138
+ A minimal Rails 8 app at `test/dummy/` tests Rails integration:
139
+
140
+ - **Filesystem skills**: `app/skills/greeting/` tests directory-based loading
141
+ - **Database skills**: `Skill` model tests ActiveRecord-based loading
142
+ - **Generator tests**: Tests for `rails generate skill`
143
+ - **Composite loading**: Tests combining filesystem + database sources
144
+
145
+ ```bash
146
+ cd test/dummy
147
+ bundle exec rails test # Run Rails tests directly
148
+ ```
149
+
99
150
  ## Resources
100
151
 
101
152
  - [Agent Skills Specification](https://agentskills.io/specification)
@@ -0,0 +1,29 @@
1
+ Description:
2
+ Creates a new Agent Skill following the agentskills.io specification.
3
+ Skills are stored in app/skills/ and contain a SKILL.md file with
4
+ YAML frontmatter (name, description) and markdown instructions.
5
+
6
+ Example:
7
+ rails generate skill pdf-report --description "Generate PDF reports"
8
+
9
+ This creates:
10
+ app/skills/pdf-report/
11
+ app/skills/pdf-report/SKILL.md
12
+
13
+ With optional directories:
14
+ --scripts Creates scripts/ directory for executable code
15
+ --references Creates references/ directory for documentation
16
+ --assets Creates assets/ directory for templates/images
17
+
18
+ Full Example:
19
+ rails generate skill data-export -d "Export data to CSV/JSON. Use when asked to export or download data." --scripts --assets
20
+
21
+ This creates:
22
+ app/skills/data-export/
23
+ app/skills/data-export/SKILL.md
24
+ app/skills/data-export/scripts/.keep
25
+ app/skills/data-export/assets/.keep
26
+
27
+ Note:
28
+ The description should include both what the skill does AND when to use it.
29
+ Example: "Generate PDF reports from data. Use when asked to create reports or export to PDF."
@@ -5,11 +5,16 @@ require "rails/generators"
5
5
  class SkillGenerator < Rails::Generators::NamedBase
6
6
  source_root File.expand_path("templates", __dir__)
7
7
 
8
- class_option :description, type: :string, default: "Description of what this skill does"
9
- class_option :license, type: :string, default: nil
10
- class_option :scripts, type: :boolean, default: false
11
- class_option :references, type: :boolean, default: false
12
- class_option :assets, type: :boolean, default: false
8
+ class_option :description, type: :string, default: "Description of what this skill does. Use when...",
9
+ aliases: "-d", desc: "Short description of the skill (max 1024 chars)"
10
+ class_option :license, type: :string, default: nil,
11
+ aliases: "-l", desc: "License identifier (e.g., MIT, Apache-2.0)"
12
+ class_option :scripts, type: :boolean, default: false,
13
+ desc: "Create scripts/ directory for executable code"
14
+ class_option :references, type: :boolean, default: false,
15
+ desc: "Create references/ directory for documentation"
16
+ class_option :assets, type: :boolean, default: false,
17
+ desc: "Create assets/ directory for templates/images"
13
18
 
14
19
  def create_skill_directory
15
20
  empty_directory skill_path
@@ -57,4 +62,8 @@ class SkillGenerator < Rails::Generators::NamedBase
57
62
  def skill_license
58
63
  options[:license]
59
64
  end
65
+
66
+ def skill_title
67
+ skill_name.split("-").map(&:capitalize).join(" ")
68
+ end
60
69
  end
@@ -6,16 +6,52 @@ license: <%= skill_license %>
6
6
  <% end -%>
7
7
  ---
8
8
 
9
- # <%= skill_name.split("-").map(&:capitalize).join(" ") %>
9
+ # <%= skill_title %>
10
10
 
11
- Instructions for using this skill.
11
+ <%= skill_description %>
12
12
 
13
13
  ## When to Use
14
14
 
15
- Use this skill when...
15
+ Use this skill when:
16
16
 
17
- ## Steps
17
+ - [Trigger condition 1 - be specific about keywords/phrases]
18
+ - [Trigger condition 2]
18
19
 
19
- 1. First step
20
- 2. Second step
21
- 3. Third step
20
+ ## Instructions
21
+
22
+ ### Step 1: [Action]
23
+
24
+ [Clear instruction with expected outcome]
25
+
26
+ ### Step 2: [Action]
27
+
28
+ [Clear instruction with expected outcome]
29
+
30
+ ### Step 3: [Action]
31
+
32
+ [Clear instruction with expected outcome]
33
+
34
+ ## Examples
35
+
36
+ ### Example 1: [Scenario]
37
+
38
+ **Input:**
39
+ ```
40
+ [Example input or command]
41
+ ```
42
+
43
+ **Output:**
44
+ ```
45
+ [Expected result]
46
+ ```
47
+
48
+ ## Edge Cases
49
+
50
+ - **[Scenario]**: [How to handle it]
51
+ - **[Error condition]**: [Recovery steps]
52
+
53
+ ## Notes
54
+
55
+ - Keep this file under 500 lines for optimal token usage
56
+ - Move detailed documentation to `references/` directory
57
+ - Move executable code to `scripts/` directory
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Skills
5
+ # Extensions for RubyLLM::Agent to enable declarative skill configuration.
6
+ #
7
+ # @example Static skills
8
+ # class SupportAgent < RubyLLM::Agent
9
+ # skills "app/skills", only: [:faq]
10
+ # end
11
+ #
12
+ # @example Dynamic skills
13
+ # class WorkspaceAgent < RubyLLM::Agent
14
+ # inputs :workspace
15
+ # skills { [workspace.skill_collection] }
16
+ # end
17
+ #
18
+ module AgentExtensions
19
+ REQUIRED_AGENT_SINGLETON_METHODS = %i[apply_configuration runtime_context llm_chat_for].freeze
20
+
21
+ module ClassMethods
22
+ def self.extended(base)
23
+ base.instance_variable_set(:@skill_sources, nil)
24
+ base.instance_variable_set(:@skill_only, nil)
25
+ end
26
+
27
+ def inherited(subclass)
28
+ super
29
+ subclass.instance_variable_set(:@skill_sources, @skill_sources.is_a?(Proc) ? @skill_sources : @skill_sources&.dup)
30
+ subclass.instance_variable_set(:@skill_only, @skill_only&.dup)
31
+ end
32
+
33
+ # Declare skill sources for this agent class.
34
+ #
35
+ # Called with no arguments, returns the current configuration.
36
+ # Called with sources or a block, sets the configuration.
37
+ #
38
+ # @param sources [Array] skill sources
39
+ # @param only [Array<Symbol, String>, nil] include only these skills
40
+ # @return [Hash] current configuration when called as a getter
41
+ def skills(*sources, only: nil, &block)
42
+ if sources.empty? && only.nil? && !block_given?
43
+ return {
44
+ sources: @skill_sources.is_a?(Proc) ? @skill_sources : @skill_sources&.dup,
45
+ only: @skill_only&.dup
46
+ }
47
+ end
48
+
49
+ @skill_sources = block_given? ? block : normalize_skill_sources(sources)
50
+ @skill_only = only&.dup
51
+ end
52
+
53
+ private
54
+
55
+ def normalize_skill_sources(raw_sources)
56
+ flatten_skill_sources(raw_sources).compact
57
+ end
58
+
59
+ def flatten_skill_sources(source)
60
+ return [] if source.nil?
61
+ return [source] if source.is_a?(String)
62
+ return [source] if loader_source?(source)
63
+ return [source] if database_collection_source?(source)
64
+ return source.flat_map { |item| flatten_skill_sources(item) } if source.is_a?(Array)
65
+
66
+ [source]
67
+ end
68
+
69
+ def loader_source?(source)
70
+ source.respond_to?(:list) && source.respond_to?(:find)
71
+ end
72
+
73
+ def database_collection_source?(source)
74
+ source.respond_to?(:to_a) && source.first&.respond_to?(:name) && source.first.respond_to?(:content)
75
+ end
76
+ end
77
+
78
+ module InstanceMethods
79
+ # Add skills to this agent instance at runtime.
80
+ #
81
+ # @param sources [Array] skill sources
82
+ # @param only [Array<Symbol, String>, nil] include only these skills
83
+ # @return [self] for chaining
84
+ def with_skills(*sources, only: nil)
85
+ chat.with_skills(*sources, only: only)
86
+ self
87
+ end
88
+ end
89
+
90
+ module ConfigurationPatch
91
+ private
92
+
93
+ def apply_configuration(chat_object, **kwargs)
94
+ super
95
+ input_values = kwargs[:input_values] || {}
96
+ runtime = runtime_context(chat: chat_object, inputs: input_values)
97
+ apply_skills(llm_chat_for(chat_object), runtime)
98
+ end
99
+
100
+ def apply_skills(llm_chat, runtime)
101
+ config = skills
102
+ sources = config[:sources]
103
+ return if sources.nil?
104
+
105
+ resolved_sources = if sources.is_a?(Proc)
106
+ runtime.instance_exec(&sources)
107
+ else
108
+ sources
109
+ end
110
+
111
+ normalized_sources = normalize_skill_sources(resolved_sources)
112
+ return if normalized_sources.empty?
113
+
114
+ validate_skill_sources!(normalized_sources)
115
+ llm_chat.with_skills(*normalized_sources, only: config[:only])
116
+ end
117
+
118
+ def validate_skill_sources!(sources)
119
+ invalid_sources = sources.reject { |source| valid_skill_source?(source) }
120
+ return if invalid_sources.empty?
121
+
122
+ invalid_types = invalid_sources.map { |source| source.class.name || source.class.to_s }.uniq.join(", ")
123
+ raise ArgumentError,
124
+ "Invalid skill source(s): #{invalid_types}. Expected String path, Loader, or record collection."
125
+ end
126
+
127
+ def valid_skill_source?(source)
128
+ source.is_a?(String) || loader_source?(source) || database_collection_source?(source)
129
+ end
130
+ end
131
+
132
+ def self.included(base)
133
+ missing_methods = REQUIRED_AGENT_SINGLETON_METHODS.reject do |method_name|
134
+ base.singleton_class.private_method_defined?(method_name) || base.singleton_class.method_defined?(method_name)
135
+ end
136
+
137
+ if missing_methods.any?
138
+ raise LoadError,
139
+ "RubyLLM::Agent is missing required methods for ruby_llm-skills integration: #{missing_methods.join(", ")}"
140
+ end
141
+
142
+ base.extend(ClassMethods)
143
+ base.include(InstanceMethods)
144
+ base.singleton_class.prepend(ConfigurationPatch)
145
+ end
146
+ end
147
+ end
148
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative "skill_tool"
4
4
 
5
- module RubyLlm
5
+ module RubyLLM
6
6
  module Skills
7
7
  # Extensions for RubyLLM::Chat to enable skill integration.
8
8
  #
@@ -25,13 +25,13 @@ module RubyLlm
25
25
  # @param only [Array<Symbol, String>, nil] include only these skills
26
26
  # @return [self] for chaining
27
27
  def with_skills(*sources, only: nil)
28
- sources = [RubyLlm::Skills.default_path] if sources.empty?
28
+ sources = [RubyLLM::Skills.default_path] if sources.empty?
29
29
  loaders = sources.map { |s| to_loader(s) }
30
30
 
31
- loader = (loaders.length == 1) ? loaders.first : RubyLlm::Skills.compose(*loaders)
31
+ loader = (loaders.length == 1) ? loaders.first : RubyLLM::Skills.compose(*loaders)
32
32
  loader = FilteredLoader.new(loader, only) if only
33
33
 
34
- skill_tool = RubyLlm::Skills::SkillTool.new(loader)
34
+ skill_tool = RubyLLM::Skills::SkillTool.new(loader)
35
35
  with_tool(skill_tool)
36
36
  end
37
37
 
@@ -40,13 +40,24 @@ module RubyLlm
40
40
  def to_loader(source)
41
41
  case source
42
42
  when String
43
- RubyLlm::Skills.from_directory(source)
44
- when ->(s) { s.respond_to?(:to_a) && s.first&.respond_to?(:name) && s.first.respond_to?(:content) }
45
- RubyLlm::Skills.from_database(source)
46
- else
43
+ RubyLLM::Skills.from_directory(source)
44
+ when ->(s) { database_collection_source?(s) }
45
+ RubyLLM::Skills.from_database(source)
46
+ when ->(s) { loader_source?(s) }
47
47
  source
48
+ else
49
+ raise ArgumentError,
50
+ "Invalid skill source: #{source.class}. Expected String path, Loader, or record collection."
48
51
  end
49
52
  end
53
+
54
+ def loader_source?(source)
55
+ source.respond_to?(:list) && source.respond_to?(:find)
56
+ end
57
+
58
+ def database_collection_source?(source)
59
+ source.respond_to?(:to_a) && source.first&.respond_to?(:name) && source.first.respond_to?(:content)
60
+ end
50
61
  end
51
62
 
52
63
  # Simple wrapper that filters skills by name.
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module RubyLlm
3
+ module RubyLLM
4
4
  module Skills
5
5
  # Combines multiple loaders into a single source.
6
6
  #
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module RubyLlm
3
+ module RubyLLM
4
4
  module Skills
5
5
  # Loads skills from database records.
6
6
  #
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module RubyLlm
3
+ module RubyLLM
4
4
  module Skills
5
5
  # Base error class for all skills-related errors.
6
6
  # Rescue this to catch any error from the gem.
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module RubyLlm
3
+ module RubyLLM
4
4
  module Skills
5
5
  # Loads skills from a filesystem directory.
6
6
  #
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module RubyLlm
3
+ module RubyLLM
4
4
  module Skills
5
5
  # Base class for skill loaders.
6
6
  #
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "yaml"
4
4
 
5
- module RubyLlm
5
+ module RubyLLM
6
6
  module Skills
7
7
  # Parses SKILL.md files with YAML frontmatter.
8
8
  #
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module RubyLlm
3
+ module RubyLLM
4
4
  module Skills
5
5
  # Rails integration for RubyLLM::Skills.
6
6
  #
@@ -9,14 +9,14 @@ module RubyLlm
9
9
  #
10
10
  class Railtie < ::Rails::Railtie
11
11
  initializer "ruby_llm_skills.configure" do
12
- RubyLlm::Skills.default_path = Rails.root.join("app", "skills").to_s
12
+ RubyLLM::Skills.default_path = Rails.root.join("app", "skills").to_s
13
13
  end
14
14
 
15
- # Add app/skills to autoload paths
16
- initializer "ruby_llm_skills.autoload_paths" do |app|
15
+ # Add app/skills to autoload paths (before initialization)
16
+ config.before_configuration do |app|
17
17
  skills_path = Rails.root.join("app", "skills")
18
18
  if skills_path.exist?
19
- app.config.autoload_paths << skills_path.to_s
19
+ app.config.autoload_paths += [skills_path.to_s]
20
20
  end
21
21
  end
22
22
 
@@ -24,7 +24,7 @@ module RubyLlm
24
24
  initializer "ruby_llm_skills.active_record" do
25
25
  ActiveSupport.on_load(:active_record) do
26
26
  if defined?(RubyLLM::ActiveRecord::ChatMethods)
27
- RubyLLM::ActiveRecord::ChatMethods.include(RubyLlm::Skills::ActiveRecordExtensions)
27
+ RubyLLM::ActiveRecord::ChatMethods.include(RubyLLM::Skills::ActiveRecordExtensions)
28
28
  end
29
29
  end
30
30
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module RubyLlm
3
+ module RubyLLM
4
4
  module Skills
5
5
  # Represents a single skill with its metadata and content.
6
6
  #
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "ruby_llm"
4
4
 
5
- module RubyLlm
5
+ module RubyLLM
6
6
  module Skills
7
7
  # A RubyLLM Tool that enables progressive skill loading.
8
8
  #
@@ -17,8 +17,8 @@ module RubyLlm
17
17
  # 3. Resources (scripts, references) can be loaded separately as needed
18
18
  #
19
19
  # @example Basic usage
20
- # loader = RubyLlm::Skills.from_directory("app/skills")
21
- # skill_tool = RubyLlm::Skills::SkillTool.new(loader)
20
+ # loader = RubyLLM::Skills.from_directory("app/skills")
21
+ # skill_tool = RubyLLM::Skills::SkillTool.new(loader)
22
22
  #
23
23
  # chat.with_tools(skill_tool)
24
24
  # chat.ask("Help me generate a PDF report")
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module RubyLlm
3
+ module RubyLLM
4
4
  module Skills
5
5
  # Validates skill structure according to the Agent Skills specification.
6
6
  #
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module RubyLlm
3
+ module RubyLLM
4
4
  module Skills
5
- VERSION = "0.2.0"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
@@ -1,5 +1,50 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # RubyLLM 1.12.0 Agent uses `delegate` but doesn't require ActiveSupport.
4
+ # Provide a minimal fallback for plain Ruby environments.
5
+ unless Module.method_defined?(:delegate)
6
+ class Module
7
+ def delegate(*methods, to:, prefix: nil, allow_nil: false, private: false, **_options)
8
+ target_method = to.to_sym
9
+
10
+ methods.each do |method_name|
11
+ delegated_method = delegated_method_name(method_name, target_method, prefix)
12
+
13
+ define_method(delegated_method) do |*args, **kwargs, &block|
14
+ target = public_send(target_method)
15
+ if target.nil?
16
+ return nil if allow_nil
17
+
18
+ raise NoMethodError,
19
+ "#{self.class}##{delegated_method} delegated to ##{target_method}, but ##{target_method} is nil"
20
+ end
21
+
22
+ if kwargs.empty?
23
+ target.public_send(method_name, *args, &block)
24
+ else
25
+ target.public_send(method_name, *args, **kwargs, &block)
26
+ end
27
+ end
28
+
29
+ private delegated_method if binding.local_variable_get(:private)
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def delegated_method_name(method_name, target_method, prefix)
36
+ case prefix
37
+ when true
38
+ :"#{target_method}_#{method_name}"
39
+ when String, Symbol
40
+ :"#{prefix}_#{method_name}"
41
+ else
42
+ method_name
43
+ end
44
+ end
45
+ end
46
+ end
47
+
3
48
  require "ruby_llm"
4
49
 
5
50
  require_relative "skills/version"
@@ -10,14 +55,16 @@ require_relative "skills/skill"
10
55
  require_relative "skills/loader"
11
56
  require_relative "skills/filesystem_loader"
12
57
  require_relative "skills/chat_extensions"
58
+ require_relative "skills/agent_extensions"
13
59
 
14
60
  # Load Rails integration when Rails is available
15
61
  require_relative "skills/railtie" if defined?(Rails::Railtie)
16
62
 
17
63
  # Extend RubyLLM::Chat with skill methods
18
- RubyLLM::Chat.include(RubyLlm::Skills::ChatExtensions)
64
+ RubyLLM::Chat.include(RubyLLM::Skills::ChatExtensions)
65
+ RubyLLM::Agent.include(RubyLLM::Skills::AgentExtensions)
19
66
 
20
- module RubyLlm
67
+ module RubyLLM
21
68
  module Skills
22
69
  class << self
23
70
  attr_accessor :default_path
@@ -27,7 +74,7 @@ module RubyLlm
27
74
  # @param path [String] path to skills directory (defaults to default_path)
28
75
  # @return [FilesystemLoader] loader for the directory
29
76
  # @example
30
- # RubyLlm::Skills.from_directory("app/skills")
77
+ # RubyLLM::Skills.from_directory("app/skills")
31
78
  def from_directory(path = default_path)
32
79
  FilesystemLoader.new(path)
33
80
  end
@@ -38,7 +85,7 @@ module RubyLlm
38
85
  # @return [Skill] the loaded skill
39
86
  # @raise [LoadError] if SKILL.md not found
40
87
  # @example
41
- # RubyLlm::Skills.load("app/skills/my-skill")
88
+ # RubyLLM::Skills.load("app/skills/my-skill")
42
89
  def load(path)
43
90
  skill_md = File.join(path, "SKILL.md")
44
91
  raise LoadError, "SKILL.md not found in #{path}" unless File.exist?(skill_md)
@@ -52,7 +99,7 @@ module RubyLlm
52
99
  # @param records [ActiveRecord::Relation, Array] collection of skill records
53
100
  # @return [DatabaseLoader] loader for the records
54
101
  # @example
55
- # RubyLlm::Skills.from_database(Skill.where(active: true))
102
+ # RubyLLM::Skills.from_database(Skill.where(active: true))
56
103
  def from_database(records)
57
104
  require_relative "skills/database_loader"
58
105
  DatabaseLoader.new(records)
@@ -63,9 +110,9 @@ module RubyLlm
63
110
  # @param loaders [Array<Loader>] loaders to combine
64
111
  # @return [CompositeLoader] combined loader
65
112
  # @example
66
- # RubyLlm::Skills.compose(
67
- # RubyLlm::Skills.from_directory("app/skills"),
68
- # RubyLlm::Skills.from_database(Skill.all)
113
+ # RubyLLM::Skills.compose(
114
+ # RubyLLM::Skills.from_directory("app/skills"),
115
+ # RubyLLM::Skills.from_database(Skill.all)
69
116
  # )
70
117
  def compose(*loaders)
71
118
  require_relative "skills/composite_loader"
metadata CHANGED
@@ -1,28 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_llm-skills
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kieran Klaassen
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-01-16 00:00:00.000000000 Z
10
+ date: 2026-02-17 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: ruby_llm
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
- - - ">="
16
+ - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '1.10'
18
+ version: '1.12'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
- - - ">="
23
+ - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '1.10'
25
+ version: '1.12'
26
26
  description: Load, validate, and integrate Agent Skills with RubyLLM. Supports the
27
27
  open Agent Skills specification for progressive skill discovery and loading from
28
28
  filesystem, zip archives, and databases.
@@ -35,9 +35,11 @@ files:
35
35
  - CHANGELOG.md
36
36
  - LICENSE.txt
37
37
  - README.md
38
+ - lib/generators/skill/USAGE
38
39
  - lib/generators/skill/skill_generator.rb
39
40
  - lib/generators/skill/templates/SKILL.md.tt
40
41
  - lib/ruby_llm/skills.rb
42
+ - lib/ruby_llm/skills/agent_extensions.rb
41
43
  - lib/ruby_llm/skills/chat_extensions.rb
42
44
  - lib/ruby_llm/skills/composite_loader.rb
43
45
  - lib/ruby_llm/skills/database_loader.rb