aia 0.9.11 → 0.9.13

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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +2 -0
  3. data/.version +1 -1
  4. data/CHANGELOG.md +66 -2
  5. data/README.md +133 -4
  6. data/docs/advanced-prompting.md +721 -0
  7. data/docs/cli-reference.md +582 -0
  8. data/docs/configuration.md +347 -0
  9. data/docs/contributing.md +332 -0
  10. data/docs/directives-reference.md +490 -0
  11. data/docs/examples/index.md +277 -0
  12. data/docs/examples/mcp/index.md +479 -0
  13. data/docs/examples/prompts/analysis/index.md +78 -0
  14. data/docs/examples/prompts/automation/index.md +108 -0
  15. data/docs/examples/prompts/development/index.md +125 -0
  16. data/docs/examples/prompts/index.md +333 -0
  17. data/docs/examples/prompts/learning/index.md +127 -0
  18. data/docs/examples/prompts/writing/index.md +62 -0
  19. data/docs/examples/tools/index.md +292 -0
  20. data/docs/faq.md +414 -0
  21. data/docs/guides/available-models.md +366 -0
  22. data/docs/guides/basic-usage.md +477 -0
  23. data/docs/guides/chat.md +474 -0
  24. data/docs/guides/executable-prompts.md +417 -0
  25. data/docs/guides/first-prompt.md +454 -0
  26. data/docs/guides/getting-started.md +455 -0
  27. data/docs/guides/image-generation.md +507 -0
  28. data/docs/guides/index.md +46 -0
  29. data/docs/guides/models.md +507 -0
  30. data/docs/guides/tools.md +856 -0
  31. data/docs/index.md +173 -0
  32. data/docs/installation.md +238 -0
  33. data/docs/mcp-integration.md +612 -0
  34. data/docs/prompt_management.md +579 -0
  35. data/docs/security.md +629 -0
  36. data/docs/tools-and-mcp-examples.md +1186 -0
  37. data/docs/workflows-and-pipelines.md +563 -0
  38. data/examples/tools/mcp/github_mcp_server.json +11 -0
  39. data/examples/tools/mcp/imcp.json +7 -0
  40. data/lib/aia/chat_processor_service.rb +38 -7
  41. data/lib/aia/config/base.rb +224 -0
  42. data/lib/aia/config/cli_parser.rb +418 -0
  43. data/lib/aia/config/defaults.rb +88 -0
  44. data/lib/aia/config/file_loader.rb +131 -0
  45. data/lib/aia/config/validator.rb +184 -0
  46. data/lib/aia/config.rb +10 -860
  47. data/lib/aia/directive_processor.rb +27 -372
  48. data/lib/aia/directives/configuration.rb +114 -0
  49. data/lib/aia/directives/execution.rb +37 -0
  50. data/lib/aia/directives/models.rb +178 -0
  51. data/lib/aia/directives/registry.rb +120 -0
  52. data/lib/aia/directives/utility.rb +70 -0
  53. data/lib/aia/directives/web_and_file.rb +71 -0
  54. data/lib/aia/prompt_handler.rb +23 -3
  55. data/lib/aia/ruby_llm_adapter.rb +367 -130
  56. data/lib/aia/session.rb +54 -18
  57. data/lib/aia/ui_presenter.rb +206 -0
  58. data/lib/aia/utility.rb +12 -8
  59. data/lib/aia.rb +11 -2
  60. data/lib/extensions/ruby_llm/.irbrc +56 -0
  61. data/mkdocs.yml +165 -0
  62. metadata +79 -37
  63. data/_notes.txt +0 -231
  64. /data/{images → docs/assets/images}/aia.png +0 -0
