-
Notifications
You must be signed in to change notification settings - Fork 2
/
aip.rb
executable file
·489 lines (335 loc) · 14.1 KB
/
aip.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
#!/usr/bin/env ruby
# encoding: utf-8
# frozen_string_literal: true
# warn_indent: true
##########################################################
###
## File: aip.rb
## Desc: Use generative AI with saved parameterized prompts
## By: Dewayne VanHoozer ([email protected])
##
## This program makes use of the gem word_wrap's
## CLI tool: ww
#
# See: aip_completion.sh
# which sets up auto TAB completion for the prompts_dir
# source aip_completion.sh whenever a new prompt is added
# or an old one deleted.
#
=begin
brew install fzf mods the_silver_searcher
fzf Command-line fuzzy finder written in Go
|__ https://github.com/junegunn/fzf
mods AI on the command-line
|__ https://github.com/charmbracelet/mods
the_silver_searcher Code-search similar to ack
|__ https://github.com/ggreer/the_silver_searcher
Program Summary
The program is a Ruby script that integrates with the `mods` CLI tool, which is built on a GPT-based generative AI model. This script is designed to make use of generative AI through a set of saved, parameterized prompts. Users can easily interact with the following features:
- **Prompt Selection**: Users have the ability to choose a prompt from a curated list. This selection process is streamlined by allowing users to search and filter prompts using keywords.
- **Prompt Editing**: There is functionality for a user to modify the text of an existing prompt, tailoring it to better meet their specific needs.
- **File Input**: The script can read in data from input files, providing the necessary context or information required for the AI to generate relevant content.
- **AI Integration**: Utilizing the `mods` GPT-based CLI tool, the script takes the chosen edited prompt to guide the AI in generating its output.
- **Output Management**: After the generative process, the resulting output is saved to a designated file, ensuring that the user has a record of the AI's creations.
- **Logging**: For tracking and accountability, the program records the details of each session, including the prompt used, the AI-generated output, and the precise timestamp when the generation occurred.
This robust tool is excellent for users who wish to harness the power of generative AI for creating content, with an efficient and user-friendly system for managing the creation process.
=end
#
# TODO: I think this script has reached the point where
# it is ready to become a proper gem.
#
# New Gem: prompt_manager has been created using this
# this script as its requirements. Will be retrofitting that
# new gem into here.
#
#
require 'prompt_manager'
# TODO: refactor aip.rb to use the prompt_manager gem.
require 'pathname'
HOME = Pathname.new( ENV['HOME'] )
MODS_MODEL = ENV['MODS_MODEL'] || 'gpt-4-1106-preview'
AI_CLI_PROGRAM = "mods"
ai_default_opts = "-m #{MODS_MODEL} --no-limit -f"
ai_options = ai_default_opts.dup
extra_inx = ARGV.index('--')
if extra_inx
ai_options += " " + ARGV[extra_inx+1..].join(' ')
ARGV.pop(ARGV.size - extra_inx)
end
AI_COMMAND = "#{AI_CLI_PROGRAM} #{ai_options} "
EDITOR = ENV['EDITOR']
PROMPT_DIR = HOME + ".prompts"
PROMPT_LOG = PROMPT_DIR + "_prompts.log"
PROMPT_EXTNAME = ".txt"
DEFAULTS_EXTNAME = ".json"
# SEARCH_COMMAND = "ag -l"
KEYWORD_REGEX = /(\[[A-Z _|]+\])/
AVAILABLE_PROMPTS = PROMPT_DIR
.children
.select{|c| PROMPT_EXTNAME == c.extname}
.map{|c| c.basename.to_s.split('.')[0]}
AVAILABLE_PROMPTS_HELP = AVAILABLE_PROMPTS
.map{|c| " * " + c}
.join("\n")
require 'amazing_print'
require 'json'
require 'readline' # TODO: or reline ??
require 'word_wrap'
require 'word_wrap/core_ext'
require 'debug_me'
include DebugMe
require 'cli_helper'
include CliHelper
configatron.version = '1.1.0'
AI_CLI_PROGRAM_HELP = `#{AI_CLI_PROGRAM} --help`
HELP = <<EOHELP
AI CLI Program
==============
The AI cli program being used is: #{AI_CLI_PROGRAM}
The defaul options to #{AI_CLI_PROGRAM} are:
"#{ai_default_opts}"
You can pass additional CLI options to #{AI_CLI_PROGRAM} like this:
"#{my_name} my options -- options for #{AI_CLI_PROGRAM}"
#{AI_CLI_PROGRAM_HELP}
EOHELP
cli_helper("Use generative AI with saved parameterized prompts") do |o|
o.string '-p', '--prompt', 'The prompt name', default: ""
o.bool '-e', '--edit', 'Edit the prompt text', default: false
o.bool '-f', '--fuzzy', 'Allow fuzzy matching', default: false
o.path '-o', '--output', 'The output file', default: Pathname.pwd + "temp.md"
end
AG_COMMAND = "ag --file-search-regex '\.txt$' e" # searching for the letter "e"
CD_COMMAND = "cd #{PROMPT_DIR}"
FIND_COMMAND = "find . -name '*.txt'"
FZF_OPTIONS = [
"--tabstop=2", # 2 soaces for a tab
"--header='Prompt contents below'",
"--header-first",
"--prompt='Search term: '",
'--delimiter :',
"--preview 'ww {1}'", # ww comes from the word_wrap gem
"--preview-window=down:50%:wrap"
].join(' ')
FZF_OPTIONS += " --exact" unless fuzzy?
FZF_COMMAND = "#{CD_COMMAND} ; #{FIND_COMMAND} | fzf #{FZF_OPTIONS}"
AG_FZF_COMMAND = "#{CD_COMMAND} ; #{AG_COMMAND} | fzf #{FZF_OPTIONS}"
# use `ag` ti build a list of text lines from each prompt
# use `fzf` to search through that list to select a prompt file
def ag_fzf = `#{AG_FZF_COMMAND}`.split(':')&.first&.strip&.gsub('.txt','')
configatron.input_files = get_pathnames_from( configatron.arguments, %w[.rb .txt .md])
def first_argument_is_a_prompt?
prompt = configatron.arguments.shift
if is_a_prompt?(prompt)
configatron.prompt = prompt
# SMELL: what-a-hack
configatron.errors = configatron.errors.reject{|e| e.end_with?(prompt)}
true
else
configatron.arguments.prepend prompt
false
end
end
def is_a_prompt?(prompt_candidate)
prompt_path = PROMPT_DIR + (prompt_candidate + PROMPT_EXTNAME)
prompt_path.exist?
end
if configatron.prompt.empty?
unless first_argument_is_a_prompt?
configatron.prompt = ag_fzf
end
end
unless edit?
if configatron.prompt.nil? || configatron.prompt.empty?
error "No prompt provided"
end
end
abort_if_errors
configatron.prompt_path = PROMPT_DIR + (configatron.prompt + PROMPT_EXTNAME)
configatron.defaults_path = PROMPT_DIR + (configatron.prompt + DEFAULTS_EXTNAME)
if !configatron.prompt_path.exist? && !edit?
error "This prompt does not exist: #{configatron.prompt}\n"
end
configatron.prompt_path = PROMPT_DIR + (configatron.prompt + PROMPT_EXTNAME)
configatron.defaults_path = PROMPT_DIR + (configatron.prompt + DEFAULTS_EXTNAME)
abort_if_errors
if edit?
unless configatron.prompt_path.exist?
configatron.prompt_path.write <<~PROMPT
# #{configatron.prompt_path.relative_path_from(HOME)}
# DESC:
PROMPT
end
`#{EDITOR} #{configatron.prompt_path}`
end
######################################################
# Local methods
def extract_raw_prompt
array_of_strings = ignore_after_end
print_header_comment(array_of_strings)
array_of_strings.reject do |a_line|
a_line.chomp.strip.start_with?('#')
end
.join("\n")
end
def ignore_after_end
array_of_strings = configatron.prompt_path.readlines
.map{|a_line| a_line.chomp.strip}
x = array_of_strings.index("__END__")
unless x.nil?
array_of_strings = array_of_strings[..x-1]
end
array_of_strings
end
def print_header_comment(array_of_strings)
print "\n\n" if verbose?
x = 0
while array_of_strings[x].start_with?('#') do
puts array_of_strings[x]
x += 1
end
print "\n\n" if x>0 && verbose?
end
# Returns an Array of keywords or phrases that look like:
# [KEYWORD]
# [KEYWORD|KEYWORD]
# [KEY PHRASE]
# [KEY PHRASE | KEY PHRASE | KEY_WORD]
#
def extract_keywords_from(prompt_raw)
prompt_raw.scan(KEYWORD_REGEX).flatten.uniq
end
# get the replacements for the keywords
def replacements_for(keywords)
replacements = load_default_replacements
keywords.each do |kw|
default = replacements[kw]
print "#{kw} (#{default}) ? "
a_string = Readline.readline("\n> ", false)
replacements[kw] = a_string unless a_string.empty?
end
save_default_replacements(replacements)
replacements
end
def load_default_replacements
if configatron.defaults_path.exist?
JSON.parse(configatron.defaults_path.read)
else
{}
end
end
def save_default_replacements(a_hash)
return if a_hash.empty?
defaults = a_hash.to_json
configatron.defaults_path.write defaults
end
# substitute the replacements for the keywords
def replace_keywords_with replacements, prompt_raw
prompt = prompt_raw.dup
replacements.each_pair do |keyword, replacement|
prompt.gsub!(keyword, replacement)
end
prompt
end
def log(prompt_path, prompt_raw, answer)
f = File.open(PROMPT_LOG, "ab")
f.write <<~EOS
=======================================
== #{Time.now}
== #{prompt_path}
PROMPT: #{prompt_raw}
RESULT:
#{answer}
EOS
end
######################################################
# Main
at_exit do
puts
puts "Done."
puts
end
ap configatron.to_h if debug?
configatron.prompt_raw = extract_raw_prompt
puts
puts "PROMPT:"
puts configatron.prompt_raw.wrap
puts
keywords = extract_keywords_from configatron.prompt_raw
replacements = replacements_for keywords
prompt = replace_keywords_with replacements, configatron.prompt_raw
ptompt = %Q{prompt}
command = AI_COMMAND + '"' + prompt + '"'
configatron.input_files.each do |input_file|
command += " < #{input_file}"
end
print "\n\n" if verbose? && !keywords.empty?
if verbose?
puts "="*42
puts command
puts "="*42
print "\n\n"
end
result = `#{command}`
configatron.output.write result
log configatron.prompt_path, prompt, result
__END__
To specify a history and autocomplete options with the readline method in Ruby using the readline gem, you can follow these steps:
1. **History** - To enable history functionality, create a Readline::HISTORY object:
```ruby
history = Readline::HISTORY
```
You can then use the `history` object to add and manipulate history entries.
2. **Autocomplete** - To enable autocomplete functionality, you need to provide a completion proc to `Readline.completion_proc`:
```ruby
Readline.completion_proc = proc { |input| ... }
```
You should replace `...` with the logic for determining the autocomplete options based on the input.
For example, you can define a method that provides autocomplete options based on a predefined array:
```ruby
def autocomplete_options(input)
available_options = ['apple', 'banana', 'cherry']
available_options.grep(/^#{Regexp.escape(input)}/)
end
Readline.completion_proc = proc { |input| autocomplete_options(input) }
```
In this example, the `autocomplete_options` method takes the user's input and uses the `grep` method to filter the available options based on the input prefix.
Remember to require the readline gem before using these features:
```ruby
require 'readline'
```
With the above steps in place, you can use the readline method in your code, and the specified history and autocomplete options will be available.
Note: Keep in mind that autocomplete options will only appear when tab is pressed while entering input.
### README for aip.rb Ruby Script
#### Overview
The `aip.rb` Ruby script is a command-line interface (CLI) tool designed to leverage generative AI with saved parameterized prompts. It integrates with the `mods` command-line tool that uses a GPT-based model to generate responses based on user-provided prompts. The script offers an array of features that make interacting with AI models more convenient and streamlined.
#### Features
- **Prompt Management**
- Users can select prompts from a saved collection with the help of command-line searching and filtering.
- Prompts can be edited by the user to better fit their specific context or requirement.
- Support for reading input from files to provide context for AI generation is included.
- **AI Integration**
- The script interacts with `mods`, a generative AI utilizing GPT-based models, to produce outputs from the prompts.
- **Output Handling**
- Generated content is saved to a specified file for record-keeping and further use.
- **Activity Logging**
- All actions, including prompt usage and AI output, are logged with timestamps for review and auditing purposes.
#### Dependencies
The script requires the installation of the following command-line tools:
- `fzf`: a powerful command-line fuzzy finder.
- `mods`: an AI-powered CLI tool for generative AI interactions.
- `the_silver_searcher (ag)`: a code-searching tool similar to ack and used for searching prompts.
#### Usage
The `aip.rb` script offers a set of command-line options to guide the interaction with AI:
- `-p, --prompt`: Specify the prompt name to be used.
- `-e, --edit`: Open the prompt text for editing before generation.
- `-f, --fuzzy`: Allows fuzzy matching for prompt selection.
- `-o, --output`: Sets the output file for the generated content.
Additional flags and options can be passed to the `mods` tool by appending them after a `--` separator.
#### Installation
Before using the script, one must ensure the required command-line tools (`fzf`, `mods`, and `the_silver_searcher`) are installed, and the Ruby environment is correctly set up with the necessary gems.
#### Development Notes
The author suggests that the script has matured enough to be converted into a Ruby gem for easier distribution and installation.
#### Getting Help
For help with using the CLI tool or further understanding the `mods` command, users can refer to the AI CLI Program help section included in the script or by invoking the `--help` flag.
#### Conclusion
The `aip.rb` script is designed to offer a user-friendly and flexible approach to integrating generative AI into content creation processes. It streamlines the interactions and management of AI-generated content by providing prompt management, AI integration, and logging capabilities, packaged inside a simple command-line utility.