agent_skills_configurations 0.1.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.
@@ -0,0 +1,285 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zeitwerk"
4
+
5
+ loader = Zeitwerk::Loader.for_gem
6
+ loader.setup
7
+
8
+ # AgentSkillsConfigurations provides a unified interface for discovering and accessing
9
+ # skill configuration paths for various AI coding agents (Cursor, Claude Code, Codex, etc.).
10
+ #
11
+ # This library loads agent configurations from a YAML file and resolves platform-specific
12
+ # paths for skill directories, taking into account environment variables, user home directories,
13
+ # and fallback locations. It supports detection of which agents are currently detected on the
14
+ # system and provides convenient query methods for accessing agent information.
15
+ #
16
+ # == Overview
17
+ #
18
+ # Each AI coding agent has two types of skill directories:
19
+ #
20
+ # 1. *Project-level skills* (skills_dir): A relative path within a project where
21
+ # project-specific skills are stored (e.g., `.cursor/skills`, `.claude/skills`)
22
+ # 2. *Global skills* (global_skills_dir): An absolute path to the user's global
23
+ # skill repository shared across all projects (e.g., `~/.cursor/skills`)
24
+ #
25
+ # The library abstracts away the differences between agents, providing a consistent
26
+ # API for working with any supported agent type.
27
+ #
28
+ # == Configuration
29
+ #
30
+ # Agent configurations are defined in `agents.yml`, which contains:
31
+ #
32
+ # * Base path definitions with environment variable references and fallbacks
33
+ # * Agent entries with names, display names, skill paths, and detection rules
34
+ #
35
+ # Example YAML structure:
36
+ #
37
+ # base_paths:
38
+ # home:
39
+ # env_var: ""
40
+ # fallback: ""
41
+ # xdg_config:
42
+ # env_var: XDG_CONFIG_HOME
43
+ # fallback: ".config"
44
+ #
45
+ # agents:
46
+ # - name: cursor
47
+ # display_name: Cursor
48
+ # skills_dir: ".cursor/skills"
49
+ # base_path: home
50
+ # global_skills_path: ".cursor/skills"
51
+ # detect_paths:
52
+ # - ".cursor"
53
+ #
54
+ # == Finding Agents
55
+ #
56
+ # To get a specific agent configuration by name:
57
+ #
58
+ # agent = AgentSkillsConfigurations.find("cursor")
59
+ # agent.name # => "cursor"
60
+ # agent.display_name # => "Cursor"
61
+ # agent.skills_dir # => ".cursor/skills"
62
+ # agent.global_skills_dir # => "/Users/username/.cursor/skills"
63
+ #
64
+ # Finding an unknown agent raises an error:
65
+ #
66
+ # AgentSkillsConfigurations.find("unknown-agent")
67
+ # # => raises AgentSkillsConfigurations::Error: Unknown agent: unknown-agent
68
+ #
69
+ # == Listing All Agents
70
+ #
71
+ # To get all configured agents:
72
+ #
73
+ # all_agents = AgentSkillsConfigurations.all
74
+ # all_agents.map(&:name)
75
+ # # => ["amp", "claude-code", "cursor", "codex", "windsurf", ...]
76
+ #
77
+ # The result is cached for performance. Use {reset!} to clear the cache:
78
+ #
79
+ # AgentSkillsConfigurations.reset!
80
+ #
81
+ # == Detecting Agents
82
+ #
83
+ # To find which agents are detected on the current machine:
84
+ #
85
+ # detected = AgentSkillsConfigurations.detected
86
+ # detected.map(&:name)
87
+ # # => ["cursor", "claude-code"]
88
+ #
89
+ # Detection works by checking configured paths:
90
+ #
91
+ # * String paths: Checks if the path exists relative to the user's home directory
92
+ # * Hash paths with +cwd+: Checks relative to the current working directory
93
+ # * Hash paths with +base+: Resolves using the configured base path
94
+ # * Hash paths with +absolute+: Checks the absolute path directly
95
+ #
96
+ # Examples from the configuration:
97
+ #
98
+ # detect_paths:
99
+ # - ".cursor" # Check ~/.cursor exists
100
+ # - { cwd: ".agent" } # Check .agent exists in current dir
101
+ # - { base: home, path: ".codex" } # Check ~/.codex exists
102
+ # - { absolute: "/etc/codex" } # Check /etc/codex exists
103
+ #
104
+ # == Environment Variables
105
+ #
106
+ # Global skill paths are resolved using environment variables when available,
107
+ # with automatic fallbacks to default locations:
108
+ #
109
+ # * <tt>XDG_CONFIG_HOME</tt>: Used by Amp, Goose, and other XDG-compliant agents
110
+ # * <tt>CLAUDE_CONFIG_DIR</tt>: Used by Claude Code and OpenCode
111
+ # * <tt>CODEX_HOME</tt>: Used by Codex
112
+ #
113
+ # Example with XDG_CONFIG_HOME:
114
+ #
115
+ # ENV["XDG_CONFIG_HOME"] = "/custom/xdg"
116
+ # agent = AgentSkillsConfigurations.find("amp")
117
+ # agent.global_skills_dir # => "/custom/xdg/agents/skills"
118
+ #
119
+ # Without the environment variable, falls back to default:
120
+ #
121
+ # ENV["XDG_CONFIG_HOME"] = nil
122
+ # agent = AgentSkillsConfigurations.find("amp")
123
+ # agent.global_skills_dir # => "/Users/username/.config/agents/skills"
124
+ #
125
+ # == Path Resolution with Fallbacks
126
+ #
127
+ # Some agents support multiple fallback paths for global skills. The first
128
+ # existing path is used:
129
+ #
130
+ # agents:
131
+ # - name: moltbot
132
+ # global_skills_path: ".moltbot/skills"
133
+ # global_skills_path_fallbacks:
134
+ # - ".clawdbot/skills"
135
+ # - ".moltbot/skills"
136
+ #
137
+ # The library checks each candidate path in order and returns the first
138
+ # one that exists.
139
+ #
140
+ # == Error Handling
141
+ #
142
+ # The library raises {AgentSkillsConfigurations::Error} for configuration errors
143
+ # (unknown agents) and {Psych::SyntaxError} for invalid YAML syntax.
144
+ #
145
+ # @author Lucian Ghinda
146
+ # @since 0.1.0
147
+ module AgentSkillsConfigurations
148
+ # Base error type for configuration lookup and loading failures.
149
+ class Error < StandardError; end
150
+
151
+ class << self
152
+ # Find a configured agent by name.
153
+ #
154
+ # Returns an {Agent} value object containing the agent's name, display name,
155
+ # and resolved skill directory paths. This is the primary method for accessing
156
+ # agent configuration.
157
+ #
158
+ # @param name [String] agent name from `agents.yml`
159
+ # @return [Agent] resolved agent configuration
160
+ # @raise [Error] when the agent name is unknown
161
+ # @raise [Psych::SyntaxError] when the YAML configuration is invalid
162
+ #
163
+ # @example Find Cursor and access its paths
164
+ # agent = AgentSkillsConfigurations.find("cursor")
165
+ # agent.name # => "cursor"
166
+ # agent.display_name # => "Cursor"
167
+ # agent.skills_dir # => ".cursor/skills"
168
+ # agent.global_skills_dir # => "/Users/username/.cursor/skills"
169
+ #
170
+ # @example Find Claude Code with custom config directory
171
+ # ENV["CLAUDE_CONFIG_DIR"] = "/custom/claude"
172
+ # agent = AgentSkillsConfigurations.find("claude-code")
173
+ # agent.global_skills_dir # => "/custom/claude/skills"
174
+ #
175
+ # @example Error for unknown agent
176
+ # AgentSkillsConfigurations.find("unknown-agent")
177
+ # # => raises AgentSkillsConfigurations::Error: Unknown agent: unknown-agent
178
+ def find(name)
179
+ registry.find(name)
180
+ end
181
+
182
+ # Return all configured agents.
183
+ #
184
+ # Returns a frozen array of all {Agent} objects defined in the configuration.
185
+ # The result is cached for performance. Use {reset!} to clear the cache
186
+ # when you need fresh results (e.g., after changing environment variables).
187
+ #
188
+ # @return [Array<Agent>] all agents defined in `agents.yml`
189
+ # @raise [Psych::SyntaxError] when the YAML configuration is invalid
190
+ #
191
+ # @example List all agent names
192
+ # all_agents = AgentSkillsConfigurations.all
193
+ # all_agents.map(&:name)
194
+ # # => ["amp", "claude-code", "cursor", "codex", "windsurf", ...]
195
+ #
196
+ # @example Iterate through all agents
197
+ # AgentSkillsConfigurations.all.each do |agent|
198
+ # puts "#{agent.display_name}: #{agent.skills_dir}"
199
+ # end
200
+ #
201
+ # @example Access specific agent attributes
202
+ # all = AgentSkillsConfigurations.all
203
+ # cursor = all.find { |a| a.name == "cursor" }
204
+ # cursor.global_skills_dir # => "/Users/username/.cursor/skills"
205
+ #
206
+ # @see #reset! Clear cached agent lists
207
+ # @see #installed Get only installed agents
208
+ def all
209
+ registry.all
210
+ end
211
+
212
+ # Return agents that appear to be installed on this machine.
213
+ #
214
+ # Installation is detected by checking the paths configured in each agent's
215
+ # <tt>detect_paths</tt> configuration. Different detection strategies are supported:
216
+ #
217
+ # * String paths: Check if the path exists relative to user's home directory
218
+ # * Hash with +cwd+: Check relative to current working directory
219
+ # * Hash with +base+: Resolve using a configured base path
220
+ # * Hash with +absolute+: Check an absolute path directly
221
+ #
222
+ # The result is cached for performance. Use {reset!} to clear the cache.
223
+ #
224
+ # @return [Array<Agent>] agents matching their detect paths
225
+ # @raise [Psych::SyntaxError] when the YAML configuration is invalid
226
+ #
227
+ # @example Get list of installed agents
228
+ # installed = AgentSkillsConfigurations.installed
229
+ # installed.map(&:name)
230
+ # # => ["cursor", "claude-code"]
231
+ #
232
+ # @example Check if a specific agent is installed
233
+ # installed_names = AgentSkillsConfigurations.installed.map(&:name)
234
+ # installed_names.include?("cursor") # => true
235
+ # installed_names.include?("unknown") # => false
236
+ #
237
+ # @example Iterate through installed agents
238
+ # AgentSkillsConfigurations.installed.each do |agent|
239
+ # puts "#{agent.display_name} is installed"
240
+ # end
241
+ #
242
+ # @see #all Get all configured agents regardless of detection status
243
+ # @see #reset! Clear cached agent lists
244
+ def detected
245
+ registry.detected
246
+ end
247
+
248
+ # Clear cached agent lists.
249
+ #
250
+ # This method clears the internal caches for {all} and {detected} results.
251
+ # Use this when you need fresh data, such as:
252
+ #
253
+ # * After changing environment variables that affect path resolution
254
+ # * After agents' paths are created or removed
255
+ # * After modifying the YAML configuration file
256
+ #
257
+ # @return [void]
258
+ # @raise [Psych::SyntaxError] when the YAML configuration is invalid
259
+ #
260
+ # @example Reset cache after changing environment variable
261
+ # ENV["XDG_CONFIG_HOME"] = "/new/path"
262
+ # AgentSkillsConfigurations.reset!
263
+ # agent = AgentSkillsConfigurations.find("amp")
264
+ # agent.global_skills_dir # => "/new/path/agents/skills"
265
+ #
266
+ # @example Reset cache for fresh detection
267
+ # AgentSkillsConfigurations.reset!
268
+ # detected = AgentSkillsConfigurations.detected
269
+ #
270
+ # @see #all Returns cached all agents
271
+ # @see #detected Returns cached detected agents
272
+ def reset!
273
+ registry.reset
274
+ end
275
+
276
+ private
277
+
278
+ # Lazy-load the registry.
279
+ #
280
+ # @return [Registry]
281
+ def registry
282
+ @registry ||= Registry.new
283
+ end
284
+ end
285
+ end
@@ -0,0 +1,4 @@
1
+ module AgentSkillsConfigurations
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: agent_skills_configurations
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Lucian Ghinda
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: zeitwerk
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.6'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2.6'
26
+ description: A Ruby library that provides a unified interface for discovering and
27
+ accessing skill configuration paths for 49+ AI coding agents including Cursor, Claude
28
+ Code, Codex, Windsurf, and more. Handles platform-specific path resolution, environment
29
+ variable support, and automatic detection of installed agents.
30
+ email:
31
+ - lucianghinda@users.noreply.github.com
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - ".yardopts"
37
+ - CHANGELOG.md
38
+ - CODE_OF_CONDUCT.md
39
+ - LICENSE.txt
40
+ - README.md
41
+ - Rakefile
42
+ - doc/AgentSkillsConfigurations.md
43
+ - doc/AgentSkillsConfigurations/Agent.md
44
+ - doc/AgentSkillsConfigurations/Error.md
45
+ - doc/AgentSkillsConfigurations/Registry.md
46
+ - doc/index.csv
47
+ - lib/agent_skills_configurations.rb
48
+ - lib/agent_skills_configurations/agent.rb
49
+ - lib/agent_skills_configurations/agents.yml
50
+ - lib/agent_skills_configurations/registry.rb
51
+ - lib/agent_skills_configurations/version.rb
52
+ - sig/agent_skills_configurations.rbs
53
+ homepage: https://github.com/lucianghinda/agent_skills_configurations
54
+ licenses:
55
+ - MIT
56
+ metadata:
57
+ allowed_push_host: https://rubygems.org
58
+ homepage_uri: https://github.com/lucianghinda/agent_skills_configurations
59
+ source_code_uri: https://github.com/lucianghinda/agent_skills_configurations
60
+ changelog_uri: https://github.com/lucianghinda/agent_skills_configurations/blob/main/CHANGELOG.md
61
+ rubygems_mfa_required: 'true'
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: 3.4.0
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubygems_version: 4.0.3
77
+ specification_version: 4
78
+ summary: Unified interface for discovering AI coding agent skill paths
79
+ test_files: []