ask_chatgpt 0.3.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 86f36d546bd015947fcac7a2bb9366530ba0ff8a9012f21f2151ae0484ecf647
4
- data.tar.gz: 136c817bc54317c77325ca0145857262d48680b8b25072e3364dcd0fe526e501
3
+ metadata.gz: e8d16800c079df0252e84b7448b7633ed07b4bdf1580a06bb76ba4e27292764e
4
+ data.tar.gz: 9233fd8f8bbbcfd3cea1db3745be6633b710585d77eaf056ee6681c5b202d65c
5
5
  SHA512:
6
- metadata.gz: 7b3112557fcb6e3fa11856693e26dc37df80dedafde058f6f181d303a336f75cc4ab21f9c1daf89dd61d253eea150b05d5f664408ab5299c07210f37d8f9d87c
7
- data.tar.gz: 4c5a22ca8157813e15fa5e2f639eefd89967ba9bbd300d6c0929bfe18cd3b2ff4e600197d59411520da05626c77ec9ca1786e2b6f3bfd7b51a2d5403f4528098
6
+ metadata.gz: 9586b20bda0e6c3a485e1ac6e1eebf91a37ac8728a1e949c917a9ab972cdc1ee8a0b16aa8f76a700e5540ac15aa8f453d06315fdb1d418ba3de6b010504d9b07
7
+ data.tar.gz: 5516576d94f71d473545b77083990d005157138c97aba1e5992faf8215592b7c2025932b1d9ea74bfcea86b016370e8e00bd1502fb34934968f04f2395a97a84
data/README.md CHANGED
@@ -5,6 +5,8 @@
5
5
 
6
6
  AI-Powered Assistant Gem right in your Rails console.
7
7
 
8
+ NEW UPDATE: Voice input using your microphone, demo: https://youtu.be/uBR0wnQvKao
9
+
8
10
  ![AskChatGPT](docs/interactive.gif)
9
11
 
10
12
  A Gem that leverages the power of AI to make your development experience more efficient and enjoyable. With this gem, you can streamline your coding process, effortlessly refactor and improve your code, and even generate tests on the fly.
@@ -18,6 +20,8 @@ Go to Rails console and run:
18
20
  ```ruby
19
21
  gpt.ask("how to get max age of user with projects from Ukraine").with_model(User, Project, Country)
20
22
  gpt.ask("convert json to xml")
23
+ gpt.with_code(User, Project).ask "make it better" # with_class alias for with_code
24
+ gpt.with_class(User).ask "make it better"
21
25
  gpt.payload(json).ask("extract emails from json")
22
26
  gpt.refactor("User.get_report")
23
27
  gpt.improve("User.get_report")
@@ -31,6 +35,10 @@ Go to Rails console and run:
31
35
  [first_name, last_name].join
32
36
  end
33
37
  }
38
+ #
39
+ # --- NEW ---
40
+ #
41
+ gpt.speak # or with alias gpt.s
34
42
  ```
35
43
 
36
44
  OR with CLI tool:
@@ -46,6 +54,8 @@ aGVsbG8gd29ybGQ=
46
54
  hello world
