aia 0.0.5 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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