aia 0.3.4 → 0.3.19

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,14 +5,20 @@ module AIA::PromptProcessing
5
5
 
6
6
  # Fetch the first argument which should be the prompt id
7
7
  def get_prompt
8
- prompt_id = @arguments.shift
8
+ prompt_id = AIA.config.arguments.shift
9
9
 
10
10
  # TODO: or maybe go to a generic search and select process
11
11
 
12
+ # TODO: if external options were provided but no prompt id
13
+ # then by pass prompt process and send the extra
14
+ # options to the backend. For example:
15
+ # aia -- --settings
16
+ # should send --settings to mods and nothing else
17
+
12
18
  abort("Please provide a prompt id") unless prompt_id
13
19
 
14
20
  search_for_a_matching_prompt(prompt_id) unless existing_prompt?(prompt_id)
15
- edit_prompt if edit?
21
+ edit_prompt if AIA.config.edit?
16
22
  end
17
23
 
18
24
 
@@ -178,8 +184,17 @@ module AIA::PromptProcessing
178
184
 
179
185
 
180
186
  def edit_prompt
181
- `#{EDITOR} #{@prompt.path}`
182
- @options[:edit?][0] = false
187
+ # FIXME: replace with the editor from the configuration
188
+
189
+ @editor = AIA::Subl.new(
190
+ file: @prompt.path
191
+ )
192
+
193
+ @editor.run # blocks until file is closed
194
+
195
+ @options[:edit?][0] = false # turn off the --edit switch
196
+
197
+ # reload the edited prompt
183
198
  @prompt = PromptManager::Prompt.get(id: @prompt.id)
184
199
  end
185
200
 
@@ -210,3 +225,192 @@ module AIA::PromptProcessing
210
225
  end
211
226
  end
212
227
 