47
55
  ```
48
56
 
57
+ >ask_chatgpt -s 1 # start voice input with CLI
58
+
49
59
  See some examples below. You can also create your own prompts with just few lines of code [here](#options--configurations).
50
60
 
51
61
  Also you can use a CLI tool, [how to use it](#cli-tool).
@@ -113,6 +123,17 @@ And you can edit:
113
123
  # config.max_tokens = 3000 # or nil by default
114
124
  # config.included_prompts = []
115
125
 
126
+ # enable voice input with `gpt.speak` or `gpt.s`. Note, you also need to configure `audio_device_id`
127
+ # config.voice_enabled = true
128
+
129
+ # to get audio device ID (index in the input devices)
130
+ # install ffmpeg, and execute from the console
131
+ # `ffmpeg -f avfoundation -list_devices true -i ""`
132
+ # config.audio_device_id = 1
133
+
134
+ # after "voice_max_duration" seconds it will send audio to Open AI
135
+ # config.voice_max_duration = 10 # 10 seconds
136
+
116
137
  # Examples of custom prompts:
117
138
  # you can use them `gpt.extract_email("some string")`
118
139
 
@@ -182,8 +203,55 @@ end
182
203
 
183
204
  or directly in console `gpt.debug!` (and finish `gpt.debug!(:off)`)
184
205
 
206
+ ## Voice Input
207
+
208
+ Demo: https://youtu.be/uBR0wnQvKao
209
+
210
+ For now I consider this as an experimental and fun feature. Look forward seeing your feedback.
211
+
212
+ Works with command: `gpt.speak` or `gpt.s` (alias).
213
+
214
+ This command starts recording right away and it will stop after `voice_max_duration` seconds or if you press any key.
215
+
216
+ To exit recording mode press `Q`.
217
+
218
+ Voice is using `ffmpeg` tool, so you need to install it. Some instruction like this will work: https://www.hostinger.com/tutorials/how-to-install-ffmpeg.
219
+
220
+ Also, you need to configure `audio_device_id`. Run `ffmpeg -f avfoundation -list_devices true -i ""`
221
+
222
+ It will give you list of all devices, like this:
223
+
224
+ ```s
225
+ ffmpeg -f avfoundation -list_devices true -i ""
226
+ ffmpeg version 6.0 Copyright (c) 2000-2023 the FFmpeg developers
227
+ built with Apple clang version 14.0.0 (clang-1400.0.29.202)
228
+ configuration: --prefix=/usr/local/Cellar/ffmpeg/6.0 --enable-shared --enable-pthreads --enable-version3 --cc=clang --host-cflags= --host-ldflags= --enable-ffplay --enable-gnutls --enable-gpl --enable-libaom --enable-libaribb24 --enable-libbluray --enable-libdav1d --enable-libmp3lame --enable-libopus --enable-librav1e --enable-librist --enable-librubberband --enable-libsnappy --enable-libsrt --enable-libsvtav1 --enable-libtesseract --enable-libtheora --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libxvid --enable-lzma --enable-libfontconfig --enable-libfreetype --enable-frei0r --enable-libass --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libspeex --enable-libsoxr --enable-libzmq --enable-libzimg --disable-libjack --disable-indev=jack --enable-videotoolbox
229
+ libavutil 58. 2.100 / 58. 2.100
230
+ libavcodec 60. 3.100 / 60. 3.100
231
+ libavformat 60. 3.100 / 60. 3.100
232
+ libavdevice 60. 1.100 / 60. 1.100
233
+ libavfilter 9. 3.100 / 9. 3.100
234
+ libswscale 7. 1.100 / 7. 1.100
235
+ libswresample 4. 10.100 / 4. 10.100
236
+ libpostproc 57. 1.100 / 57. 1.100
237
+ [AVFoundation indev @ 0x7f7fd1a04380] AVFoundation video devices:
238
+ [AVFoundation indev @ 0x7f7fd1a04380] [0] FaceTime HD Camera
239
+ [AVFoundation indev @ 0x7f7fd1a04380] [1] USB Camera VID:1133 PID:2085
240
+ [AVFoundation indev @ 0x7f7fd1a04380] [2] Capture screen 0
241
+ [AVFoundation indev @ 0x7f7fd1a04380] [3] Capture screen 1
242
+ [AVFoundation indev @ 0x7f7fd1a04380] AVFoundation audio devices:
243
+ [AVFoundation indev @ 0x7f7fd1a04380] [0] Microsoft Teams Audio
244
+ [AVFoundation indev @ 0x7f7fd1a04380] [1] Built-in Microphone
245
+ [AVFoundation indev @ 0x7f7fd1a04380] [2] Unknown USB Audio Device
246
+ : Input/output error
247
+ ```
248
+
249
+ In my case I used "1", because it's `Built-in Microphone`.
250
+
185
251
  ## CLI Tool
186
252
 
253
+ You can ask questions from cli or even start voice input.
254
+
187
255
  Example 1:
188
256
  ![AskChatGPT](docs/unzip.gif)
189
257
 
@@ -197,6 +265,8 @@ How to use:
197
265
  ask_chatgpt -f app/models/user.rb -q "find a bug in this Rails model"
198
266
  ask_chatgpt -f app/models/user.rb -q "create RSpec spec for this model"
199
267
  ask_chatgpt -f test/dummy/Gemfile -q "sort Ruby gems alphabetically"
268
+ ask_chatgpt -m 3.5 -q "How to parse JSON file in Ruby?"
269
+ ask_chatgpt -m 4 -q "Why Ruby is the best language?"
200
270
  ```
