aia 0.9.5 → 0.9.7

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.
data/examples/headlines CHANGED
@@ -1,21 +1,30 @@
1
- #!/usr/bin/env aia run --no-out_file
2
- # File: examples/headlines
1
+ #!/usr/bin/env aia run --no-out_file --exec
2
+ # NOTE: the --exec option is REQUIRED to run this executable prompt file
3
+ # This option signals that the contents of this file are to be appended
4
+ # to the contents of the given prompt ID file. In this case it is the
5
+ # "run" prompt ID's text file.
6
+ #
7
+ # All other AIA options are, well, optional. The --no-out_file is
8
+ # used here to cause the response to this executable prompt to be
9
+ # sent to STDOUT like a good little *nix CLI tool. Its not necessary.
10
+ # if you do not use it, the output will go to the default out_file.
11
+ # You could also specify a specific file to write the output to built
12
+ # but it is more convential to use the *nix STDOUT redirect "> output.md"
13
+ #
3
14
  # Desc: retrieves the news.google.com website
4
15
  # extracts and formats the headlines
5
- # and prints them to the console
16
+ # and prints them to STDOUT
17
+ #
18
+ # Method:
19
+ # There are several ways you can accomplish this task using
20
+ # the shell or ERB integration. For this example lets use
21
+ # a built-in directive.
6
22
 
7
- //config shell = true
8
- # //config
23
+ Extract and summarize the headlines from the following markdown:
9
24
 
10
- # Puts the webpage into index.html
11
- # //shell wget2 https://news.google.com
25
+ //webpage https://news.google.com
12
26
 