228
+
229
+
230
+ __END__
231
+
232
+ # lib/aia/prompt_processing.rb
233
+
234
+ class AIA::PromptProcessing
235
+ KW_HISTORY_MAX = 5
236
+
237
+ def initialize(arguments, options)
238
+ @arguments = arguments
239
+ @options = options
240
+ @prompt = nil
241
+ end
242
+
243
+ def execute
244
+ get_prompt
245
+ process_prompt
246
+ end
247
+
248
+ private
249
+
250
+ def get_prompt
251
+ prompt_id = @arguments.shift
252
+ abort("Please provide a prompt id") unless prompt_id
253
+ search_for_a_matching_prompt(prompt_id) unless existing_prompt?(prompt_id)
254
+ edit_prompt if edit?
255
+ end
256
+
257
+ def existing_prompt?(prompt_id)
258
+ @prompt = PromptManager::Prompt.get(id: prompt_id)
259
+ @prompt.keywords.each do |kw|
260
+ @prompt.parameters[kw] = Array(@prompt.parameters[kw])
261
+ end
262
+ true
263
+ rescue ArgumentError
264
+ false
265
+ end
266
+
267
+ def process_prompt
268
+ return if @prompt.keywords.empty?
269
+ replace_keywords
270
+ @prompt.build
271
+ @prompt.save
272
+ end
273
+
274
+ def replace_keywords
275
+ puts "ID: #{@prompt.id}"
276
+ show_prompt_without_comments
277
+ puts_instructions
278
+ @prompt.keywords.each do |kw|
279
+ value = keyword_value(kw, @prompt.parameters[kw])
280
+ update_keyword_history(kw, value) unless value.nil? || value.strip.empty?
281
+ end
282
+ end
283
+
284
+ def puts_instructions
285
+ puts "\nPress up/down arrow to scroll through history."
286
+ puts "Type new input or edit the current input."
287
+ puts "Quit #{MY_NAME} with a CNTL-D or a CNTL-C"
288
+ puts
289
+ end
290
+
291
+ def update_keyword_history(kw, value)
292
+ params = @prompt.parameters[kw]
293
+ params.delete(value)
294
+ params << value
295
+ params.shift if params.size > KW_HISTORY_MAX
296
+ end
297
+
298
+ def keyword_value(kw, history_array)
299
+ Readline::HISTORY.clear
300
+ Array(history_array).each { |entry| Readline::HISTORY.push(entry) unless entry.nil? || entry.empty? }
301
+ puts "Parameter #{kw} ..."
302
+ begin
303
+ a_string = Readline.readline("\n-=> ", true)
304
+ rescue Interrupt
305
+ a_string = nil
306
+ end
307
+ abort("okay. Come back soon.") if a_string.nil?
308
+ a_string.empty? ? history_array.first : a_string
309
+ end
310
+
311
+ def search_for_a_matching_prompt(prompt_id)
312
+ found_prompts = PromptManager::Prompt.search(prompt_id)
313
+ handle_no_prompts_found(prompt_id) if found_prompts.empty?
314
+ prompt_id = found_prompts.size == 1 ? found_prompts.first : handle_multiple_prompts(found_prompts, prompt_id)
315
+ @prompt = PromptManager::Prompt.get(id: prompt_id)
316
+ end
317
+
318
+ def handle_no_prompts_found(prompt_id)
319
+ if edit?
320
+ create_prompt(prompt_id)
321
+ edit_prompt
322
+ else
323
+ abort_no_prompts_error(prompt_id)
324
+ end
325
+ end
326
+
327
+ def abort_no_prompts_error(prompt_id)
328
+ abort <<~EOS
329
+
330
+ No prompts were found for: #{prompt_id}
331
+ To create a prompt with this ID use the --edit option
332
+ like this:
333
+ #{MY_NAME} #{prompt_id} --edit
334
+
335
+ EOS
336
+ end
337
+
338
+ def handle_multiple_prompts(found_these, while_looking_for_this)
339
+ raise ArgumentError, "Argument is not an Array" unless found_these.is_a?(Array)
340
+ result = execute_fzf(found_these, while_looking_for_this)
341
+ abort unless result
342
+ result
343
+ end
344
+
345
+ def execute_fzf(found_these, while_looking_for_this)
346
+ fzf_options = build_fzf_options(while_looking_for_this)
347
+ temp_file = create_tempfile_with_entries(found_these)
348
+ selected = `cat #{temp_file.path} | fzf #{fzf_options}`.strip
349
+ temp_file.unlink
350
+ selected.empty? ? nil : selected
351
+ end
352
+
353
+ def build_fzf_options(search_term)
354
+ [
355
+ "--tabstop=2",
356
+ "--header='Prompt IDs which contain: #{search_term}\nPress ESC to cancel.'",
357
+ "--header-first",
358
+ "--prompt='Search term: '",
359
+ '--delimiter :',
360
+ "--preview 'cat $PROMPTS_DIR/{1}.txt'",
361
+ "--preview-window=down:50%:wrap"
362
+ ].join(' ')
363
+ end
364
+
365
+ def create_tempfile_with_entries(entries)
366
+ temp_file = Tempfile.new('fzf-input')
367
+ temp_file.puts(entries)
368
+ temp_file.close
369
+ temp_file
370
+ end
371
+
372
+ def create_prompt(prompt_id)
373
+ @prompt = PromptManager::Prompt.create(id: prompt_id)
374
+ # Additional prompt config...
375
+ end
376
+
377
+
378
+
379
+ def edit_prompt
380
+ # FIXME: replace with the editor from the configuration
381
+
382
+ @editor = AIA::Subl.new(
383
+ file: @prompt.path
384
+ )
385
+
386
+ @editor.run # blocks until file is closed
387
+
388
+ @options[:edit?][0] = false # turn off the --edit switch
389
+
390
+ # reload the edited prompt
391
+ @prompt = PromptManager::Prompt.get(id: @prompt.id)
392
+ end
393
+
394
+
395
+ def show_prompt_without_comments
396
+ puts remove_comments.wrap(indent: 4)
397
+ end
398
+
399
+ def remove_comments
400
+ lines = @prompt.text.lines
401
+ .reject { |a_line| a_line.strip.start_with?('#') }
402
+ .drop_while(&:empty?)
403
+ logical_end_inx = lines.index("__END__")
404
+ lines = lines[0...logical_end_inx] if logical_end_inx
405
+ lines.join("\n")
406
+ end
407
+
408
+ def edit?
409
+ @options[:edit?] && @options[:edit?][0] == true
410
+ end
411
+ end
412
+
413
+
414
+
415
+
416
+
@@ -0,0 +1,50 @@
1
+ # lib/aia/tools/editor.rb
2
+ # This is the default editor setup in the
3
+ # system environment variable EDITOR
4
+
5
+
6
+ class AIA::Editor < AIA::Tools
7
+ DEFAULT_PARAMETERS = ""
8
+
9
+ attr_accessor :command
10
+
11
+
12
+ def initialize(file: "")
13
+ super
14
+
15
+ @role = :editor
16
+ @description = "Your default system $EDITOR"
17
+ @url = "unknown"
18
+ @install = "should already be installed"
19
+
20
+ @file = file
21
+
22
+ discover_editor
23
+
24
+ build_command
25
+ end
26
+
27
+
28
+ def discover_editor
29
+ editor = ENV['EDITOR'] # This might be nil
30
+
31
+ if editor.nil?
32
+ @name = "echo"
33
+ @description = "You have no default editor"
34
+ @install = "Set your system environment variable EDITOR"
35
+ else
36
+ @name = editor
37
+ end
38
+ end
39
+
40
+
41
+ def build_command
42
+ @command = "#{name} #{DEFAULT_PARAMETERS} #{@file}"
43
+ end
44
+
45
+
46
+ def run
47
+ `#{command}`
48
+ end
49
+ end
50
+
@@ -1,22 +1,81 @@
1
- # lib/aia/external/mods.rb
1
+ # lib/aia/tools/mods.rb
2
2
 