201
271
 
202
272
  ## Streaming (async vs sync mode)
@@ -232,6 +302,11 @@ end
232
302
  - print tokens usage? `.with_usage`
233
303
  - support org_id? in the configs
234
304
  - use `gpt` in the code of the main app (e.g. model/controller)
305
+ - when voice is used add support for payloads, e.g. `gpt.with_payload(json).speak` (and it will send payload with my question)
306
+ - refactor voice input code :) as first version it's fine
307
+ - can we discover audio device ID?
308
+ - use tempfile for audio, instead of output.wav
309
+ - handle case when empty response is returned in CLI (when for example non-existing model is specified)
235
310
 
236
311
  ## Contributing
237
312
 
data/bin/ask_chatgpt CHANGED
@@ -21,9 +21,13 @@ parser = OptionParser.new do |opts|
21
21
 
22
22
  Examples:
23
23
  ask_chatgpt -q "How to parse JSON file in Ruby?"
24
+ ask_chatgpt -m gpt-4 -q "How to parse JSON file in Ruby?"
24
25
  ask_chatgpt -f app/models/user.rb -q "find a bug in this Rails model"
25
26
  ask_chatgpt -f app/models/user.rb -q "create RSpec spec for this model"
26
27
  ask_chatgpt -f test/dummy/Gemfile -q "sort Ruby gems alphabetically"
28
+ ask_chatgpt -s 1"
29
+
30
+ To specify model you can use shorter version: -m 4, -m 4.0, -m 3.5.
27
31
 
28
32
  Version: #{AskChatGPT::VERSION}
29
33
 
@@ -33,10 +37,18 @@ parser = OptionParser.new do |opts|
33
37
  options[:prompt] = prompt
34
38
  end
35
39
 
40
+ opts.on("-s", "--speak AudioDeviceID", String, "Specify audio device ID") do |audio_device_id|
41
+ options[:audio_device_id] = audio_device_id
42
+ end
43
+
36
44
  opts.on("-f", "--file FILE", String, "Specify file with prompt") do |file|
37
45
  options[:file_path] = file
38
46
  end
39
47
 
48
+ opts.on("-m", "--model MODEL", String, "Specify the ChatGPT model. Uses \"gpt-3.5-turbo\" by default") do |model|
49
+ options[:model] = model
50
+ end
51
+
40
52
  opts.on("-d", "--debug", "Output request/response") do |debug|
41
53
  options[:debug] = true
42
54
  end
@@ -51,16 +63,38 @@ parser.parse!
51
63
 
52
64
  AskChatGPT.debug = !!options[:debug]
53
65
 
66
+ if options[:model].present?
67
+ model = options[:model].to_s
68
+ disctionary = {
69
+ "4" => "gpt-4",
70
+ "4.0" => "gpt-4",
71
+ "40" => "gpt-4",
72
+ "3.5t" => "gpt-3.5-turbo",
73
+ "3.5" => "gpt-3.5-turbo",
74
+ "35" => "gpt-3.5-turbo",
75
+ }
76
+ model = disctionary[model.downcase] || model
77
+ AskChatGPT.model = model
78
+ end
79
+
54
80
  options[:prompt] = ARGV.join(" ") if options[:prompt].blank?
55
81
 
56
- if options[:prompt].blank?
82
+ if options[:prompt].blank? && options[:audio_device_id].blank?
57
83
  puts parser
58
84
  exit
59
85
  end
60
86
 
61
87
  include AskChatGPT::Console