13
- $(wget2 https://news.google.com)
27
+ __END__
14
28
 
15
-
16
- # Lets hear the headlines as well as read them.
17
- //config speak = true
18
-
19
- Extract and summarize the headlines from the following text:
20
-
21
- $(html2text index.html)
29
+ The //webpage directive makes use of the website https://pure.md
30
+ to convert any given URL to a markdown file.
@@ -1,5 +1,6 @@
1
1
  # experiments/ai_misc/coding_agent_with_ruby_llm/tools/run_shell_command.rb
2
2
 
3
+ require "io/console"
3
4
  require "ruby_llm/tool"
4
5
 
5
6
  module Tools
@@ -8,10 +9,21 @@ module Tools
8
9
  param :command, desc: "The command to execute"
9
10
 
10
11
  def execute(command:)
11
- puts "AI wants to execute the following shell command: '#{command}'"
12
- print "Do you want to execute it? (y/n) "
13
- response = gets.chomp
14
- return { error: "User declined to execute the command" } unless response == "y"
12
+ print "\n\n"
13
+ puts "AI wants to execute the following shell command:"
14
+ puts "="*command.size
15
+ puts command
16
+ puts "="*command.size
17
+ print "\n\n"
18
+
19
+ sleep 0.5
20
+ print "Execute the command? (y/N):"
21
+ allowed = STDIN.getch == "y"
22
+
23
+ unless allowed
24
+ print "Command aborted" + " "*30 if defined?(AIA)
25
+ return { error: "User declined to execute the command" }
26
+ end
15
27
 
16
28
  `#{command}`
17
29
  rescue => e
data/images/aia.png ADDED
Binary file
@@ -62,7 +62,7 @@ module AIA
62
62
 
63
63
  # Only output to STDOUT if we're in chat mode
64
64
 
65
- if AIA.chat? || 'STDOUT' == AIA.config.out_file.upcase
65
+ if AIA.chat? || AIA.config.out_file.nil? || 'STDOUT' == AIA.config.out_file.upcase
66
66
  print "\nAI:\n "
67
67
  puts response
68
68
  else
@@ -94,21 +94,22 @@ module AIA
94
94
 
95
95
  def determine_operation_type
96
96
  mode = AIA.config.client.model.modalities
97
- if mode.supports?(:text_to_image)
97
+
98
+ if mode.text_to_image?
98
99
  :text_to_image
99
- elsif mode.supports?(:image_to_text)
100
+ elsif mode.image_to_text?
100
101
  :image_to_text
101
- elsif mode.supports?(:audio_to_text)
102
+ elsif mode.audio_to_text?
102
103
  :audio_to_text
103
- elsif mode.supports?(:text_to_audio)
104
+ elsif mode.text_to_audio?
104
105
  :text_to_audio
105
- elsif mode.supports?(:audio_to_audio)
106
+ elsif mode.audio_to_audio?
106
107
  :audio_to_audio
107
- elsif mode.supports?(:image_to_image)
108
+ elsif mode.image_to_image?
108
109
  :image_to_image
109
- elsif mode.supports?(:audio_to_image)
110
+ elsif mode.audio_to_image?
110
111
  :audio_to_image
111
- elsif mode.supports?(:image_to_audio)
112
+ elsif mode.image_to_audio?
112
113
  :image_to_audio
113
114
  else
114
115
  :text_to_text
data/lib/aia/config.rb CHANGED
@@ -124,6 +124,17 @@ module AIA
124
124
  remaining_args = config.remaining_args.dup
125
125
  config.remaining_args = nil
126
126
 
127
+ # Check for STDIN content
128
+ stdin_content = nil
129
+ if !STDIN.tty? && !STDIN.closed?
130
+ begin
131
+ stdin_content = STDIN.read
132
+ STDIN.reopen('/dev/tty') # Reopen STDIN for interactive use
133
+ rescue => _
134
+ # If we can't reopen, continue without error
135
+ end
136
+ end
137
+
127
138
  # Is first remaining argument a prompt ID?
128
139
  unless remaining_args.empty?
129
140
  maybe_id = remaining_args.first
@@ -134,6 +145,11 @@ module AIA
134
145
  end
135
146
  end
136
147
 
148
+ # Store STDIN content for later processing in session.rb
149
+ if stdin_content && !stdin_content.strip.empty?
150
+ config.stdin_content = stdin_content
151
+ end
152
+
137
153
  unless remaining_args.empty?
138
154
  bad_files = remaining_args.reject { |filename| AIA.good_file?(filename) }
139
155
  if bad_files.any?
@@ -141,9 +157,21 @@ module AIA
141
157
  exit 1
142
158
  end
143
159
 
144
- config.context_files = remaining_args
160
+ config.context_files ||= []
161
+ config.context_files += remaining_args
162
+ end
163
+
164
+ # Check if the last context file is an executable prompt
165
+ if config.executable_prompt &&
166
+ config.context_files &&
167
+ !config.context_files.empty?
168
+ config.executable_prompt_file = config.context_files.pop
145
169
  end
146
170
 
171
+ # TODO: Consider that if there is no prompt ID but there is an executable prompt
172
+ # then maybe that is all that is needed.
173
+
174
+
147
175
  if config.prompt_id.nil? && !config.chat && !config.fuzzy
148
176
  STDERR.puts "Error: A prompt ID is required unless using --chat, --fuzzy, or providing context files. Use -h or --help for help."
149
177
  exit 1
@@ -325,10 +353,68 @@ module AIA
325
353
  end
326
354
  end
327
355
 
356
+ 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|
357
+
358
+ # SMELL: mostly duplications the code in the vailable_models directive
359
+ # assumes that the adapter is for the ruby_llm gem
360
+ # should this be moved to the Utilities class as a common method?
361
+
362
+ if query.nil?
363
+ query = []
364
+ else
365
+ query = query.split(',')
366
+ end
367
+
368
+ header = "\nAvailable LLMs"
369
+ header += " for #{query.join(' and ')}" if query
370
+
371
+ puts header + ':'
372
+ puts
373
+
374
+ q1 = query.select{|q| q.include?('_to_')}.map{|q| ':'==q[0] ? q[1...] : q}
375
+ q2 = query.reject{|q| q.include?('_to_')}
376
+
377
+
378
+ # query = nil
379
+ counter = 0
380
+
381
+ RubyLLM.models.all.each do |llm|
382
+ inputs = llm.modalities.input.join(',')
383
+ outputs = llm.modalities.output.join(',')
384
+ entry = "- #{llm.id} (#{llm.provider}) #{inputs} to #{outputs}"
385
+
386
+ if query.nil? || query.empty?
387
+ counter += 1
388
+ puts entry
389
+ next
390
+ end
391
+
392
+ show_it = true
393
+ q1.each{|q| show_it &&= llm.modalities.send("#{q}?")}
394
+ q2.each{|q| show_it &&= entry.include?(q)}
395
+
396
+ if show_it
397
+ counter += 1
398
+ puts entry
399
+ end
400
+ end
401
+
402
+ puts if counter > 0
403
+ puts "#{counter} LLMs matching your query"
404
+ puts
405
+
406
+ exit
407
+ end
408
+
328
409
  opts.on("-m MODEL", "--model MODEL", "Name of the LLM model to use") do |model|
329
410
  config.model = model
330
411
  end
331
412
 
413
+ opts.on("-x", "--[no-]exec", "Used to designate an executable prompt file") do |value|
414
+ config.executable_prompt = value
415
+ end
416
+
417
+
332
418
  opts.on("--terse", "Adds a special instruction to the prompt asking the AI to keep responses short and to the point") do
333
419
  config.terse = true
334
420
  end
@@ -383,7 +469,13 @@ module AIA
383
469
  end
384
470
 
385
471
  opts.on("-o", "--[no-]out_file [FILE]", "Output file (default: temp.md)") do |file|
386
- config.out_file = file ? File.expand_path(file, Dir.pwd) : 'temp.md'
472
+ if file == false # --no-out_file was used
473
+ config.out_file = nil
474
+ elsif file.nil? # No argument provided
475
+ config.out_file = 'temp.md'
476
+ else # File name provided
477
+ config.out_file = File.expand_path(file, Dir.pwd)
478
+ end
387
479
  end
388
480
 
389
481
  opts.on("-a", "--[no-]append", "Append to output file instead of overwriting") do |append|
@@ -422,8 +514,8 @@ module AIA
422
514
  config.debug = $DEBUG_ME = false
423
515
  end
424
516
 
425
- opts.on("-v", "--verbose", "Be verbose") do
426
- config.verbose = true
517
+ opts.on("-v", "--[no-]verbose", "Be verbose") do |value|
518
+ config.verbose = value
427
519
  end
428
520
 
429
521
  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
@@ -1,6 +1,8 @@
1
1
  # lib/aia/directive_processor.rb
2
2
 
3
+ require 'active_support/all'
3
4
  require 'faraday'
5
+ require 'word_wrapper' # Pure ruby word wrapping
4
6
 
5
7
  module AIA
6
8
  class DirectiveProcessor
@@ -145,7 +147,7 @@ module AIA
145
147
  end
146
148
 
147
149
  desc "Specify the next prompt ID to process after this one"
148
- def next(args = [])
150
+ def next(args = [], context_manager=nil)
149
151
  if args.empty?
150
152
  ap AIA.config.next
151
153
  else
@@ -154,8 +156,33 @@ module AIA
154
156
  ''
155
157
  end
156
158
 
159
+ desc "Show a list of tools and their description"
160
+ def tools(args = [], context_manager=nil)
161
+ indent = 4
162
+ spaces = " "*indent
163
+ width = TTY::Screen.width - indent - 2
164
+
165
+ if !AIA.config.tools.empty?
166
+ puts
167
+ puts "Available Tools"
168
+ puts "==============="
169
+
170
+ AIA.config.tools.split(',').map(&:strip).each do |tool|
171
+ klass = tool.constantize
172
+ puts "\n#{klass.name}"
173
+ puts "-"*klass.name.size
174
+ puts WordWrapper::MinimumRaggedness.new(width, klass.description).wrap.split("\n").map{|s| spaces+s+"\n"}.join
175
+ end
176
+ else
177
+ puts "No tools configured"
178
+ end
179
+ puts
180
+
181
+ ''
182
+ end
183
+
157
184
  desc "Specify a sequence pf prompt IDs to process after this one"
158
- def pipeline(args = [])
185
+ def pipeline(args = [], context_manager=nil)
159
186
  if args.empty?
160
187
  ap AIA.config.pipeline
161
188
  else
@@ -299,16 +326,21 @@ module AIA
299
326
  desc "All Available models or query on [partial LLM or provider name] Examples: //llms ; //llms openai ; //llms claude"
300
327
  def available_models( args=nil, context_manager=nil)
301
328
  query = args
302
- header = "Available LLMs"
303
329
 
304
- if query
305
- header += " for #{query.join(' and ')}"
330
+ if 1 == query.size
331
+ query = query.first.split(',')
306
332
  end
307
333
 
334
+ header = "\nAvailable LLMs"
335
+ header += " for #{query.join(' and ')}" if query
336
+
308
337
  puts header + ':'
338
+ puts
309
339
 
310
- q1 = query.select{|q| !q.start_with?(':')}
311
- q2 = query.select{|q| q.start_with?(':')}
340
+ q1 = query.select{|q| q.include?('_to_')}.map{|q| ':'==q[0] ? q[1...] : q}
341
+ q2 = query.reject{|q| q.include?('_to_')}
342
+
343
+ counter = 0
312
344
 
313
345
  RubyLLM.models.all.each do |llm|
314
346
  inputs = llm.modalities.input.join(',')
@@ -316,17 +348,25 @@ module AIA
316
348
  entry = "- #{llm.id} (#{llm.provider}) #{inputs} to #{outputs}"
317
349
 
318
350
  if query.nil? || query.empty?
351
+ counter += 1
319
352
  puts entry
320
353
  next
321
354
  end
322
355
 
323
356
  show_it = true
324
- q1.each{|q| show_it &&= entry.include?(q)}
325
- q2.each{|q| show_it &&= llm.modalities.supports?(q)}
357
+ q1.each{|q| show_it &&= llm.modalities.send("#{q}?")}
358
+ q2.each{|q| show_it &&= entry.include?(q)}
326
359
 
327
- puts entry if show_it
360
+ if show_it
361
+ counter += 1
362
+ puts entry
363
+ end
328
364
  end
329
365
 
366
+ puts if counter > 0
367
+ puts "#{counter} LLMs matching your query"
368
+ puts
369
+
330
370
  ""
331
371
  end
332
372
  alias_method :am, :available_models
@@ -74,6 +74,8 @@ module AIA
74
74
  exit 1
75
75
  end
76
76
 
77
+ return unless @chat.model.supports_functions?
78
+
77
79
  if !AIA.config.tool_paths.empty? && !@chat.model.supports?(:function_calling)
78
80
  STDERR.puts "ERROR: The model #{@model} does not support tools"
79
81
  exit 1
@@ -96,17 +98,17 @@ module AIA
96
98
  modes = @chat.model.modalities
97
99
 
98
100
  # TODO: Need to consider how to handle multi-mode models
99
- if modes.supports? :text_to_text
101
+ if modes.text_to_text?
100
102
  text_to_text(prompt)
101
103
 
102
- elsif modes.supports? :image_to_text
104
+ elsif modes.image_to_text?
103
105
  image_to_text(prompt)
104
- elsif modes.supports? :text_to_image
106
+ elsif modes.text_to_image?
105
107
  text_to_image(prompt)
106
108
 
107
- elsif modes.supports? :text_to_audio
109
+ elsif modes.text_to_audio?
108
110
  text_to_audio(prompt)
109
- elsif modes.supports? :audio_to_text
111
+ elsif modes.audio_to_text?
110
112
  audio_to_text(prompt)
111
113
 
112
114
  else
data/lib/aia/session.rb CHANGED
@@ -41,7 +41,7 @@ module AIA
41
41
  @directive_processor = DirectiveProcessor.new
42
42
  @chat_processor = ChatProcessorService.new(@ui_presenter, @directive_processor)
43
43
 
44
- if AIA.config.out_file && !AIA.append? && File.exist?(AIA.config.out_file)
44
+ if AIA.config.out_file && !AIA.config.out_file.nil? && !AIA.append? && File.exist?(AIA.config.out_file)
45
45
  File.open(AIA.config.out_file, 'w') {} # Truncate the file
46
46
  end
47
47
  end
@@ -111,8 +111,17 @@ module AIA
111
111
  prompt.text << TERSE_PROMPT
112
112
  end
113
113
 
114
- prompt.save
115
- # Substitute variables and get final prompt text
114
+ if AIA.config.stdin_content && !AIA.config.stdin_content.strip.empty?
115
+ prompt.text << "\n\n" << AIA.config.stdin_content
116
+ end
117
+
118
+ if AIA.config.executable_prompt_file
119
+ prompt.text << "\n\n" << File.read(AIA.config.executable_prompt_file)
120
+ .lines[1..]
121
+ .join
122
+ end
123
+
124
+ # Substitute variables, execute dynamic content and get final prompt text
116
125
  prompt_text = prompt.to_s
117
126
 
118
127
  # Add context files if any
@@ -26,7 +26,7 @@ module AIA
26
26
  puts "\nAI: "
27
27
  format_chat_response(response)
28
28
 
29
- if AIA.config.out_file
29
+ if AIA.config.out_file && !AIA.config.out_file.nil?
30
30
  File.open(AIA.config.out_file, 'a') do |file|
31
31
  file.puts "\nAI: "
32
32
  format_chat_response(response, file)
data/lib/aia/utility.rb CHANGED
@@ -21,7 +21,7 @@ module AIA
21
21
  (O) using #{AIA.config.adapter} (v#{RubyLLM::VERSION})
22
22
  __||__ \\) model db was last refreshed on