@@ -0,0 +1,224 @@
1
+ # lib/aia/config/base.rb
2
+
3
+ require 'ostruct'
4
+ require 'date'
5
+ require_relative 'defaults'
6
+ require_relative 'cli_parser'
7
+ require_relative 'file_loader'
8
+ require_relative 'validator'
9
+
10
+ module AIA
11
+ module ConfigModules
12
+ module Base
13
+ class << self
14
+ # Delegate to other config modules
15
+ def cli_options
16
+ CLIParser.cli_options
17
+ end
18
+
19
+ def cf_options(file)
20
+ FileLoader.cf_options(file)
21
+ end
22
+
23
+ def dump_config(config, file)
24
+ FileLoader.dump_config(config, file)
25
+ end
26
+
27
+ def generate_completion_script(shell)
28
+ FileLoader.generate_completion_script(shell)
29
+ end
30
+
31
+ def tailor_the_config(config)
32
+ Validator.tailor_the_config(config)
33
+ end
34
+
35
+ def validate_pipeline_prompts(config)
36
+ Validator.validate_pipeline_prompts(config)
37
+ end
38
+
39
+ def normalize_boolean_flag(config, flag)
40
+ Validator.normalize_boolean_flag(config, flag)
41
+ end
42
+
43
+ def process_tools_option(path_list, config)
44
+ CLIParser.process_tools_option(path_list, config)
45
+ end
46
+
47
+ def validate_and_set_context_files(config, remaining_args)
48
+ Validator.validate_and_set_context_files(config, remaining_args)
49
+ end
50
+
51
+ def setup_mode_options(opts, config)
52
+ CLIParser.setup_mode_options(opts, config)
53
+ end
54
+
55
+ def parse_config_content(content, ext)
56
+ FileLoader.parse_config_content(content, ext)
57
+ end
58
+
59
+ def normalize_last_refresh_date(config)
60
+ FileLoader.normalize_last_refresh_date(config)
61
+ end
62
+
63
+ def process_prompt_id_from_args(config, remaining_args)
64
+ Validator.process_prompt_id_from_args(config, remaining_args)
65
+ end
66
+
67
+ def process_role_configuration(config)
68
+ Validator.process_role_configuration(config)
69
+ end
70
+
71
+ def prepare_pipeline(config)
72
+ Validator.prepare_pipeline(config)
73
+ end
74
+
75
+ def setup_model_options(opts, config)
76
+ CLIParser.setup_model_options(opts, config)
77
+ end
78
+
79
+ def setup_ai_parameters(opts, config)
80
+ CLIParser.setup_ai_parameters(opts, config)
81
+ end
82
+
83
+ def read_and_process_config_file(file)
84
+ FileLoader.read_and_process_config_file(file)
85
+ end
86
+
87
+ def process_stdin_content
88
+ Validator.process_stdin_content
89
+ end
90
+
91
+ def process_allowed_tools_option(tools_list, config)
92
+ CLIParser.process_allowed_tools_option(tools_list, config)
93
+ end
94
+
95
+ def process_rejected_tools_option(tools_list, config)
96
+ CLIParser.process_rejected_tools_option(tools_list, config)
97
+ end
98
+
99
+ def normalize_boolean_flags(config)
100
+ Validator.normalize_boolean_flags(config)
101
+ end
102
+
103
+ def handle_executable_prompt(config)
104
+ Validator.handle_executable_prompt(config)
105
+ end
106
+
107
+ def handle_fuzzy_search_prompt_id(config)
108
+ Validator.handle_fuzzy_search_prompt_id(config)
109
+ end
110
+
111
+ def create_option_parser(config)
112
+ CLIParser.create_option_parser(config)
113
+ end
114
+
115
+ def apply_file_config_to_struct(config, file_config)
116
+ FileLoader.apply_file_config_to_struct(config, file_config)
117
+ end
118
+
119
+ def configure_prompt_manager(config)
120
+ Validator.configure_prompt_manager(config)
121
+ end
122
+
123
+ def setup
124
+ default_config = Defaults::DEFAULT_CONFIG.dup
125
+ cli_config = cli_options
126
+ envar_config = envar_options(default_config, cli_config)
127
+
128
+ file = envar_config.config_file unless envar_config.config_file.nil?
129
+ file = cli_config.config_file unless cli_config.config_file.nil?
130
+
131
+ cf_config = cf_options(file)
132
+
133
+ config = OpenStruct.merge(
134
+ default_config,
135
+ cf_config || {},
136
+ envar_config || {},
137
+ cli_config || {}
138
+ )
139
+
140
+ config = tailor_the_config(config)
141
+ load_libraries(config)
142
+ load_tools(config)
143
+
144
+ if config.dump_file
145
+ dump_config(config, config.dump_file)
146
+ end
147
+
148
+ config
149
+ end
150
+
151
+ def load_libraries(config)
152
+ return if config.require_libs.empty?
153
+
154
+ exit_on_error = false
155
+
156
+ config.require_libs.each do |library|
157
+ begin
158
+ require(library)
159
+ rescue => e
160
+ STDERR.puts "Error loading library '#{library}' #{e.message}"
161
+ exit_on_error = true
162
+ end
163
+ end
164
+
165
+ exit(1) if exit_on_error
166
+
167
+ config
168
+ end
169
+
170
+ def load_tools(config)
171
+ return if config.tool_paths.empty?
172
+
173
+ require_all_tools(config)
174
+
175
+ config
176
+ end
177
+
178
+ def require_all_tools(config)
179
+ exit_on_error = false
180
+
181
+ config.tool_paths.each do |tool_path|
182
+ begin
183
+ # expands path based on PWD
184
+ absolute_tool_path = File.expand_path(tool_path)
185
+ require(absolute_tool_path)
186
+ rescue => e
187
+ STDERR.puts "Error loading tool '#{tool_path}' #{e.message}"
188
+ exit_on_error = true
189
+ end
190
+ end
191
+
192
+ exit(1) if exit_on_error
193
+ end
194
+
195
+ # envar values are always String object so need other config
196
+ # layers to know the prompter type for each key's value
197
+ def envar_options(default, cli_config)
198
+ config = OpenStruct.merge(default, cli_config)
199
+ envars = ENV.keys.select { |key, _| key.start_with?('AIA_') }
200
+ envars.each do |envar|
201
+ key = envar.sub(/^AIA_/, '').downcase.to_sym
202
+ value = ENV[envar]
203
+
204
+ value = case config[key]
205
+ when TrueClass, FalseClass
206
+ value.downcase == 'true'
207
+ when Integer
208
+ value.to_i
209
+ when Float
210
+ value.to_f
211
+ when Array
212
+ value.split(',').map(&:strip)
213
+ else
214
+ value # defaults to String
215
+ end
216
+ config[key] = value
217
+ end
218
+
219
+ config
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,418 @@
1
+ # lib/aia/config/cli_parser.rb
2
+
3
+ require 'optparse'
4
+ require 'ostruct'
5
+
6
+ module AIA
7
+ module ConfigModules
8
+ module CLIParser
9
+ class << self
10
+ def cli_options
11
+ config = OpenStruct.new
12
+
13
+ begin
14
+ opt_parser = create_option_parser(config)
15
+ opt_parser.parse!
16
+ rescue => e
17
+ STDERR.puts "ERROR: #{e.message}"
18
+ STDERR.puts " use --help for usage report"
19
+ exit 1
20
+ end
21
+
22
+ parse_remaining_arguments(opt_parser, config)
23
+ config
24
+ end
25
+
26
+ def create_option_parser(config)
27
+ OptionParser.new do |opts|
28
+ setup_banner(opts)
29
+ setup_mode_options(opts, config)
30
+ setup_adapter_options(opts, config)
31
+ setup_model_options(opts, config)
32
+ setup_file_options(opts, config)
33
+ setup_prompt_options(opts, config)
34
+ setup_ai_parameters(opts, config)
35
+ setup_audio_image_options(opts, config)
36
+ setup_tool_options(opts, config)
37
+ setup_utility_options(opts, config)
38
+ end
39
+ end
40
+
41
+ def setup_banner(opts)
42
+ opts.banner = "Usage: aia [options] [PROMPT_ID] [CONTEXT_FILE]*\n" +
43
+ " aia --chat [PROMPT_ID] [CONTEXT_FILE]*\n" +
44
+ " aia --chat [CONTEXT_FILE]*"
45
+ end
46
+
47
+ def setup_mode_options(opts, config)
48
+ opts.on("--chat", "Begin a chat session with the LLM after processing all prompts in the pipeline.") do
49
+ config.chat = true
50
+ puts "Debug: Setting chat mode to true" if config.debug
51
+ end
52
+
53
+ opts.on("-f", "--fuzzy", "Use fuzzy matching for prompt search") do
54
+ unless system("which fzf > /dev/null 2>&1")
55
+ STDERR.puts "Error: 'fzf' is not installed. Please install 'fzf' to use the --fuzzy option."
56
+ exit 1
57
+ end
58
+ config.fuzzy = true
59
+ end
60
+
61
+ opts.on("--terse", "Adds a special instruction to the prompt asking the AI to keep responses short and to the point") do
62
+ config.terse = true
63
+ end
64
+ end
65
+
66
+ def setup_adapter_options(opts, config)
67
+ opts.on("--adapter ADAPTER", "Interface that adapts AIA to the LLM") do |adapter|
68
+ adapter.downcase!
69
+ valid_adapters = %w[ ruby_llm ] # NOTE: Add additional adapters here when needed
70
+ if valid_adapters.include? adapter
71
+ config.adapter = adapter
72
+ else
73
+ STDERR.puts "ERROR: Invalid adapter #{adapter} must be one of these: #{valid_adapters.join(', ')}"
74
+ exit 1
75
+ end
76
+ end
77
+
78
+ opts.on('--available_models [QUERY]', 'List (then exit) available models that match the optional query - a comma separated list of AND components like: openai,mini') do |query|
79
+ list_available_models(query)
80
+ end
81
+ end
82
+
83
+ def setup_model_options(opts, config)
84
+ opts.on("-m MODEL", "--model MODEL", "Name of the LLM model(s) to use (comma-separated for multiple models)") do |model|
85
+ config.model = model.split(',').map(&:strip)
86
+ end
87
+
88
+ opts.on("--[no-]consensus", "Enable/disable consensus mode for multi-model responses (default: show individual responses)") do |consensus|
89
+ config.consensus = consensus
90
+ end
91
+
92
+ opts.on("--sm", "--speech_model MODEL", "Speech model to use") do |model|
93
+ config.speech_model = model
94
+ end
95
+
96
+ opts.on("--tm", "--transcription_model MODEL", "Transcription model to use") do |model|
97
+ config.transcription_model = model
98
+ end
99
+ end
100
+
101
+ def setup_file_options(opts, config)
102
+ opts.on("-c", "--config_file FILE", "Load config file") do |file|
103
+ FileLoader.load_config_file(file, config)
104
+ end
105
+
106
+ opts.on("-o", "--[no-]out_file [FILE]", "Output file (default: temp.md)") do |file|
107
+ if file == false # --no-out_file was used
108
+ config.out_file = nil
109
+ elsif file.nil? # No argument provided
110
+ config.out_file = 'temp.md'
111
+ else # File name provided
112
+ config.out_file = File.expand_path(file, Dir.pwd)
113
+ end
114
+ end
115
+
116
+ opts.on("-a", "--[no-]append", "Append to output file instead of overwriting") do |append|
117
+ config.append = append
118
+ end
119
+
120
+ opts.on("-l", "--[no-]log_file [FILE]", "Log file") do |file|
121
+ config.log_file = file
122
+ end
123
+
124
+ opts.on("--md", "--[no-]markdown", "Format with Markdown") do |md|
125
+ config.markdown = md
126
+ end
127
+ end
128
+
129
+ def setup_prompt_options(opts, config)
130
+ opts.on("--prompts_dir DIR", "Directory containing prompt files") do |dir|
131
+ config.prompts_dir = dir
132
+ end
133
+
134
+ opts.on("--roles_prefix PREFIX", "Subdirectory name for role files (default: roles)") do |prefix|
135
+ config.roles_prefix = prefix
136
+ end
137
+
138
+ opts.on("-r", "--role ROLE_ID", "Role ID to prepend to prompt") do |role|
139
+ config.role = role
140
+ end
141
+
142
+ opts.on("-n", "--next PROMPT_ID", "Next prompt to process") do |next_prompt|
143
+ config.pipeline ||= []
144
+ config.pipeline << next_prompt
145
+ end
146
+
147
+ opts.on("-p PROMPTS", "--pipeline PROMPTS", "Pipeline of comma-seperated prompt IDs to process") do |pipeline|
148
+ config.pipeline ||= []
149
+ config.pipeline += pipeline.split(',').map(&:strip)
150
+ end
151
+
152
+ opts.on("-x", "--[no-]exec", "Used to designate an executable prompt file") do |value|
153
+ config.executable_prompt = value
154
+ end
155
+
156
+ opts.on("--system_prompt PROMPT_ID", "System prompt ID to use for chat sessions") do |prompt_id|
157
+ config.system_prompt = prompt_id
158
+ end
159
+
160
+ opts.on('--regex pattern', 'Regex pattern to extract parameters from prompt text') do |pattern|
161
+ config.parameter_regex = pattern
162
+ end
163
+ end
164
+
165
+ def setup_ai_parameters(opts, config)
166
+ opts.on("-t", "--temperature TEMP", Float, "Temperature for text generation") do |temp|
167
+ config.temperature = temp
168
+ end
169
+
170
+ opts.on("--max_tokens TOKENS", Integer, "Maximum tokens for text generation") do |tokens|
171
+ config.max_tokens = tokens
172
+ end
173
+
174
+ opts.on("--top_p VALUE", Float, "Top-p sampling value") do |value|
175
+ config.top_p = value
176
+ end
177
+
178
+ opts.on("--frequency_penalty VALUE", Float, "Frequency penalty") do |value|
179
+ config.frequency_penalty = value
180
+ end
181
+
182
+ opts.on("--presence_penalty VALUE", Float, "Presence penalty") do |value|
183
+ config.presence_penalty = value
184
+ end
185
+ end
186
+
187
+ def setup_audio_image_options(opts, config)
188
+ opts.on("--speak", "Simple implementation. Uses the speech model to convert text to audio, then plays the audio. Fun with --chat. Supports configuration of speech model and voice.") do
189
+ config.speak = true
190
+ end
191
+
192
+ opts.on("--voice VOICE", "Voice to use for speech") do |voice|
193
+ config.voice = voice
194
+ end
195
+
196
+ opts.on("--is", "--image_size SIZE", "Image size for image generation") do |size|
197
+ config.image_size = size
198
+ end
199
+
200
+ opts.on("--iq", "--image_quality QUALITY", "Image quality for image generation") do |quality|
201
+ config.image_quality = quality
202
+ end
203
+
204
+ opts.on("--style", "--image_style STYLE", "Style for image generation") do |style|
205
+ config.image_style = style
206
+ end
207
+ end
208
+
209
+ def setup_tool_options(opts, config)
210
+ opts.on("--rq LIBS", "--require LIBS", "Ruby libraries to require for Ruby directive") do |libs|
211
+ config.require_libs ||= []
212
+ config.require_libs += libs.split(',')
213
+ end
214
+
215
+ opts.on("--tools PATH_LIST", "Add a tool(s)") do |a_path_list|
216
+ process_tools_option(a_path_list, config)
217
+ end
218
+
219
+ opts.on("--at", "--allowed_tools TOOLS_LIST", "Allow only these tools to be used") do |tools_list|
220
+ process_allowed_tools_option(tools_list, config)
221
+ end
222
+
223
+ opts.on("--rt", "--rejected_tools TOOLS_LIST", "Reject these tools") do |tools_list|
224
+ process_rejected_tools_option(tools_list, config)
225
+ end
226
+ end
227
+
228
+ def setup_utility_options(opts, config)
229
+ opts.on("-d", "--debug", "Enable debug output") do
230
+ config.debug = $DEBUG_ME = true
231
+ end
232
+
233
+ opts.on("--no-debug", "Disable debug output") do
234
+ config.debug = $DEBUG_ME = false
235
+ end
236
+
237
+ opts.on("-v", "--[no-]verbose", "Be verbose") do |value|
238
+ config.verbose = value
239
+ end
240
+
241
+ opts.on("--refresh DAYS", Integer, "Refresh models database interval in days") do |days|
242
+ config.refresh = days || 0
243
+ end
244
+
245
+ opts.on("--dump FILE", "Dump config to file") do |file|
246
+ config.dump_file = file
247
+ end
248
+
249
+ opts.on("--completion SHELL", "Show completion script for bash|zsh|fish - default is nil") do |shell|
250
+ config.completion = shell
251
+ end
252
+
253
+ opts.on("--metrics", "Display token usage in chat mode") do
254
+ config.show_metrics = true
255
+ end
256
+
257
+ opts.on("--cost", "Include cost calculations with metrics (requires --metrics)") do
258
+ config.show_cost = true
259
+ config.show_metrics = true # Automatically enable metrics when cost is requested
260
+ end
261
+
262
+ opts.on("--version", "Show version") do
263
+ puts AIA::VERSION
264
+ exit
265
+ end
266
+
267
+ opts.on("-h", "--help", "Prints this help") do
268
+ puts <<~HELP
269
+
270
+ AIA your AI Assistant
271
+ - designed for generative AI workflows,
272
+ - effortlessly manage AI prompts,
273
+ - integrate seamlessly with shell and embedded Ruby (ERB),
274
+ - run batch processes,
275
+ - engage in interactive chats,
276
+ - with user defined directives, tools and MCP clients.
277
+
278
+ HELP
279
+
280
+ puts opts
281
+
282
+ puts <<~EXTRA
283
+
284
+ Explore Further:
285
+ - AIA Report an Issue: https://github.com/MadBomber/aia/issues
286
+ - AIA Documentation: https://github.com/MadBomber/aia/blob/main/README.md
287
+ - AIA GitHub Repository: https://github.com/MadBomber/aia
288
+ - PromptManager Docs: https://github.com/MadBomber/prompt_manager/blob/main/README.md
289
+ - ERB Documentation: https://rubyapi.org/o/erb
290
+ - RubyLLM Tool Docs: https://rubyllm.com/guides/tools
291
+ - MCP Client Docs: https://github.com/patvice/ruby_llm-mcp/blob/main/README.md
292
+
293
+ EXTRA
294
+
295
+ exit
296
+ end
297
+ end
298
+
299
+ def list_available_models(query)
300
+ # SMELL: mostly duplications the code in the vailable_models directive
301
+ # assumes that the adapter is for the ruby_llm gem
302
+ # should this be moved to the Utilities class as a common method?
303
+
304
+ if query.nil?
305
+ query = []
306
+ else
307
+ query = query.split(',')
308
+ end
309
+
310
+ header = "\nAvailable LLMs"
311
+ header += " for #{query.join(' and ')}" if query
312
+
313
+ puts header + ':'
314
+ puts
315
+
316
+ q1 = query.select{|q| q.include?('_to_')}.map{|q| ':'==q[0] ? q[1...] : q}
317
+ q2 = query.reject{|q| q.include?('_to_')}
318
+
319
+ counter = 0
320
+
321
+ RubyLLM.models.all.each do |llm|
322
+ inputs = llm.modalities.input.join(',')
323
+ outputs = llm.modalities.output.join(',')
324
+ entry = "- #{llm.id} (#{llm.provider}) #{inputs} to #{outputs}"
325
+
326
+ if query.nil? || query.empty?
327
+ counter += 1
328
+ puts entry
329
+ next
330
+ end
331
+
332
+ show_it = true
333
+ q1.each{|q| show_it &&= llm.modalities.send("#{q}?")}
334
+ q2.each{|q| show_it &&= entry.include?(q)}
335
+
336
+ if show_it
337
+ counter += 1
338
+ puts entry
339
+ end
340
+ end
341
+
342
+ puts if counter > 0
343
+ puts "#{counter} LLMs matching your query"
344
+ puts
345
+
346
+ exit
347
+ end
348
+
349
+ def parse_remaining_arguments(opt_parser, config)
350
+ args = ARGV.dup
351
+
352
+ # Parse the command line arguments
353
+ begin
354
+ config.remaining_args = opt_parser.parse(args)
355
+ rescue OptionParser::InvalidOption => e
356
+ puts e.message
357
+ puts opt_parser
358
+ exit 1
359
+ end
360
+ end
361
+
362
+ def process_tools_option(a_path_list, config)
363
+ config.tool_paths ||= []
364
+
365
+ if a_path_list.empty?
366
+ STDERR.puts "No list of paths for --tools option"
367
+ exit 1
368
+ else
369
+ paths = a_path_list.split(',').map(&:strip).uniq
370
+ end
371
+
372
+ paths.each do |a_path|
373
+ if File.exist?(a_path)
374
+ if File.file?(a_path)
375
+ if '.rb' == File.extname(a_path)
376
+ config.tool_paths << a_path
377
+ else
378
+ STDERR.puts "file should have *.rb extension: #{a_path}"
379
+ exit 1
380
+ end
381
+ elsif File.directory?(a_path)
382
+ rb_files = Dir.glob(File.join(a_path, '*.rb'))
383
+ config.tool_paths += rb_files
384
+ end
385
+ else
386
+ STDERR.puts "file/dir path is not valid: #{a_path}"
387
+ exit 1
388
+ end
389
+ end
390
+
391
+ config.tool_paths.uniq!
392
+ end
393
+
394
+ def process_allowed_tools_option(tools_list, config)
395
+ config.allowed_tools ||= []
396
+ if tools_list.empty?
397
+ STDERR.puts "No list of tool names provided for --allowed_tools option"
398
+ exit 1
399
+ else
400
+ config.allowed_tools += tools_list.split(',').map(&:strip)
401
+ config.allowed_tools.uniq!
402
+ end
403
+ end
404
+
405
+ def process_rejected_tools_option(tools_list, config)
406
+ config.rejected_tools ||= []
407
+ if tools_list.empty?
408
+ STDERR.puts "No list of tool names provided for --rejected_tools option"
409
+ exit 1
410
+ else
411
+ config.rejected_tools += tools_list.split(',').map(&:strip)
412
+ config.rejected_tools.uniq!
413
+ end
414
+ end
415
+ end
416
+ end
417
+ end
418
+ end