62
88
 
63
- instance = gpt.ask(options[:prompt])
64
- instance = instance.payload(File.read(options[:file_path])) if options[:file_path].present?
89
+ if options[:audio_device_id].present?
90
+ AskChatGPT.voice_enabled = true
91
+ AskChatGPT.voice_max_duration = 20
92
+ AskChatGPT.audio_device_id = options[:audio_device_id]
65
93
 
66
- puts instance.inspect
94
+ instance = gpt.speak
95
+ else
96
+ instance = gpt.ask(options[:prompt])
97
+ instance = instance.payload(File.read(options[:file_path])) if options[:file_path].present?
98
+
99
+ puts instance.inspect
100
+ end
@@ -1,6 +1,6 @@
1
1
  module AskChatgpt
2
2
  module DefaultBehavior
3
- DEFAULT_PROMPTS = [:improve, :refactor, :question, :find_bug, :code_review, :rspec_test, :unit_test, :explain]
3
+ DEFAULT_PROMPTS = [:improve, :refactor, :question, :with_code, :find_bug, :code_review, :rspec_test, :unit_test, :explain]
4
4
 
5
5
  def with_model(*models)
6
6
  self.tap do
@@ -22,6 +22,7 @@ module AskChatgpt
22
22
  alias :how :question
23
23
  alias :find :question
24
24
  alias :review :code_review
25
+ alias :with_class :with_code
25
26
 
26
27
  def add_prompt(prompt)
27
28
  scope << prompt
@@ -2,6 +2,7 @@ require_relative "sugar"
2
2
  require_relative "prompts/base"
3
3
  require_relative "prompts/improve"
4
4
  require_relative "default_behavior"
5
+ require_relative "voice"
5
6
 
6
7
  Dir[File.join(__dir__, "prompts", "*.rb")].each do |file|
7
8
  require file
@@ -23,6 +24,14 @@ module AskChatgpt
23
24
  AskChatgpt::Executor.new(client)
24
25
  end
25
26
 
27
+ def speak
28
+ puts "Voice input is not enabled (docs: https://github.com/railsjazz/ask_chatgpt)" unless AskChatGPT.voice_enabled
29
+ puts "Audio device ID is not configured (docs: https://github.com/railsjazz/ask_chatgpt)" unless AskChatGPT.audio_device_id
30
+
31
+ AskChatgpt::VoiceFlow::Voice.new.run
32
+ end
33
+ alias_method :s, :speak
34
+
26
35
  def initialize(client)
27
36
  @scope = AskChatGPT.included_prompts.dup
28
37
  @client = client
@@ -0,0 +1,25 @@
1
+ module AskChatgpt
2
+ module Prompts
3
+ class WithCode < Base
4
+
5
+ attr_reader :args
6
+
7
+ def initialize(*args)
8
+ @args = args
9
+ end
10
+
11
+ def content
12
+ code_info.reject { |v| v.blank? }.join("\n")
13
+ end
14
+
15
+ private
16
+
17
+ def code_info
18
+ args.compact.map do |arg|
19
+ AskChatGPT::Helpers.extract_source(arg)
20
+ end
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -1,3 +1,3 @@
1
1
  module AskChatgpt