3
- class AIA::External::Mods < AIA::External::Tool
4
- def initialize
3
+ class AIA::Mods < AIA::Tools
4
+ DEFAULT_PARAMETERS = [
5
+ "--no-limit" # no limit on input context
6
+ ].join(' ').freeze
7
+
8
+ attr_accessor :command, :extra_options, :text, :files
9
+
10
+ # TODO: put the prompt text to be resolved into a
11
+ # temporary text file then cat that file into mods.
12
+ # This will keep from polluting the CLI history with
13
+ # lots of text
14
+
15
+
16
+ def initialize(
17
+ extra_options: "", # everything after -- on command line
18
+ text: "", # prompt text after keyword replacement
19
+ files: [] # context file paths (Array of Pathname)
20
+ )
5
21
  super
6
- @role = :search
7
- @desc = 'AI on the command-line'
8
- @url = 'https://github.com/charmbracelet/mods'
22
+ @role = :gen_ai
23
+ @description = 'AI on the command-line'
24
+ @url = 'https://github.com/charmbracelet/mods'
25
+
26
+ @extra_options = extra_options
27
+ @text = text
28
+ @files = files
29
+
30
+ build_command
31
+ end
32
+
33
+
34
+ def build_command
35
+ parameters = DEFAULT_PARAMETERS.dup + " "
36
+ parameters += "-f " if ::AIA.config.markdown?
37
+ parameters += "-m #{AIA.config.model} " if ::AIA.config.model
38
+ parameters += @extra_options
39
+ @command = "mods #{parameters} "
40
+ @command += %Q["#{@text}"] # TODO: consider using the pipeline
41
+
42
+ @files.each {|f| @command += " < #{f}" }
43
+
44
+ @command
9
45
  end
10
46
 
11
- def command(extra_options = [])
12
- model = ENV['MODS_MODEL'] || 'gpt-4-1106-preview'
13
- ai_default_opts = "-m #{model} --no-limit -f"
14
- "#{name} #{ai_default_opts} #{extra_options.join(' ')}"
47
+
48
+ def run
49
+ `#{command}`
15
50
  end
