aia 0.9.11 → 0.9.12

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/.version +1 -1
  3. data/CHANGELOG.md +66 -2
  4. data/README.md +133 -4
  5. data/docs/advanced-prompting.md +721 -0
  6. data/docs/cli-reference.md +582 -0
  7. data/docs/configuration.md +347 -0
  8. data/docs/contributing.md +332 -0
  9. data/docs/directives-reference.md +490 -0
  10. data/docs/examples/index.md +277 -0
  11. data/docs/examples/mcp/index.md +479 -0
  12. data/docs/examples/prompts/analysis/index.md +78 -0
  13. data/docs/examples/prompts/automation/index.md +108 -0
  14. data/docs/examples/prompts/development/index.md +125 -0
  15. data/docs/examples/prompts/index.md +333 -0
  16. data/docs/examples/prompts/learning/index.md +127 -0
  17. data/docs/examples/prompts/writing/index.md +62 -0
  18. data/docs/examples/tools/index.md +292 -0
  19. data/docs/faq.md +414 -0
  20. data/docs/guides/available-models.md +366 -0
  21. data/docs/guides/basic-usage.md +477 -0
  22. data/docs/guides/chat.md +474 -0
  23. data/docs/guides/executable-prompts.md +417 -0
  24. data/docs/guides/first-prompt.md +454 -0
  25. data/docs/guides/getting-started.md +455 -0
  26. data/docs/guides/image-generation.md +507 -0
  27. data/docs/guides/index.md +46 -0
  28. data/docs/guides/models.md +507 -0
  29. data/docs/guides/tools.md +856 -0
  30. data/docs/index.md +173 -0
  31. data/docs/installation.md +238 -0
  32. data/docs/mcp-integration.md +612 -0
  33. data/docs/prompt_management.md +579 -0
  34. data/docs/security.md +629 -0
  35. data/docs/tools-and-mcp-examples.md +1186 -0
  36. data/docs/workflows-and-pipelines.md +563 -0
  37. data/examples/tools/mcp/github_mcp_server.json +11 -0
  38. data/examples/tools/mcp/imcp.json +7 -0
  39. data/lib/aia/chat_processor_service.rb +19 -3
  40. data/lib/aia/config/base.rb +224 -0
  41. data/lib/aia/config/cli_parser.rb +409 -0
  42. data/lib/aia/config/defaults.rb +88 -0
  43. data/lib/aia/config/file_loader.rb +131 -0
  44. data/lib/aia/config/validator.rb +184 -0
  45. data/lib/aia/config.rb +10 -860
  46. data/lib/aia/directive_processor.rb +27 -372
  47. data/lib/aia/directives/configuration.rb +114 -0
  48. data/lib/aia/directives/execution.rb +37 -0
  49. data/lib/aia/directives/models.rb +178 -0
  50. data/lib/aia/directives/registry.rb +120 -0
  51. data/lib/aia/directives/utility.rb +70 -0
  52. data/lib/aia/directives/web_and_file.rb +71 -0
  53. data/lib/aia/prompt_handler.rb +23 -3
  54. data/lib/aia/ruby_llm_adapter.rb +307 -128
  55. data/lib/aia/session.rb +27 -14
  56. data/lib/aia/utility.rb +12 -8
  57. data/lib/aia.rb +11 -2
  58. data/lib/extensions/ruby_llm/.irbrc +56 -0
  59. data/mkdocs.yml +165 -0
  60. metadata +77 -20
  61. /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,409 @@
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("--version", "Show version") do
254
+ puts AIA::VERSION
255
+ exit
256
+ end
257
+
258
+ opts.on("-h", "--help", "Prints this help") do
259
+ puts <<~HELP
260
+
261
+ AIA your AI Assistant
262
+ - designed for generative AI workflows,
263
+ - effortlessly manage AI prompts,
264
+ - integrate seamlessly with shell and embedded Ruby (ERB),
265
+ - run batch processes,
266
+ - engage in interactive chats,
267
+ - with user defined directives, tools and MCP clients.
268
+
269
+ HELP
270
+
271
+ puts opts
272
+
273
+ puts <<~EXTRA
274
+
275
+ Explore Further:
276
+ - AIA Report an Issue: https://github.com/MadBomber/aia/issues
277
+ - AIA Documentation: https://github.com/MadBomber/aia/blob/main/README.md
278
+ - AIA GitHub Repository: https://github.com/MadBomber/aia
279
+ - PromptManager Docs: https://github.com/MadBomber/prompt_manager/blob/main/README.md
280
+ - ERB Documentation: https://rubyapi.org/o/erb
281
+ - RubyLLM Tool Docs: https://rubyllm.com/guides/tools
282
+ - MCP Client Docs: https://github.com/patvice/ruby_llm-mcp/blob/main/README.md
283
+
284
+ EXTRA
285
+
286
+ exit
287
+ end
288
+ end
289
+
290
+ def list_available_models(query)
291
+ # SMELL: mostly duplications the code in the vailable_models directive
292
+ # assumes that the adapter is for the ruby_llm gem
293
+ # should this be moved to the Utilities class as a common method?
294
+
295
+ if query.nil?
296
+ query = []
297
+ else
298
+ query = query.split(',')
299
+ end
300
+
301
+ header = "\nAvailable LLMs"
302
+ header += " for #{query.join(' and ')}" if query
303
+
304
+ puts header + ':'
305
+ puts
306
+
307
+ q1 = query.select{|q| q.include?('_to_')}.map{|q| ':'==q[0] ? q[1...] : q}
308
+ q2 = query.reject{|q| q.include?('_to_')}
309
+
310
+ counter = 0
311
+
312
+ RubyLLM.models.all.each do |llm|
313
+ inputs = llm.modalities.input.join(',')
314
+ outputs = llm.modalities.output.join(',')
315
+ entry = "- #{llm.id} (#{llm.provider}) #{inputs} to #{outputs}"
316
+
317
+ if query.nil? || query.empty?
318
+ counter += 1
319
+ puts entry
320
+ next
321
+ end
322
+
323
+ show_it = true
324
+ q1.each{|q| show_it &&= llm.modalities.send("#{q}?")}
325
+ q2.each{|q| show_it &&= entry.include?(q)}
326
+
327
+ if show_it
328
+ counter += 1
329
+ puts entry
330
+ end
331
+ end
332
+
333
+ puts if counter > 0
334
+ puts "#{counter} LLMs matching your query"
335
+ puts
336
+
337
+ exit
338
+ end
339
+
340
+ def parse_remaining_arguments(opt_parser, config)
341
+ args = ARGV.dup
342
+
343
+ # Parse the command line arguments
344
+ begin
345
+ config.remaining_args = opt_parser.parse(args)
346
+ rescue OptionParser::InvalidOption => e
347
+ puts e.message
348
+ puts opt_parser
349
+ exit 1
350
+ end
351
+ end
352
+
353
+ def process_tools_option(a_path_list, config)
354
+ config.tool_paths ||= []
355
+
356
+ if a_path_list.empty?
357
+ STDERR.puts "No list of paths for --tools option"
358
+ exit 1
359
+ else
360
+ paths = a_path_list.split(',').map(&:strip).uniq
361
+ end
362
+
363
+ paths.each do |a_path|
364
+ if File.exist?(a_path)
365
+ if File.file?(a_path)
366
+ if '.rb' == File.extname(a_path)
367
+ config.tool_paths << a_path
368
+ else
369
+ STDERR.puts "file should have *.rb extension: #{a_path}"
370
+ exit 1
371
+ end
372
+ elsif File.directory?(a_path)
373
+ rb_files = Dir.glob(File.join(a_path, '*.rb'))
374
+ config.tool_paths += rb_files
375
+ end
376
+ else
377
+ STDERR.puts "file/dir path is not valid: #{a_path}"
378
+ exit 1
379
+ end
380
+ end
381
+
382
+ config.tool_paths.uniq!
383
+ end
384
+
385
+ def process_allowed_tools_option(tools_list, config)
386
+ config.allowed_tools ||= []
387
+ if tools_list.empty?
388
+ STDERR.puts "No list of tool names provided for --allowed_tools option"
389
+ exit 1
390
+ else
391
+ config.allowed_tools += tools_list.split(',').map(&:strip)
392
+ config.allowed_tools.uniq!
393
+ end
394
+ end
395
+
396
+ def process_rejected_tools_option(tools_list, config)
397
+ config.rejected_tools ||= []
398
+ if tools_list.empty?
399
+ STDERR.puts "No list of tool names provided for --rejected_tools option"
400
+ exit 1
401
+ else
402
+ config.rejected_tools += tools_list.split(',').map(&:strip)
403
+ config.rejected_tools.uniq!
404
+ end
405
+ end
406
+ end
407
+ end
408
+ end
409
+ end