aia 0.3.4 → 0.3.19

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.
@@ -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
+