16
51
  end
17
52
 
18
53
  __END__
19
54
 
55
+
56
+ # Execute the command and log the results
57
+ def send_prompt_to_external_command
58
+ command = build_command
59
+
60
+ puts command if verbose?
61
+ @result = `#{command}`
62
+
63
+ if @output.nil?
64
+ puts @result
65
+ else
66
+ @output.write @result
67
+ end
68
+
69
+ @result
70
+ end
71
+
72
+
73
+
74
+
75
+
76
+ ##########################################################
77
+
78
+
20
79
  GPT on the command line. Built for pipelines.
21
80
 
22
81
  Usage:
@@ -1,6 +1,6 @@
1
- # lib/aia/external/sgpt.rb
1
+ # lib/aia/tools/sgpt.rb
2
2
 
3
- class AIA::External::Sgpt < AIA::External::Tool
3
+ class AIA::Sgpt < AIA::Tools
4
4
  def initialize
5
5
  super
6
6
  @role = :backend
@@ -1,17 +1,35 @@
1
- # lib/aia/external/subl.rb
1
+ # lib/aia/tools/subl.rb
2
2
 
3
- class AIA::External::Subl < AIA::External::Tool
4
- def initialize
3
+ class AIA::Subl < AIA::Tools
4
+ DEFAULT_PARAMETERS = [
5
+ "--new-window", # Open a new window
6
+ "--wait", # Wait for the files to be closed before returning
7
+ ].join(' ')
8
+
9
+ attr_accessor :command
10
+
11
+
12
+ def initialize(file: "")
5
13
  super
14
+
6
15
  @role = :editor
7
16
  @desc = "Sublime Text Editor"
8
17
  @url = "https://www.sublimetext.com/"
9
18
  @install = "echo 'Download from website'"
19
+
20
+ @file = file
21
+
22
+ build_command
23
+ end
24
+
25
+
26
+ def build_command
27
+ @command = "#{name} #{DEFAULT_PARAMETERS} #{@file}"
10
28
  end
11
29
 
12
30
 
13
- def open(file)
14
- `#{name} #{file}`
31
+ def run
32
+ `#{command}`
15
33
  end
16
34
  end
17
35
 