2
- VERSION = "0.3.1"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -0,0 +1,176 @@
1
+ module StringExt
2
+ refine String do
3
+ def black; "\e[30m#{self}\e[0m" end
4
+ def red; "\e[31m#{self}\e[0m" end
5
+ def green; "\e[32m#{self}\e[0m" end
6
+ def brown; "\e[33m#{self}\e[0m" end
7
+ def blue; "\e[34m#{self}\e[0m" end
8
+ def magenta; "\e[35m#{self}\e[0m" end
9
+ def cyan; "\e[36m#{self}\e[0m" end
10
+ def gray; "\e[37m#{self}\e[0m" end
11
+ end
12
+ end
13
+
14
+ using StringExt
15
+
16
+ module AskChatgpt
17
+ module VoiceFlow
18
+ require 'io/console'
19
+ require 'fileutils'
20
+ require 'timeout'
21
+ require 'open3'
22
+
23
+ class AudioRecorder
24
+ OUTPUT_FILE = "output.wav"
25
+
26
+ def initialize(duration)
27
+ @duration = duration
28
+ end
29
+
30
+ # ffmpeg -f avfoundation -list_devices true -i ""
31
+ def audio_device_id
32
+ AskChatGPT.audio_device_id
33
+ end
34
+
35
+ def start
36
+ delete_audio_file
37
+ ffmpeg_command = build_ffmpeg_command
38
+ @stdin, @stdout_and_stderr, @wait_thread = Open3.popen2e(ffmpeg_command)
39
+ end
40
+
41
+ def stop
42
+ @stdin.puts 'q'
43
+ @stdin.close
44
+ @stdout_and_stderr.close
45
+ sleep(0.2)
46
+ rescue Errno::EPIPE, IOError
47
+ end
48
+
49
+ def delete_audio_file
50
+ FileUtils.rm(OUTPUT_FILE) if File.exist?(OUTPUT_FILE)
51
+ end
52
+
53
+ private
54
+
55
+ def build_ffmpeg_command
56
+ case RUBY_PLATFORM
57
+ when /darwin/
58
+ input_device = "-f avfoundation -i \":#{audio_device_id}\""
59
+ when /linux/
60
+ input_device = '-f alsa -i default'
61
+ when /mingw|mswin/
62
+ input_device = '-f dshow -i audio="Microphone (Realtek High Definition Audio)"'
63
+ else
64
+ raise "Unsupported platform: #{RUBY_PLATFORM}"
65
+ end
66
+
67
+ "ffmpeg -loglevel quiet #{input_device} -t #{@duration} #{OUTPUT_FILE}"
68
+ end
69
+ end
70
+
71
+ class Voice
72
+ def initialize
73
+ @messages = []
74
+ @wanna_quit = false
75
+ @duration = (AskChatGPT.voice_max_duration.presence || 10).to_i
76
+ @ffmpeg_wait_duration = 0.5
77
+ @executing = true
78
+ @spinner = nil
79
+ end
80
+
81
+ def run
82
+ while @executing
83
+ # Start the parallel process
84
+ audio_recorder = AudioRecorder.new(@duration)
85
+ audio_recorder.start
86
+
87
+ begin
88
+ Timeout.timeout(@duration + @ffmpeg_wait_duration) do
89
+ @spinner = TTY::Spinner.new("[Recording]".red + " / Press any key to stop recording or \"Esc\" / \"q\" to quit ... ".blue + ":spinner".red, format: :spin)
90
+ @spinner.auto_spin
91
+ sleep(@ffmpeg_wait_duration) # five some time for ffmpeg to start
92
+ # Listen for user input in the main process
93
+ begin
94
+ char = $stdin.getch
95
+ if char.ord == 27 || char.upcase == "Q"
96
+ audio_recorder.stop
97
+ @executing = false
98
+ @spinner.stop
99
+ puts "Bye...".brown
100
+ end
101
+ break
102
+ end while char.nil?
103
+ end
104
+ rescue Timeout::Error
105
+ ensure
106
+ audio_recorder.stop
107
+ @spinner.stop
108
+ break unless @executing
109
+ end
110
+
111
+ if !File.exist?("output.wav")
112
+ puts "No audio file found, please try again.".brown
113
+ sleep(0.5)
114
+ next
115
+ end
116
+
117
+ @spinner = TTY::Spinner.new("Thinking :spinner".cyan, format: :dots)
118
+ @spinner.auto_spin
119
+ response = client.transcribe(parameters: { model: "whisper-1", file: File.open("output.wav", "rb") })
120
+ @spinner.stop
121
+
122
+ if response["error"]
123
+ puts response["error"].inspect.brown
124
+ @executing = false
125
+ break
126
+ end
127
+
128
+ user_input = response["text"].to_s
129
+ puts "USER> ".green + user_input
130
+ @messages << { role: "user", content:user_input }
131
+ print "ASSISTANT> ".magenta
132
+
133
+ stop_stream = false
134
+ reply = []
135
+
136
+ keypresser = Thread.new do
137
+ loop { stop_stream = true if $stdin.getch }
138
+ end
139
+
140
+ begin
141
+ client.chat(
142
+ parameters: {
143
+ model: "gpt-3.5-turbo",
144
+ messages: @messages,
145
+ temperature: 0.7,
146
+ stream: proc do |chunk, _bytesize|
147
+ break if stop_stream
148
+ message = chunk.dig("choices", 0, "delta", "content")
149
+ next if message.to_s.empty?
150
+
151
+ message = message.gsub("\n", "\r\n")
152
+
153
+ print message
154
+ reply += [message]
155
+ end
156
+ })
157
+ rescue LocalJumpError
158
+ puts
159
+ ensure
160
+ Thread.kill(keypresser)
161
+ stop_stream = false
162
+ @messages << { role: "assistant", content: reply.join }
163
+ puts
164
+ end
165
+
166
+ audio_recorder.delete_audio_file
167
+ end
168
+ end
169
+
170
+ def client
171
+ @client ||= OpenAI::Client.new(access_token: AskChatGPT.access_token)
172
+ end
173
+ end
174
+
175
+ end
176
+ end
data/lib/ask_chatgpt.rb CHANGED
@@ -49,6 +49,19 @@ module AskChatgpt
49
49
  mattr_accessor :included_prompts
