aia 0.5.17 → 0.8.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 +4 -4
- data/.envrc +1 -0
- data/.version +1 -2
- data/CHANGELOG.md +61 -22
- data/README.md +387 -227
- data/Rakefile +16 -5
- data/_notes.txt +231 -0
- data/bin/aia +3 -2
- data/examples/README.md +140 -0
- data/examples/headlines +21 -0
- data/justfile +16 -3
- data/lib/aia/ai_client_adapter.rb +210 -0
- data/lib/aia/chat_processor_service.rb +120 -0
- data/lib/aia/config.rb +473 -4
- data/lib/aia/context_manager.rb +58 -0
- data/lib/aia/directive_processor.rb +267 -0
- data/lib/aia/{tools/fzf.rb → fzf.rb} +9 -17
- data/lib/aia/history_manager.rb +85 -0
- data/lib/aia/prompt_handler.rb +178 -0
- data/lib/aia/session.rb +215 -0
- data/lib/aia/shell_command_executor.rb +109 -0
- data/lib/aia/ui_presenter.rb +110 -0
- data/lib/aia/utility.rb +24 -0
- data/lib/aia/version.rb +9 -6
- data/lib/aia.rb +57 -61
- data/lib/extensions/openstruct_merge.rb +44 -0
- metadata +43 -42
- data/LICENSE.txt +0 -21
- data/doc/aia_and_pre_compositional_prompts.md +0 -474
- data/lib/aia/clause.rb +0 -7
- data/lib/aia/cli.rb +0 -452
- data/lib/aia/directives.rb +0 -142
- data/lib/aia/dynamic_content.rb +0 -26
- data/lib/aia/logging.rb +0 -62
- data/lib/aia/main.rb +0 -265
- data/lib/aia/prompt.rb +0 -275
- data/lib/aia/tools/backend_common.rb +0 -58
- data/lib/aia/tools/client.rb +0 -197
- data/lib/aia/tools/editor.rb +0 -52
- data/lib/aia/tools/glow.rb +0 -90
- data/lib/aia/tools/llm.rb +0 -77
- data/lib/aia/tools/mods.rb +0 -100
- data/lib/aia/tools/sgpt.rb +0 -79
- data/lib/aia/tools/subl.rb +0 -68
- data/lib/aia/tools/vim.rb +0 -93
- data/lib/aia/tools.rb +0 -88
- data/lib/aia/user_query.rb +0 -21
- data/lib/core_ext/string_wrap.rb +0 -73
- data/lib/core_ext/tty-spinner_log.rb +0 -25
- data/man/aia.1 +0 -272
- data/man/aia.1.md +0 -236
@@ -0,0 +1,267 @@
|
|
1
|
+
# lib/aia/directive_processor.rb
|
2
|
+
|
3
|
+
module AIA
|
4
|
+
class DirectiveProcessor
|
5
|
+
EXCLUDED_METHODS = %w[ run initialize private? ]
|
6
|
+
@descriptions = {}
|
7
|
+
@aliases = {}
|
8
|
+
|
9
|
+
class << self
|
10
|
+
attr_reader :descriptions, :aliases
|
11
|
+
|
12
|
+
def desc(description, method_name = nil)
|
13
|
+
@last_description = description
|
14
|
+
@descriptions[method_name.to_s] = description if method_name
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_added(method_name)
|
19
|
+
if @last_description
|
20
|
+
@descriptions[method_name.to_s] = @last_description
|
21
|
+
@last_description = nil
|
22
|
+
end
|
23
|
+
super if defined?(super)
|
24
|
+
end
|
25
|
+
|
26
|
+
def build_aliases(private_methods)
|
27
|
+
private_methods.each do |method_name|
|
28
|
+
method = instance_method(method_name)
|
29
|
+
|
30
|
+
@aliases[method_name] = []
|
31
|
+
|
32
|
+
private_methods.each do |other_method_name|
|
33
|
+
next if method_name == other_method_name
|
34
|
+
|
35
|
+
other_method = instance_method(other_method_name)
|
36
|
+
|
37
|
+
if method == other_method
|
38
|
+
@aliases[method_name] << other_method_name
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize
|
46
|
+
@prefix_size = PromptManager::Prompt::DIRECTIVE_SIGNAL.size
|
47
|
+
@included_files = []
|
48
|
+
end
|
49
|
+
|
50
|
+
def directive?(a_string)
|
51
|
+
a_string.strip.start_with?(PromptManager::Prompt::DIRECTIVE_SIGNAL)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Used with the chat loop to allow user to enter a single directive
|
55
|
+
def process(a_string, context_manager)
|
56
|
+
return a_string unless directive?(a_string)
|
57
|
+
|
58
|
+
key = a_string.strip
|
59
|
+
sans_prefix = key[@prefix_size..]
|
60
|
+
args = sans_prefix.split(' ')
|
61
|
+
method_name = args.shift.downcase
|
62
|
+
|
63
|
+
if EXCLUDED_METHODS.include?(method_name)
|
64
|
+
return "Error: #{method_name} is not a valid directive: #{key}"
|
65
|
+
elsif respond_to?(method_name, true)
|
66
|
+
return send(method_name, args)
|
67
|
+
else
|
68
|
+
return "Error: Unknown directive '#{key}'"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def run(directives)
|
73
|
+
return {} if directives.nil? || directives.empty?
|
74
|
+
directives.each do |key, _|
|
75
|
+
sans_prefix = key[@prefix_size..]
|
76
|
+
args = sans_prefix.split(' ')
|
77
|
+
method_name = args.shift.downcase
|
78
|
+
|
79
|
+
if EXCLUDED_METHODS.include?(method_name)
|
80
|
+
directives[key] = "Error: #{method_name} is not a valid directive: #{key}"
|
81
|
+
next
|
82
|
+
elsif respond_to?(method_name, true)
|
83
|
+
directives[key] = send(method_name, args)
|
84
|
+
else
|
85
|
+
directives[key] = "Error: Unknown directive '#{key}'"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
directives
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
#####################################################
|
94
|
+
## Directives are implemented as private methods
|
95
|
+
## All directives return a String. It can be empty.
|
96
|
+
#
|
97
|
+
private
|
98
|
+
|
99
|
+
def private?(method_name)
|
100
|
+
!respond_to?(method_name) && respond_to?(method_name, true)
|
101
|
+
end
|
102
|
+
|
103
|
+
################
|
104
|
+
## Directives ##
|
105
|
+
################
|
106
|
+
|
107
|
+
desc "Specify the next prompt ID to process after this one"
|
108
|
+
def next(args = [])
|
109
|
+
if args.empty?
|
110
|
+
ap AIA.config.next
|
111
|
+
else
|
112
|
+
AIA.config.next = args.shift
|
113
|
+
end
|
114
|
+
''
|
115
|
+
end
|
116
|
+
|
117
|
+
desc "Specify a sequence pf prompt IDs to process after this one"
|
118
|
+
def pipeline(args = [])
|
119
|
+
if args.empty?
|
120
|
+
ap AIA.config.pipeline
|
121
|
+
else
|
122
|
+
AIA.config.pipeline += args.map {|id| id.gsub(',', '').strip}
|
123
|
+
end
|
124
|
+
''
|
125
|
+
end
|
126
|
+
|
127
|
+
desc "Inserts the contents of a file Example: //include path/to/file"
|
128
|
+
def include(args)
|
129
|
+
file_path = args.shift
|
130
|
+
|
131
|
+
if @included_files.include?(file_path)
|
132
|
+
""
|
133
|
+
else
|
134
|
+
if File.exist?(file_path) && File.readable?(file_path)
|
135
|
+
@included_files << file_path
|
136
|
+
File.read(file_path)
|
137
|
+
else
|
138
|
+
"Error: File '#{file_path}' is not accessible"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
alias_method :include_file, :include
|
143
|
+
alias_method :import, :include
|
144
|
+
|
145
|
+
desc "Without arguments it will print a list of all config items and their values _or_ //config item (for one item's value) _or_ //config item = value (to set a value of an item)"
|
146
|
+
def config(args = [])
|
147
|
+
args = Array(args)
|
148
|
+
|
149
|
+
if args.empty?
|
150
|
+
ap AIA.config
|
151
|
+
""
|
152
|
+
elsif args.length == 1
|
153
|
+
config_item = args.first
|
154
|
+
local_cfg = Hash.new
|
155
|
+
local_cfg[config_item] = AIA.config[config_item]
|
156
|
+
ap local_cfg
|
157
|
+
""
|
158
|
+
else
|
159
|
+
config_item = args.shift
|
160
|
+
boolean = AIA.respond_to?("#{config_item}?")
|
161
|
+
new_value = args.join(' ').gsub('=', '').strip
|
162
|
+
|
163
|
+
if boolean
|
164
|
+
new_value = %w[true t yes y on 1 yea yeah yep yup].include?(new_value.downcase)
|
165
|
+
end
|
166
|
+
|
167
|
+
AIA.config[config_item] = new_value
|
168
|
+
""
|
169
|
+
end
|
170
|
+
end
|
171
|
+
alias_method :cfg, :config
|
172
|
+
|
173
|
+
desc "Shortcut for //config top_p _and_ //config top_p = value"
|
174
|
+
def top_p(*args)
|
175
|
+
send(:config, args.prepend('top_p'))
|
176
|
+
end
|
177
|
+
alias_method :topp, :top_p
|
178
|
+
|
179
|
+
desc "Shortcut for //config model _and_ //config model = value"
|
180
|
+
def model(*args)
|
181
|
+
send(:config, args.prepend('model'))
|
182
|
+
end
|
183
|
+
|
184
|
+
desc "Shortcut for //config temperature _and_ //config temperature = value"
|
185
|
+
def temperature(*args)
|
186
|
+
send(:config, args.prepend('temperature'))
|
187
|
+
end
|
188
|
+
alias_method :temp, :temperature
|
189
|
+
|
190
|
+
desc "Clears the conversation history (aka context) same as //config clear = true"
|
191
|
+
def clear(args, context_manager)
|
192
|
+
if context_manager.nil?
|
193
|
+
return "Error: Context manager not available for //clear directive."
|
194
|
+
end
|
195
|
+
context_manager.clear_context
|
196
|
+
nil
|
197
|
+
end
|
198
|
+
|
199
|
+
desc "Shortcut for a one line of ruby code; result is added to the context"
|
200
|
+
def ruby(*args)
|
201
|
+
ruby_code = args.join(' ')
|
202
|
+
|
203
|
+
begin
|
204
|
+
String(eval(ruby_code))
|
205
|
+
rescue Exception => e
|
206
|
+
<<~ERROR
|
207
|
+
This ruby code failed: #{ruby_code}
|
208
|
+
#{e.message}
|
209
|
+
ERROR
|
210
|
+
end
|
211
|
+
end
|
212
|
+
alias_method :rb, :ruby
|
213
|
+
|
214
|
+
desc "Use the system's say command to speak text //say some text"
|
215
|
+
def say(*args)
|
216
|
+
`say #{args.join(' ')}`
|
217
|
+
""
|
218
|
+
end
|
219
|
+
|
220
|
+
desc "Inserts an instruction to keep responses short and to the point."
|
221
|
+
def terse(*args)
|
222
|
+
AIA::Session::TERSE_PROMPT
|
223
|
+
end
|
224
|
+
|
225
|
+
desc "Display the ASCII art AIA robot."
|
226
|
+
def robot(*args)
|
227
|
+
AIA::Utility.robot
|
228
|
+
""
|
229
|
+
end
|
230
|
+
|
231
|
+
desc "Generates this help content"
|
232
|
+
def help(*args)
|
233
|
+
puts
|
234
|
+
puts "Available Directives"
|
235
|
+
puts "===================="
|
236
|
+
puts
|
237
|
+
|
238
|
+
directives = self.class
|
239
|
+
.private_instance_methods(false)
|
240
|
+
.map(&:to_s)
|
241
|
+
.reject { |m| EXCLUDED_METHODS.include?(m) }
|
242
|
+
.sort
|
243
|
+
|
244
|
+
self.class.build_aliases(directives)
|
245
|
+
|
246
|
+
directives.each do |directive|
|
247
|
+
next unless self.class.descriptions[directive]
|
248
|
+
|
249
|
+
others = self.class.aliases[directive]
|
250
|
+
|
251
|
+
if others.empty?
|
252
|
+
others_line = ""
|
253
|
+
else
|
254
|
+
with_prefix = others.map{|m| PromptManager::Prompt::DIRECTIVE_SIGNAL + m}
|
255
|
+
others_line = "\tAliases:#{with_prefix.join(' ')}\n"
|
256
|
+
end
|
257
|
+
|
258
|
+
puts <<~TEXT
|
259
|
+
//#{directive} #{self.class.descriptions[directive]}
|
260
|
+
#{others_line}
|
261
|
+
TEXT
|
262
|
+
end
|
263
|
+
|
264
|
+
""
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
@@ -4,16 +4,7 @@
|
|
4
4
|
require 'shellwords'
|
5
5
|
require 'tempfile'
|
6
6
|
|
7
|
-
class AIA::Fzf
|
8
|
-
|
9
|
-
meta(
|
10
|
-
name: 'fzf',
|
11
|
-
role: :search_tool,
|
12
|
-
desc: "A command-line fuzzy finder",
|
13
|
-
url: "https://github.com/junegunn/fzf",
|
14
|
-
install: "brew install fzf",
|
15
|
-
)
|
16
|
-
|
7
|
+
class AIA::Fzf
|
17
8
|
DEFAULT_PARAMETERS = %w[
|
18
9
|
--tabstop=2
|
19
10
|
--header-first
|
@@ -39,26 +30,27 @@ class AIA::Fzf < AIA::Tools
|
|
39
30
|
@subject = subject
|
40
31
|
@prompt = prompt
|
41
32
|
@extension = extension
|
42
|
-
|
33
|
+
|
43
34
|
build_command
|
44
35
|
end
|
45
36
|
|
46
37
|
|
47
38
|
def build_command
|
48
39
|
fzf_options = DEFAULT_PARAMETERS.dup
|
49
|
-
fzf_options << "--header='#{subject} which contain: #{query}
|
40
|
+
fzf_options << "--header='#{subject} which contain: #{query}\nPress ESC to cancel.'"
|
50
41
|
fzf_options << "--preview='cat #{directory}/{1}#{extension}'"
|
51
42
|
fzf_options << "--prompt=#{Shellwords.escape(prompt)}"
|
52
|
-
|
53
|
-
fzf_command = "
|
43
|
+
|
44
|
+
fzf_command = "fzf #{fzf_options.join(' ')}"
|
54
45
|
|
55
46
|
@command = "cat #{tempfile_path} | #{fzf_command}"
|
56
47
|
end
|
57
|
-
|
48
|
+
|
58
49
|
|
59
50
|
def run
|
60
|
-
puts "Executing: #{@command}"
|
61
|
-
selected = `#{@command}
|
51
|
+
# puts "Executing: #{@command}"
|
52
|
+
selected = `#{@command}`.strip
|
53
|
+
|
62
54
|
selected.strip.empty? ? nil : selected.strip
|
63
55
|
ensure
|
64
56
|
unlink_tempfile
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# lib/aia/history_manager.rb
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
module AIA
|
7
|
+
class HistoryManager
|
8
|
+
MAX_VARIABLE_HISTORY = 5
|
9
|
+
|
10
|
+
# prompt is PromptManager::Prompt instance
|
11
|
+
def initialize(prompt:)
|
12
|
+
@prompt = prompt
|
13
|
+
@history = []
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def history
|
18
|
+
@history
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def history=(new_history)
|
23
|
+
@history = new_history
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def setup_variable_history(history_values)
|
28
|
+
Reline::HISTORY.clear
|
29
|
+
history_values.each do |value|
|
30
|
+
Reline::HISTORY.push(value) unless value.nil? || value.empty?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def get_variable_history(variable, value = '')
|
36
|
+
return if value.nil? || value.empty?
|
37
|
+
|
38
|
+
values = @prompt.parameters[variable]
|
39
|
+
if values.include?(value)
|
40
|
+
values.delete(value)
|
41
|
+
end
|
42
|
+
|
43
|
+
values << value
|
44
|
+
|
45
|
+
if values.size > MAX_VARIABLE_HISTORY
|
46
|
+
values.shift
|
47
|
+
end
|
48
|
+
|
49
|
+
@prompt.parameters[variable] = values
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
def request_variable_value(variable_name:, history_values: [])
|
54
|
+
setup_variable_history(history_values) # Setup Reline's history for completion
|
55
|
+
|
56
|
+
default_value = history_values.last || ''
|
57
|
+
question = "Value for #{variable_name} (#{default_value}): "
|
58
|
+
|
59
|
+
# Ensure Reline is writing to stdout explicitly for this interaction
|
60
|
+
Reline.output = $stdout
|
61
|
+
|
62
|
+
# Store the original prompt proc to restore later
|
63
|
+
original_prompt_proc = Reline.line_editor.prompt_proc
|
64
|
+
|
65
|
+
# Note: Temporarily setting prompt_proc might not be needed if passing prompt to readline works.
|
66
|
+
# Reline.line_editor.prompt_proc = ->(context) { [question] }
|
67
|
+
|
68
|
+
begin
|
69
|
+
input = Reline.readline(question, true)
|
70
|
+
return default_value if input.nil? # Handle Ctrl+D -> use default
|
71
|
+
|
72
|
+
chosen_value = input.strip.empty? ? default_value : input.strip
|
73
|
+
# Update the persistent history for this variable
|
74
|
+
get_variable_history(variable_name, chosen_value)
|
75
|
+
return chosen_value
|
76
|
+
rescue Interrupt
|
77
|
+
puts "\nVariable input interrupted."
|
78
|
+
exit(1) # Exit cleanly on Ctrl+C
|
79
|
+
ensure
|
80
|
+
# Restore the original prompt proc
|
81
|
+
Reline.line_editor.prompt_proc = original_prompt_proc
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
# lib/aia/prompt_handler.rb
|
2
|
+
|
3
|
+
require 'prompt_manager'
|
4
|
+
require 'prompt_manager/storage/file_system_adapter'
|
5
|
+
require 'erb'
|
6
|
+
|
7
|
+
|
8
|
+
module AIA
|
9
|
+
class PromptHandler
|
10
|
+
def initialize
|
11
|
+
@prompts_dir = AIA.config.prompts_dir
|
12
|
+
@roles_dir = AIA.config.roles_dir # A sub-directory of @prompts_dir
|
13
|
+
@directive_processor = AIA::DirectiveProcessor.new
|
14
|
+
|
15
|
+
PromptManager::Prompt.storage_adapter =
|
16
|
+
PromptManager::Storage::FileSystemAdapter.config do |c|
|
17
|
+
c.prompts_dir = @prompts_dir
|
18
|
+
c.prompt_extension = '.txt' # default
|
19
|
+
c.params_extension = '.json' # default
|
20
|
+
end.new
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def get_prompt(prompt_id, role_id = '')
|
25
|
+
prompt = fetch_prompt(prompt_id)
|
26
|
+
|
27
|
+
unless role_id.empty?
|
28
|
+
role_prompt = fetch_role(role_id)
|
29
|
+
prompt.text.prepend(role_prompt.text)
|
30
|
+
end
|
31
|
+
|
32
|
+
prompt
|
33
|
+
end
|
34
|
+
|
35
|
+
def fetch_prompt(prompt_id)
|
36
|
+
# Special case for fuzzy search without an initial query
|
37
|
+
if prompt_id == '__FUZZY_SEARCH__'
|
38
|
+
return fuzzy_search_prompt('')
|
39
|
+
end
|
40
|
+
|
41
|
+
# First check if the prompt file exists to avoid ArgumentError from PromptManager
|
42
|
+
prompt_file_path = File.join(@prompts_dir, "#{prompt_id}.txt")
|
43
|
+
if File.exist?(prompt_file_path)
|
44
|
+
prompt = PromptManager::Prompt.new(
|
45
|
+
id: prompt_id,
|
46
|
+
directives_processor: @directive_processor,
|
47
|
+
external_binding: binding,
|
48
|
+
erb_flag: AIA.config.erb,
|
49
|
+
envar_flag: AIA.config.shell
|
50
|
+
)
|
51
|
+
|
52
|
+
return prompt if prompt
|
53
|
+
else
|
54
|
+
puts "Warning: Invalid prompt ID or file not found: #{prompt_id}"
|
55
|
+
end
|
56
|
+
|
57
|
+
handle_missing_prompt(prompt_id)
|
58
|
+
end
|
59
|
+
|
60
|
+
def handle_missing_prompt(prompt_id)
|
61
|
+
if AIA.config.fuzzy
|
62
|
+
return fuzzy_search_prompt(prompt_id)
|
63
|
+
elsif AIA.config.fuzzy
|
64
|
+
puts "Warning: Fuzzy search is enabled but Fzf tool is not available."
|
65
|
+
raise "Error: Could not find prompt with ID: #{prompt_id}"
|
66
|
+
else
|
67
|
+
raise "Error: Could not find prompt with ID: #{prompt_id}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def fuzzy_search_prompt(prompt_id)
|
72
|
+
new_prompt_id = search_prompt_id_with_fzf(prompt_id)
|
73
|
+
|
74
|
+
if new_prompt_id.nil? || new_prompt_id.empty?
|
75
|
+
raise "Error: Could not find prompt with ID: #{prompt_id} even with fuzzy search"
|
76
|
+
end
|
77
|
+
|
78
|
+
prompt = PromptManager::Prompt.new(
|
79
|
+
id: new_prompt_id,
|
80
|
+
directives_processor: @directive_processor,
|
81
|
+
external_binding: binding,
|
82
|
+
erb_flag: AIA.config.erb,
|
83
|
+
envar_flag: AIA.config.shell
|
84
|
+
)
|
85
|
+
|
86
|
+
raise "Error: Could not find prompt with ID: #{prompt_id} even with fuzzy search" if prompt.nil?
|
87
|
+
|
88
|
+
prompt
|
89
|
+
end
|
90
|
+
|
91
|
+
def fetch_role(role_id)
|
92
|
+
# Prepend roles_prefix if not already present
|
93
|
+
unless role_id.start_with?(AIA.config.roles_prefix)
|
94
|
+
role_id = "#{AIA.config.roles_prefix}/#{role_id}"
|
95
|
+
end
|
96
|
+
|
97
|
+
# NOTE: roles_prefix is a sub-directory of the prompts directory
|
98
|
+
role_file_path = File.join(@prompts_dir, "#{role_id}.txt")
|
99
|
+
|
100
|
+
if File.exist?(role_file_path)
|
101
|
+
role_prompt = PromptManager::Prompt.new(
|
102
|
+
id: role_id,
|
103
|
+
directives_processor: @directive_processor,
|
104
|
+
external_binding: binding,
|
105
|
+
erb_flag: AIA.config.erb,
|
106
|
+
envar_flag: AIA.config.shell
|
107
|
+
)
|
108
|
+
return role_prompt if role_prompt
|
109
|
+
else
|
110
|
+
puts "Warning: Invalid role ID or file not found: #{role_id}"
|
111
|
+
end
|
112
|
+
|
113
|
+
handle_missing_role(role_id)
|
114
|
+
end
|
115
|
+
|
116
|
+
def handle_missing_role(role_id)
|
117
|
+
if AIA.config.fuzzy
|
118
|
+
return fuzzy_search_role(role_id)
|
119
|
+
else
|
120
|
+
raise "Error: Could not find role with ID: #{role_id}"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def fuzzy_search_role(role_id)
|
125
|
+
new_role_id = search_role_id_with_fzf(role_id)
|
126
|
+
if new_role_id.nil? || new_role_id.empty?
|
127
|
+
raise "Error: Could not find role with ID: #{role_id} even with fuzzy search"
|
128
|
+
end
|
129
|
+
|
130
|
+
role_prompt = PromptManager::Prompt.new(
|
131
|
+
id: new_role_id,
|
132
|
+
directives_processor: @directive_processor,
|
133
|
+
external_binding: binding,
|
134
|
+
erb_flag: AIA.config.erb,
|
135
|
+
envar_flag: AIA.config.shell
|
136
|
+
)
|
137
|
+
|
138
|
+
raise "Error: Could not find role with ID: #{role_id} even with fuzzy search" if role_prompt.nil?
|
139
|
+
role_prompt
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
def search_prompt_id_with_fzf(initial_query)
|
144
|
+
prompt_files = Dir.glob(File.join(@prompts_dir, "*.txt")).map { |file| File.basename(file, ".txt") }
|
145
|
+
fzf = AIA::Fzf.new(
|
146
|
+
list: prompt_files,
|
147
|
+
directory: @prompts_dir,
|
148
|
+
query: initial_query,
|
149
|
+
subject: 'Prompt IDs',
|
150
|
+
prompt: 'Select a prompt ID:'
|
151
|
+
)
|
152
|
+
fzf.run || (raise "No prompt ID selected")
|
153
|
+
end
|
154
|
+
|
155
|
+
def search_role_id_with_fzf(initial_query)
|
156
|
+
role_files = Dir.glob(File.join(@roles_dir, "*.txt")).map { |file| File.basename(file, ".txt") }
|
157
|
+
fzf = AIA::Fzf.new(
|
158
|
+
list: role_files,
|
159
|
+
directory: @prompts_dir,
|
160
|
+
query: initial_query,
|
161
|
+
subject: 'Role IDs',
|
162
|
+
prompt: 'Select a role ID:'
|
163
|
+
)
|
164
|
+
|
165
|
+
role = fzf.run
|
166
|
+
|
167
|
+
if role.nil? || role.empty?
|
168
|
+
raise "No role ID selected"
|
169
|
+
end
|
170
|
+
|
171
|
+
unless role.start_with?(AIA.config.role_prefix)
|
172
|
+
role = AIA.config.role_prefix + '/' + role
|
173
|
+
end
|
174
|
+
|
175
|
+
role
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|