@@ -0,0 +1,91 @@
1
+ # lib/aia/tools/vim.rb
2
+
3
+ class AIA::Vim < AIA::Tools
4
+ DEFAULT_PARAMETERS = [
5
+ " ", # no parameters
6
+ ].join(' ')
7
+
8
+ attr_accessor :command
9
+
10
+
11
+ def initialize(file: "")
12
+ super
13
+
14
+ @role = :editor
15
+ @description = "Vi IMproved (VIM)"
16
+ @url = "https://www.vim.org"
17
+ @install = "brew install vim"
18
+
19
+ @file = file
20
+
21
+ build_command
22
+ end
23
+
24
+
25
+ def build_command
26
+ @command = "#{name} #{DEFAULT_PARAMETERS} #{@file}"
27
+ end
28
+
29
+
30
+ def run
31
+ # Using 'system' instead of backticks becuase
32
+ # with the back ticks vim was complaining that it
33
+ # was not connected to a terminal.
34
+ system command
35
+ end
36
+ end
37
+
38
+ __END__
39
+
40
+ VIM - Vi IMproved 9.0 (2022 Jun 28, compiled Sep 30 2023 05:45:56)
41
+
42
+ Usage: vim [arguments] [file ..] edit specified file(s)
43
+ or: vim [arguments] - read text from stdin
44
+ or: vim [arguments] -t tag edit file where tag is defined
45
+ or: vim [arguments] -q [errorfile] edit file with first error
46
+
47
+ Arguments:
48
+ -- Only file names after this
49
+ -v Vi mode (like "vi")
50
+ -e Ex mode (like "ex")
51
+ -E Improved Ex mode
52
+ -s Silent (batch) mode (only for "ex")
53
+ -d Diff mode (like "vimdiff")
54
+ -y Easy mode (like "evim", modeless)
55
+ -R Readonly mode (like "view")
56
+ -Z Restricted mode (like "rvim")
57
+ -m Modifications (writing files) not allowed
58
+ -M Modifications in text not allowed
59
+ -b Binary mode
60
+ -l Lisp mode
61
+ -C Compatible with Vi: 'compatible'
62
+ -N Not fully Vi compatible: 'nocompatible'
63
+ -V[N][fname] Be verbose [level N] [log messages to fname]
64
+ -D Debugging mode
65
+ -n No swap file, use memory only
66
+ -r List swap files and exit
67
+ -r (with file name) Recover crashed session
68
+ -L Same as -r
69
+ -T <terminal> Set terminal type to <terminal>
70
+ --not-a-term Skip warning for input/output not being a terminal
71
+ --ttyfail Exit if input or output is not a terminal
72
+ -u <vimrc> Use <vimrc> instead of any .vimrc
73
+ --noplugin Don't load plugin scripts
74
+ -p[N] Open N tab pages (default: one for each file)
75
+ -o[N] Open N windows (default: one for each file)
76
+ -O[N] Like -o but split vertically
77
+ + Start at end of file
78
+ +<lnum> Start at line <lnum>
79
+ --cmd <command> Execute <command> before loading any vimrc file
80
+ -c <command> Execute <command> after loading the first file
81
+ -S <session> Source file <session> after loading the first file
82
+ -s <scriptin> Read Normal mode commands from file <scriptin>
83
+ -w <scriptout> Append all typed commands to file <scriptout>
84
+ -W <scriptout> Write all typed commands to file <scriptout>
85
+ -x Edit encrypted files
86
+ --startuptime <file> Write startup timing messages to <file>
87
+ --log <file> Start logging to <file> early
88
+ -i <viminfo> Use <viminfo> instead of .viminfo
89
+ --clean 'nocompatible', Vim defaults, no plugins, no viminfo
90
+ -h or --help Print Help (this message) and exit
91
+ --version Print version information and exit
@@ -1,27 +1,21 @@
1
- # lib/aia/external/tool.rb
1
+ # lib/aia/tools.rb
2
2
 
3
- class AIA::External::Tool
3
+ class AIA::Tools
4
4
  @@subclasses = []
5
5
 
6
- # This method is called whenever a subclass is created
7
6
  def self.inherited(subclass)
8
7
  @@subclasses << subclass
9
8
  end
10
9
 
10
+ attr_accessor :role, :name, :description, :url, :install
11
+
11
12
 
12
- attr_reader :name, :description, :url
13
-
14
- def initialize
15
- @role = :role
16
- @name = self.class.name.split('::').last.downcase
17
- @desc = "description"
18
- @url = "URL"
19
- @install = "brew install #{name}"
20
- end
21
-
22
-
23
- def self.tools
24
- @@subclasses.map(&:name)
13
+ def initialize(*)
14
+ @role = :role
15
+ @name = self.class.name.split('::').last.downcase
16
+ @description = "description"
17
+ @url = "URL"
18
+ @install = "brew install #{name}"
25
19
  end
26
20
 
27
21
 
@@ -31,14 +25,25 @@ class AIA::External::Tool
31
25
  end
32
26
 
33
27
 
34
- def help = `#{name} --help`
35
- def version = `#{name} --version`
28
+ def help
29
+ `#{name} --help`
30
+ end
31
+
32
+
33
+ def version
34
+ `#{name} --version`
35
+ end
36
36
 
37
37
 
38
- ###################################################
38
+ #########################################
39
39
  class << self
40
- def verify_tools(tools)
41
- missing_tools = tools.reject(&:installed?)
40
+ def tools
41
+ @@subclasses.map(&:name)
42
+ end
43
+
44
+
45
+ def verify_tools
46
+ missing_tools = @@subclasses.map(&:new).reject(&:installed?)
42
47
  unless missing_tools.empty?