50
50
  @@included_prompts = [AskChatGPT::Prompts::App.new]
51
51
 
52
+ # enable voice input, requires ffmpeg to be installed and also you need to configure audio_device_id
53
+ mattr_accessor :voice_enabled
54
+ @voice_enabled = false
55
+
56
+ # to get audio device ID (index in the input devices)
57
+ # ffmpeg -f avfoundation -list_devices true -i ""
58
+ mattr_accessor :audio_device_id
59
+ @@audio_device_id = nil
60
+
61
+ # max duration of audio to record
62
+ mattr_accessor :voice_max_duration
63
+ @@voice_max_duration = 10 # 10 seconds
64
+
52
65
  def self.setup
53
66
  yield(self)
54
67
  end
@@ -10,6 +10,11 @@ AskChatGPT.setup do |config|
10
10
  # config.temperature = 0.1
11
11
  # config.included_prompts = []
12
12
 
13
+ # enable voice input, requires ffmpeg to be installed and also you need to configure audio_device_id
14
+ # config.voice_enabled = true
15
+ # config.audio_device_id = 1
16
+ # config.voice_max_duration = 10 # 10 seconds
17
+
13
18
  # Examples of custom prompts:
14
19
  # you can use them `gpt.ask(:extract_email, "some string")`
15
20
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ask_chatgpt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Igor Kasyanchuk
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-05-01 00:00:00.000000000 Z
12
+ date: 2023-05-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -95,6 +95,20 @@ dependencies:
95
95
  - - ">="
96
96
  - !ruby/object:Gem::Version
97
97
  version: 1.4.3
98
+ - !ruby/object:Gem::Dependency
99
+ name: io-console
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :runtime
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
98
112
  - !ruby/object:Gem::Dependency
99
113
  name: wrapped_print
100
114
  requirement: !ruby/object:Gem::Requirement
@@ -153,9 +167,11 @@ files:
153
167
  - lib/ask_chatgpt/prompts/refactor.rb
154
168
  - lib/ask_chatgpt/prompts/rspec_test.rb
155
169
  - lib/ask_chatgpt/prompts/unit_test.rb
170
+ - lib/ask_chatgpt/prompts/with_code.rb
156
171
  - lib/ask_chatgpt/railtie.rb
157
172
  - lib/ask_chatgpt/sugar.rb
158
173
  - lib/ask_chatgpt/version.rb
174
+ - lib/ask_chatgpt/voice.rb
159
175
  - lib/generators/ask_chatgpt/USAGE
160
176
  - lib/generators/ask_chatgpt/ask_chatgpt_generator.rb
161
177
  - lib/generators/ask_chatgpt/templates/template.rb