23
23
  [/______\\] / #{AIA.config.last_refresh}
24
- / \\__AI__/ \\/ #{AIA.config.tool_paths.empty? ? 'I forgot my toolbox' : 'I brought some tools'}
24
+ / \\__AI__/ \\/ #{AIA.config.tool_paths.empty? ? 'You can share my tools' : 'I will also use your tools'}
25
25
  / /__\\
26
26
  (\\ /____\\ #{AIA.config.tool_paths.empty? ? '' : 'My Toolbox contains:'}
27
27
  ROBOT
data/lib/aia.rb CHANGED
@@ -5,15 +5,17 @@
5
5
  # provides an interface for interacting with AI models and managing prompts.
6
6
 
7
7
  require 'ruby_llm'
8
+ require 'ruby_llm/mcp'
8
9
  require 'prompt_manager'
9
10
 
11
+
10
12
  require 'debug_me'
11
13
  include DebugMe
12
14
  $DEBUG_ME = false
13
15
  DebugMeDefaultOptions[:skip1] = true
14
16
 
15
17
  require_relative 'extensions/openstruct_merge' # adds self.merge self.get_value
16
- require_relative 'extensions/ruby_llm/modalities' # adds model.modalities.supports? :text-to-text etc.
18
+ require_relative 'extensions/ruby_llm/modalities' # adds model.modalities.text_to_text? etc.
17
19
 
18
20
  require_relative 'refinements/string.rb' # adds #include_any? #include_all?
19
21
 
@@ -37,6 +39,10 @@ require_relative 'aia/session'
37
39
  module AIA
38
40
  at_exit do
39
41
  STDERR.puts "Exiting AIA application..."
42
+ # Clean up temporary STDIN file if it exists
43
+ if @config&.stdin_temp_file && File.exist?(@config.stdin_temp_file)
44
+ File.unlink(@config.stdin_temp_file)
45
+ end
40
46
  end
41
47
 
42
48
  @config = nil
@@ -1,26 +1,34 @@
1
1
  # lib/extensions/ruby_llm/modalities.rb
2
- # A models "modes" are often expressed in terms like:
3
- # text-to-text
4
- # text_to_audio
5
- # audio to image
6
- # image2image
7
- # This new supports? method tests the models modalities against
8
- # these common expressions
9
2
 
10
3
  class RubyLLM::Model::Modalities
11
- def supports?(query_mode)
12
- parts = query_mode
13
- .to_s
14
- .downcase
15
- .split(/2|-to-| to |_to_/)
16
- .map(&:strip)
17
-
18
- if 2 == parts.size
19
- input.include?(parts[0]) && output.include?(parts[1])
20
- elsif 1 == parts.size
21
- input.include?(parts[0]) || output.include?(parts[0])
22
- else
23
- false
24
- end
25
- end
4
+ #
5
+ def text_to_text? = input.include?('text') && output.include?('text')
6
+ def text_to_embeddings? = input.include?('text') && output.include?('embeddings')
7
+ def text_to_audio? = input.include?('text') && output.include?('audio')
8
+ def text_to_image? = input.include?('text') && output.include?('image')
9
+ def text_to_moderation? = input.include?('text') && output.include?('moderation')
10
+ #
11
+ def image_to_text? = input.include?('image') && output.include?('text')
12
+ def image_to_embeddings? = input.include?('image') && output.include?('embeddings')
13
+ def image_to_audio? = input.include?('image') && output.include?('audio')
14
+ def image_to_image? = input.include?('image') && output.include?('image')
15
+ def image_to_moderation? = input.include?('image') && output.include?('moderation')
16
+ #
17
+ def pdf_to_text? = input.include?('pdf') && output.include?('text')
18
+ def pdf_to_embeddings? = input.include?('pdf') && output.include?('embeddings')
19
+ def pdf_to_audio? = input.include?('pdf') && output.include?('audio')
20
+ def pdf_to_image? = input.include?('pdf') && output.include?('image')
21
+ def pdf_to_moderation? = input.include?('pdf') && output.include?('moderation')
22
+ #
23
+ def audio_to_text? = input.include?('audio') && output.include?('text')
24
+ def audio_to_embeddings? = input.include?('audio') && output.include?('embeddings')
25
+ def audio_to_audio? = input.include?('audio') && output.include?('audio')
26
+ def audio_to_image? = input.include?('audio') && output.include?('image')
27
+ def audio_to_moderation? = input.include?('audio') && output.include?('moderation')
28
+ #
29
+ def file_to_text? = input.include?('file') && output.include?('text')
30
+ def file_to_embeddings? = input.include?('file') && output.include?('embeddings')
31
+ def file_to_audio? = input.include?('file') && output.include?('audio')
32
+ def file_to_image? = input.include?('file') && output.include?('image')
33
+ def file_to_moderation? = input.include?('file') && output.include?('moderation')
26
34
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aia
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.5
4
+ version: 0.9.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dewayne VanHoozer
@@ -9,6 +9,20 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activesupport
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: amazing_print
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -57,14 +71,28 @@ dependencies:
57
71
  requirements:
58
72
  - - ">="
59
73
  - !ruby/object:Gem::Version
60
- version: 1.3.0
74
+ version: 1.3.1
61
75
  type: :runtime
62
76
  prerelease: false
63
77
  version_requirements: !ruby/object:Gem::Requirement
64
78
  requirements:
65
79
  - - ">="
66
80
  - !ruby/object:Gem::Version
67
- version: 1.3.0
81
+ version: 1.3.1
82
+ - !ruby/object:Gem::Dependency
83
+ name: ruby_llm-mcp
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
68
96
  - !ruby/object:Gem::Dependency
69
97
  name: reline
70
98
  requirement: !ruby/object:Gem::Requirement
@@ -292,6 +320,7 @@ files:
292
320
  - examples/tools/list_files.rb
293
321
  - examples/tools/read_file.rb
294
322
  - examples/tools/run_shell_command.rb
323
+ - images/aia.png
295
324
  - justfile
296
325
  - lib/aia.rb
297
326
  - lib/aia/aia_completion.bash