43
48
  puts format_missing_tools_response(missing_tools)
44
49
  end
@@ -64,10 +69,9 @@ class AIA::External::Tool
64
69
  end
65
70
 
66
71
 
67
- Pathname.new(__dir__)
72
+ (Pathname.new(__dir__)+"tools")
68
73
  .glob('*.rb')
69
- .reject{|f| f.basename.to_s.end_with?('tool.rb') }
70
74
  .each do |tool|
71
- require_relative tool.basename.to_s
75
+ require_relative "tools/#{tool.basename.to_s}"
72
76
  end
73
77
 
data/lib/aia/version.rb CHANGED
@@ -1,6 +1,10 @@
1
1
  # lib/aia/version.rb
2
2
  # frozen_string_literal: true
3
3
 
4
+ require 'semver'
5
+
4
6
  module AIA
5
- VERSION = "0.3.4"
7
+ # .semver is located at the gem's root directory
8
+ version_file_path = File.join(__dir__, '..', '..')
9
+ VERSION = SemVer.find(version_file_path).to_s[1..]
6
10
  end
data/lib/aia.rb CHANGED
@@ -1,5 +1,9 @@
1
1
  # lib/aia.rb
2
2
 
3
+ require 'debug_me'
4
+ include DebugMe
5
+
6
+ require 'hashie'
3
7
  require 'pathname'
4
8
  require 'readline'
5
9
  require 'tempfile'
@@ -12,9 +16,19 @@ require_relative "aia/main"
12
16
  require_relative "core_ext/string_wrap"
13
17
 
14
18
  module AIA
15
- def self.run(args=ARGV)
16
- args = args.split(' ') if args.is_a?(String)
17
- AIA::Main.new(args).call
19
+ class << self
20
+ attr_accessor :config
21
+
22
+ def run(args=ARGV)
23
+ args = args.split(' ') if args.is_a?(String)
24
+
25
+ # TODO: Currently this is a one and done architecture.
26
+ # If the args contain an "-i" or and "--interactive"
27
+ # flag could this turn into some kind of
28
+ # conversation REPL?
29
+
30
+ AIA::Main.new(args).call
31
+ end
18
32
  end
19
33
  end
20
34
 
data/main.just ADDED
@@ -0,0 +1,56 @@
1
+ # aia/main.just
2
+ #
3
+ # Support man pages with ...
4
+ # gem install kramdown-man
5
+ #
6
+
7
+ RR := env_var('RR')
8
+
9
+ with ~/.justfile
10
+
11
+ # FIXME: justprep module process still has an issue with ~ and $HOME
12
+ # FIXME: justprep does not like more than one space between module name and path.
13
+
14
+ module repo /Users/dewayne/sandbox/git_repos/repo.just
15
+ module gem /Users/dewayne/sandbox/git_repos/gem.just
16
+ module version /Users/dewayne/just_modules/version.just
17
+
18
+
19
+ # Preview man page
20
+ preview_man_page:
21
+ kramdown-man {{RR}}/man/aia.1.md
22
+
23
+
24
+ # View man page
25
+ view_man_page: create_man_page
26
+ man {{RR}}/man/aia.1
27
+
28
+
29
+ # Create man page
30
+ create_man_page:
31
+ rake man
32
+
33
+ ##########################################
34
+
35
+ # Tag the current commit, push it, then bump the version
36
+ tag_push_and_bump: tag push bump
37
+
38
+
39
+ # Create a git tag for the current version
40
+ tag:
41
+ git tag $(semver)
42
+
43
+ # Push the git current working directory and all tags
44
+ push:
45
+ git push
46
+ git push origin --tags
47
+
48
+
49
+ alias inc := bump
50
+
51
+ # Increament version's level: major.minor.patch
52
+ @bump level='patch':
53
+ semver increment {{level}}
54
+ echo "Now working on: $(semver)"
55
+ git add {{RR}}/.semver
56
+