aia 0.0.5 → 0.3.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.
@@ -0,0 +1,58 @@
1
+ # lib/aia/external/sgpt.rb
2
+
3
+ class AIA::External::Sgpt < AIA::External::Tool
4
+ def initialize
5
+ super
6
+ @role = :backend
7
+ @desc = "shell-gpt"
8
+ @url = "https://github.com/TheR1D/shell_gpt"
9
+ @install = "pip install shell-gpt"
10
+ end
11
+ end
12
+
13
+ __END__
14
+
15
+ Usage: sgpt [OPTIONS] [PROMPT]
16
+
17
+ ╭─ Arguments ─────────────────────────────────────────────────────────────────────────────────╮
18
+ │ prompt [PROMPT] The prompt to generate completions for. │
19
+ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯
20
+ ╭─ Options ───────────────────────────────────────────────────────────────────────────────────╮
21
+ │ --model TEXT Large language model to use. │
22
+ │ [default: gpt-3.5-turbo] │
23
+ │ --temperature FLOAT RANGE [0.0<=x<=2.0] Randomness of generated │
24
+ │ output. │
25
+ │ [default: 0.1] │
26
+ │ --top-probability FLOAT RANGE [0.1<=x<=1.0] Limits highest probable │
27
+ │ tokens (words). │
28
+ │ [default: 1.0] │
29
+ │ --editor --no-editor Open $EDITOR to provide a │
30
+ │ prompt. │
31
+ │ [default: no-editor] │
32
+ │ --cache --no-cache Cache completion results. │
33
+ │ [default: cache] │
34
+ │ --help Show this message and exit. │
35
+ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯
36
+ ╭─ Assistance Options ────────────────────────────────────────────────────────────────────────╮
37
+ │ --shell -s Generate and execute shell commands. │
38
+ │ --describe-shell -d Describe a shell command. │
39
+ │ --code --no-code Generate only code. [default: no-code] │
40
+ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯
41
+ ╭─ Chat Options ──────────────────────────────────────────────────────────────────────────────╮
42
+ │ --chat TEXT Follow conversation with id, use "temp" for quick │
43
+ │ session. │
44
+ │ [default: None] │
45
+ │ --repl TEXT Start a REPL (Read–eval–print loop) session. │
46
+ │ [default: None] │
47
+ │ --show-chat TEXT Show all messages from provided chat id. │
48
+ │ [default: None] │
49
+ │ --list-chats --no-list-chats List all existing chat ids. │
50
+ │ [default: no-list-chats] │
51
+ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯
52
+ ╭─ Role Options ──────────────────────────────────────────────────────────────────────────────╮
53
+ │ --role TEXT System role for GPT model. [default: None] │
54
+ │ --create-role TEXT Create role. [default: None] │
55
+ │ --show-role TEXT Show role. [default: None] │
56
+ │ --list-roles --no-list-roles List roles. [default: no-list-roles] │
57
+ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯
58
+
@@ -0,0 +1,47 @@
1
+ # lib/aia/external/subl.rb
2
+
3
+ class AIA::External::Subl < AIA::External::Tool
4
+ def initialize
5
+ super
6
+ @role = :editor
7
+ @desc = "Sublime Text Editor"
8
+ @url = "https://www.sublimetext.com/"
9
+ @install = "echo 'Download from website'"
10
+ end
11
+
12
+
13
+ def open(file)
14
+ `#{name} #{file}`
15
+ end
16
+ end
17
+
18
+ __END__
19
+
20
+ $ subl --help
21
+ Sublime Text build 4166
22
+
23
+ Usage: subl [arguments] [files] Edit the given files
24
+ or: subl [arguments] [directories] Open the given directories
25
+ or: subl [arguments] -- [files] Edit files that may start with '-'
26
+ or: subl [arguments] - Edit stdin
27
+ or: subl [arguments] - >out Edit stdin and write the edit to stdout
28
+
29
+ Arguments:
30
+ --project <project>: Load the given project
31
+ --command <command>: Run the given command
32
+ -n or --new-window: Open a new window
33
+ --launch-or-new-window: Only open a new window if the application is open
34
+ -a or --add: Add folders to the current window
35
+ -w or --wait: Wait for the files to be closed before returning
36
+ -b or --background: Don't activate the application
37
+ -s or --stay: Keep the application activated after closing the file
38
+ --safe-mode: Launch using a sandboxed (clean) environment
39
+ -h or --help: Show help (this message) and exit
40
+ -v or --version: Show version and exit
41
+
42
+ --wait is implied if reading from stdin. Use --stay to not switch back
43
+ to the terminal when a file is closed (only relevant if waiting for a file).
44
+
45
+ Filenames may be given a :line or :line:column suffix
46
+
47
+
@@ -0,0 +1,37 @@
1
+ If you want to find out which classes have inherited from a class named `Tool`, you can leverage Ruby's `ObjectSpace` module to iterate through all the classes and select those that are descendants of `Tool`. Here's how you could write the code:
2
+
3
+ ```ruby
4
+ class Tool
5
+ # Tool class implementation
6
+ end
7
+
8
+ # Other classes that inherit from Tool
9
+ class Hammer < Tool; end
10
+ class Screwdriver < Tool; end
11
+ class Wrench < Tool; end
12
+
13
+ # Non-inheriting classes
14
+ class RandomClass; end
15
+
16
+ def find_descendants_of(klass)
17
+ ObjectSpace.each_object(Class).select { |c| c < klass }.map(&:name)
18
+ end
19
+
20
+ # Get the list of class names that inherit from Tool
21
+ descendant_classes = find_descendants_of(Tool)
22
+
23
+ # Format the list as markdown (as required)
24
+ markdown_list = descendant_classes.map { |name| "- #{name}" }.join("\n")
25
+ puts markdown_list
26
+ ```
27
+
28
+ When you run this code, you will get an output similar to the following (the actual order may vary):
29
+
30
+ ```
31
+ - Hammer
32
+ - Screwdriver
33
+ - Wrench
34
+ ```
35
+
36
+ This list shows the class names that have inherited from the `Tool` class, and it is formatted as a markdown list without enclosing backticks as requested.
37
+
@@ -0,0 +1,73 @@
1
+ # lib/aia/external/tool.rb
2
+
3
+ class AIA::External::Tool
4
+ @@subclasses = []
5
+
6
+ # This method is called whenever a subclass is created
7
+ def self.inherited(subclass)
8
+ @@subclasses << subclass
9
+ end
10
+
11
+
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)
25
+ end
26
+
27
+
28
+ def installed?
29
+ path = `which #{name}`.chomp
30
+ !path.empty? && File.executable?(path)
31
+ end
32
+
33
+
34
+ def help = `#{name} --help`
35
+ def version = `#{name} --version`
36
+
37
+
38
+ ###################################################
39
+ class << self
40
+ def verify_tools(tools)
41
+ missing_tools = tools.reject(&:installed?)
42
+ unless missing_tools.empty?
43
+ puts format_missing_tools_response(missing_tools)
44
+ end
45
+ end
46
+
47
+
48
+ def format_missing_tools_response(missing_tools)
49
+ response = <<~EOS
50
+
51
+ WARNING: AIA makes use of external CLI tools that are missing.
52
+
53
+ Please install the following tools:
54
+
55
+ EOS
56
+
57
+ missing_tools.each do |tool|
58
+ response << " #{tool.name}: install from #{tool.url}\n"
59
+ end
60
+
61
+ response
62
+ end
63
+ end
64
+ end
65
+
66
+
67
+ Pathname.new(__dir__)
68
+ .glob('*.rb')
69
+ .reject{|f| f.basename.to_s.end_with?('tool.rb') }
70
+ .each do |tool|
71
+ require_relative tool.basename.to_s
72
+ end
73
+
@@ -0,0 +1,259 @@
1
+ # lib/aia/external.rb
2
+
3
+ # TODO: move stuff associated with the CLI options for
4
+ # external commands to this module.
5
+ # Is the EDITOR considered an external command? Yes.
6
+
7
+ =begin
8
+
9
+ There are at least 4 processes handled by external tools:
10
+
11
+ search .......... default PromptManager::Prompt or search_proc
12
+ review/select ... using fzf either exact or fuzzy
13
+ edit ............ ENV['EDITOR']
14
+ execute ......... mods or sgpt or ???
15
+ with different models / settings
16
+
17
+ sgpt is the executable for "shell-gpt" a python project
18
+
19
+ =end
20
+
21
+ module AIA::External
22
+ class Mods; end
23
+ class Fzf; end
24
+ class Rg; end
25
+ class Editor; end
26
+ end
27
+
28
+ module AIA::External
29
+ TOOLS = {
30
+ 'fzf' => [ 'Command-line fuzzy finder written in Go',
31
+ 'https://github.com/junegunn/fzf'],
32
+
33
+ 'mods' => [ 'AI on the command-line',
34
+ 'https://github.com/charmbracelet/mods'],
35
+
36
+ 'rg' => [ 'Search tool like grep and The Silver Searcher',
37
+ 'https://github.com/BurntSushi/ripgrep']
38
+ }
39
+
40
+
41
+ HELP = <<~EOS
42
+ External Tools Used
43
+ -------------------
44
+
45
+ To install the external CLI programs used by aia:
46
+ brew install #{TOOLS.keys.join(' ')}
47
+
48
+ #{TOOLS.to_a.map{|t| t.join("\n ") }.join("\n\n")}
49
+
50
+ A text editor whose executable is setup in the
51
+ system environment variable 'EDITOR' like this:
52
+
53
+ export EDITOR="#{ENV['EDITOR']}"
54
+
55
+ EOS
56
+
57
+
58
+ # Setup the AI CLI program with necessary variables
59
+ def setup_external_programs
60
+ verify_external_tools
61
+
62
+ ai_default_opts = "-m #{MODS_MODEL} --no-limit "
63
+ ai_default_opts += "-f " if markdown?
64
+ @ai_options = ai_default_opts.dup
65
+
66
+
67
+ @ai_options += @extra_options.join(' ')
68
+
69
+ @ai_command = "#{AI_CLI_PROGRAM} #{@ai_options} "
70
+ end
71
+
72
+
73
+ # Check if the external tools are present on the system
74
+ def verify_external_tools
75
+ missing_tools = []
76
+
77
+ TOOLS.each do |tool, url|
78
+ path = `which #{tool}`.chomp
79
+ if path.empty? || !File.executable?(path)
80
+ missing_tools << { name: tool, url: url }
81
+ end
82
+ end
83
+
84
+ if missing_tools.any?
85
+ puts format_missing_tools_response(missing_tools)
86
+ end
87
+ end
88
+
89
+
90
+ def format_missing_tools_response(missing_tools)
91
+ response = <<~EOS
92
+
93
+ WARNING: #{MY_NAME} makes use of a few external CLI tools.
94
+ #{MY_NAME} may not respond as designed without these.
95
+
96
+ The following tools are missing on your system:
97
+
98
+ EOS
99
+
100
+ missing_tools.each do |tool|
101
+ response << " #{tool[:name]}: install from #{tool[:url]}\n"
102
+ end
103
+
104
+ response
105
+ end
106
+
107
+
108
+ # Build the command to interact with the AI CLI program
109
+ def build_command
110
+ command = @ai_command + %Q["#{@prompt.to_s}"]
111
+
112
+ @arguments.each do |input_file|
113
+ file_path = Pathname.new(input_file)
114
+ abort("File does not exist: #{input_file}") unless file_path.exist?
115
+ command += " < #{input_file}"
116
+ end
117
+
118
+ command
119
+ end
120
+
121
+
122
+ # Execute the command and log the results
123
+ def send_prompt_to_external_command
124
+ command = build_command
125
+
126
+ puts command if verbose?
127
+ @result = `#{command}`
128
+
129
+ if output.nil?
130
+ puts @result
131
+ else
132
+ output.write @result
133
+ end
134
+
135
+ @result
136
+ end
137
+ end
138
+
139
+
140
+ __END__
141
+
142
+
143
+
144
+ MODS_MODEL = ENV['MODS_MODEL'] || 'gpt-4-1106-preview'
145
+
146
+ AI_CLI_PROGRAM = "mods"
147
+ ai_default_opts = "-m #{MODS_MODEL} --no-limit -f"
148
+ ai_options = ai_default_opts.dup
149
+
150
+ extra_inx = ARGV.index('--')
151
+
152
+ if extra_inx
153
+ ai_options += " " + ARGV[extra_inx+1..].join(' ')
154
+ ARGV.pop(ARGV.size - extra_inx)
155
+ end
156
+
157
+ AI_COMMAND = "#{AI_CLI_PROGRAM} #{ai_options} "
158
+ EDITOR = ENV['EDITOR']
159
+ PROMPT_DIR = HOME + ".prompts"
160
+ PROMPT_LOG = PROMPT_DIR + "_prompts.log"
161
+ PROMPT_EXTNAME = ".txt"
162
+ DEFAULTS_EXTNAME = ".json"
163
+ # SEARCH_COMMAND = "ag -l"
164
+ KEYWORD_REGEX = /(\[[A-Z _|]+\])/
165
+
166
+ AVAILABLE_PROMPTS = PROMPT_DIR
167
+ .children
168
+ .select{|c| PROMPT_EXTNAME == c.extname}
169
+ .map{|c| c.basename.to_s.split('.')[0]}
170
+
171
+ AVAILABLE_PROMPTS_HELP = AVAILABLE_PROMPTS
172
+ .map{|c| " * " + c}
173
+ .join("\n")
174
+
175
+
176
+ AI_CLI_PROGRAM_HELP = `#{AI_CLI_PROGRAM} --help`
177
+
178
+ HELP = <<EOHELP
179
+ AI CLI Program
180
+ ==============
181
+
182
+ The AI cli program being used is: #{AI_CLI_PROGRAM}
183
+
184
+ The defaul options to #{AI_CLI_PROGRAM} are:
185
+ "#{ai_default_opts}"
186
+
187
+ You can pass additional CLI options to #{AI_CLI_PROGRAM} like this:
188
+ "#{my_name} my options -- options for #{AI_CLI_PROGRAM}"
189
+
190
+ #{AI_CLI_PROGRAM_HELP}
191
+
192
+ EOHELP
193
+
194
+
195
+
196
+ AG_COMMAND = "ag --file-search-regex '\.txt$' e" # searching for the letter "e"
197
+ CD_COMMAND = "cd #{PROMPT_DIR}"
198
+ FIND_COMMAND = "find . -name '*.txt'"
199
+
200
+ FZF_OPTIONS = [
201
+ "--tabstop=2", # 2 soaces for a tab
202
+ "--header='Prompt contents below'",
203
+ "--header-first",
204
+ "--prompt='Search term: '",
205
+ '--delimiter :',
206
+ "--preview 'ww {1}'", # ww comes from the word_wrap gem
207
+ "--preview-window=down:50%:wrap"
208
+ ].join(' ')
209
+
210
+ FZF_OPTIONS += " --exact" unless fuzzy?
211
+
212
+ FZF_COMMAND = "#{CD_COMMAND} ; #{FIND_COMMAND} | fzf #{FZF_OPTIONS}"
213
+ AG_FZF_COMMAND = "#{CD_COMMAND} ; #{AG_COMMAND} | fzf #{FZF_OPTIONS}"
214
+
215
+ # use `ag` ti build a list of text lines from each prompt
216
+ # use `fzf` to search through that list to select a prompt file
217
+
218
+ def ag_fzf = `#{AG_FZF_COMMAND}`.split(':')&.first&.strip&.gsub('.txt','')
219
+
220
+
221
+ if configatron.prompt.empty?
222
+ unless first_argument_is_a_prompt?
223
+ configatron.prompt = ag_fzf
224
+ end
225
+ end
226
+
227
+ ###############################################
228
+
229
+
230
+
231
+ #!/usr/bin/env bash
232
+ # ~/scripts/ripfzfsubl
233
+ #
234
+ # Uses Sublime Text (subl) as the text editor
235
+ #
236
+ # brew install bat ripgrep fzf
237
+ #
238
+ # bat Clone of cat(1) with syntax highlighting and Git integration
239
+ # |__ https://github.com/sharkdp/bat
240
+ #
241
+ # ripgrep Search tool like grep and The Silver Searcher
242
+ # |__ https://github.com/BurntSushi/ripgrep
243
+ #
244
+ # fzf Command-line fuzzy finder written in Go
245
+ # |__ https://github.com/junegunn/fzf
246
+ #
247
+ #
248
+ # 1. Search for text in files using Ripgrep
249
+ # 2. Interactively narrow down the list using fzf
250
+ # 3. Open the file in Sublime Text Editor
251
+
252
+ rg --color=always --line-number --no-heading --smart-case "${*:-}" |
253
+ fzf --ansi \
254
+ --color "hl:-1:underline,hl+:-1:underline:reverse" \
255
+ --delimiter : \
256
+ --preview 'bat --color=always {1} --highlight-line {2}' \
257
+ --preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
258
+ --bind 'enter:become(subl {1}:{2})'
259
+
@@ -0,0 +1,43 @@
1
+ # lib/aia/external_two.rb
2
+ #
3
+ # Maybe something like this ...
4
+ #
5
+ # or a class structure based upon function where the external
6
+ # tool and its default options can be injected.
7
+ #
8
+ module AIA::External
9
+ EDITOR = ENV['EDITOR']
10
+
11
+
12
+ end
13
+
14
+ # Usage example:
15
+
16
+ # Verify and install tools if needed
17
+ mods = AIA::External::Mods.new
18
+ fzf = AIA::External::Fzf.new
19
+ rg = AIA::External::Rg.new
20
+ tools = [mods, fzf, rg]
21
+ AIA::External.verify_tools(tools)
22
+
23
+ # Build command for Mods tool with extra_options
24
+ extra_options = ['--some-extra-option']
25
+ mods_command = mods.command(extra_options)
26
+ puts "Mods command: #{mods_command}"
27
+
28
+ # Open a file with the system editor
29
+ AIA::External::Editor.open('path/to/file.txt')
30
+
31
+ # Search and select a file using Fzf tool
32
+ fzf_options = {
33
+ prompt_dir: 'path/to/prompts',
34
+ fuzzy: true
35
+ }
36
+ fzf_command = fzf.command(fzf_options)
37
+ puts "Fzf command: #{fzf_command}"
38
+
39
+ # Use Rg tool to search within files
40
+ search_term = 'search_query'
41
+ rg_command = rg.command(search_term, fzf_options: fzf.options)
42
+ puts "Rg command: #{rg_command}"
43
+
data/lib/aia/main.rb CHANGED
@@ -6,7 +6,7 @@ require_relative 'configuration'
6
6
 
7
7
  require_relative 'cli'
8
8
  require_relative 'prompt_processing'
9
- require_relative 'external_commands'
9
+ require_relative 'external'
10
10
  require_relative 'logging'
11
11
 
12
12
  # Everything is being handled within the context
@@ -16,7 +16,7 @@ class AIA::Main
16
16
  include AIA::Configuration
17
17
  include AIA::Cli
18
18
  include AIA::PromptProcessing
19
- include AIA::ExternalCommands
19
+ include AIA::External
20
20
  include AIA::Logging
21
21
 
22
22
 
@@ -1,6 +1,7 @@
1
1
  # lib/aia/prompt_processing.rb
2
2
 
3
3
  module AIA::PromptProcessing
4
+ KW_HISTORY_MAX = 5
4
5
 
5
6
  # Fetch the first argument which should be the prompt id
6
7
  def get_prompt
@@ -18,6 +19,19 @@ module AIA::PromptProcessing
18
19
  # Check if a prompt with the given id already exists
19
20
  def existing_prompt?(prompt_id)
20
21
  @prompt = PromptManager::Prompt.get(id: prompt_id)
22
+
23
+ # FIXME: Kludge until prompt_manager is changed
24
+ # prompt_manager v0.3.0 now supports this feature.
25
+ # keeping the kludge in for legacy JSON files
26
+ # files which have not yet been reformatted.
27
+ @prompt.keywords.each do |kw|
28
+ if @prompt.parameters[kw].nil? || @prompt.parameters[kw].empty?
29
+ @prompt.parameters[kw] = []
30
+ else
31
+ @prompt.parameters[kw] = Array(@prompt.parameters[kw])
32
+ end
33
+ end
34
+
21
35
  true
22
36
  rescue ArgumentError
23
37
  false
@@ -36,31 +50,45 @@ module AIA::PromptProcessing
36
50
 
37
51
 
38
52
  def replace_keywords
39
- print "\nQuit #{MY_NAME} with a CNTL-D or a CNTL-C\n\n"
53
+ puts
54
+ puts "ID: #{@prompt.id}"
40
55
 
41
- defaults = @prompt.parameters
56
+ show_prompt_without_comments
42
57
 
58
+ puts "\nPress up/down arrow to scroll through history."
59
+ puts "Type new input or edit the current input."
60
+ puts "Quit #{MY_NAME} with a CNTL-D or a CNTL-C"
61
+ puts
43
62
  @prompt.keywords.each do |kw|
44
- defaults[kw] = keyword_value(kw, defaults[kw])
63
+ value = keyword_value(kw, @prompt.parameters[kw])
64
+
65
+ unless value.nil? || value.strip.empty?
66
+ value_inx = @prompt.parameters[kw].index(value)
67
+
68
+ if value_inx
69
+ @prompt.parameters[kw].delete_at(value_inx)
70
+ end
71
+
72
+ # The most recent value for this kw will always be
73
+ # in the last position
74
+ @prompt.parameters[kw] << value
75
+ @prompt.parameters[kw].shift if @prompt.parameters[kw].size > KW_HISTORY_MAX
76
+ end
45
77
  end
46
-
47
- @prompt.parameters = defaults
48
78
  end
49
79
 
50
80
 
51
-
52
-
53
81
  # query the user for a value to the keyword allow the
54
82
  # reuse of the previous value shown as the default
55
- def keyword_value(kw, default)
56
- label = "Default: "
83
+ def keyword_value(kw, history_array)
84
+
85
+ Readline::HISTORY.clear
86
+ Array(history_array).each { |entry| Readline::HISTORY.push(entry) unless entry.nil? || entry.empty? }
87
+
57
88
  puts "Parameter #{kw} ..."
58
- default_wrapped = default.wrap(indent: label.size)
59
- default_wrapped[0..label.size] = label
60
- puts default_wrapped
61
89
 
62
90
  begin
63
- a_string = Readline.readline("\n-=> ", false)
91
+ a_string = Readline.readline("\n-=> ", true)
64
92
  rescue Interrupt
65
93
  a_string = nil
66
94
  end
@@ -155,4 +183,30 @@ module AIA::PromptProcessing
155
183
  @prompt = PromptManager::Prompt.get(id: @prompt.id)
156
184
  end
157
185
 
186
+
187
+ def show_prompt_without_comments
188
+ puts remove_comments.wrap(indent: 4)
189
+ end
190
+
191
+
192
+ def remove_comments
193
+ lines = @prompt.text
194
+ .split("\n")
195
+ .reject{|a_line| a_line.strip.start_with?('#')}
196
+
197
+ # Remove empty lines at the start of the prompt
198
+ #
199
+ lines = lines.drop_while(&:empty?)
200
+
201
+ # Drop all the lines at __END__ and after
202
+ #
203
+ logical_end_inx = lines.index("__END__")
204
+
205
+ if logical_end_inx
206
+ lines[0...logical_end_inx] # NOTE: ... means to not include last index
207
+ else
208
+ lines
209
+ end.join("\n")
210
+ end
158
211
  end
212
+
data/lib/aia/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module AIA
5
- VERSION = "0.0.5"
5
+ VERSION = "0.3.0"
6
6
  end