gpterminal 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b11c4f28f70c97e2f7bfa082039d8504e548035cf98b86126d7bafd38098727d
4
+ data.tar.gz: 4a992292676cb07170d8588a4a5724e94726db3b0e4437099a784f536195c7a4
5
+ SHA512:
6
+ metadata.gz: f7126e3f4d2082a72402febcf1728f9f8361c3312f3eb465684f1a4f73e044e939e55d4549606a0e27bdfe70db0851c9451be902c014858cdc04684bb314b2c5
7
+ data.tar.gz: d3818548a22406e5a1149f9fb275926dcdbbe217f61ba052210bf8cf6637a9dcc0856b6811d96dd1c7963f4e65eeab8f170c6e3238851f77f871bc7d27504b4f
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2024 Daniel Hough
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # gpterminal
2
+
3
+ **WARNING:** `gpterminal` has very few guardrails. If used indiscriminately, it can wipe your entire system or leak information.
4
+
5
+ `gpterminal` is a powerful, flexible and dangerous command-line tool designed to help you generate commands for your terminal using OpenAI's Chat Completions. It will not execute commands without your consent, but please do check which commands it is presenting before you let it execute them. Like so:
6
+
7
+ ```bash
8
+ $ ./bin/gpterminal -p "Using git diff to gather info, commit all the latest changes with a descriptive commit message, then push the changes"
9
+ $ # It gathers info, asks for consent, and does the thing
10
+ $ [main 94a9292] Update README with gpterminal usage example
11
+ $ 1 file changed, 4 insertions(+)
12
+ ```
13
+
14
+ ## Getting Started
15
+
16
+ To use gpterminal, ensure you have Ruby installed on your system. Then, follow these steps:
17
+
18
+ - Clone the repository or download the source code.
19
+ - Navigate to the gpterminal directory and run `bundle install` to install dependencies.
20
+ - Start the application by running `./bin/gpterminal`
21
+
22
+ ## Configuration
23
+
24
+ On first run, you'll be prompted to enter your OpenAI API key. This is required for the application to interact with OpenAI's API. You will also be asked to specify whether you'd like your `PATH` variable to be sent in the prompt, which can help with command generation.
25
+
26
+ ## Usage
27
+
28
+ gpterminal can be used with the following options:
29
+
30
+ - `-p`, `--prompt PROMPT`: Set a custom prompt for generating text.
31
+ - `-s`, `--save NAME,PROMPT`: Create a custom preset prompt that can be reused.
32
+ Without any options, gpterminal will prompt you to enter a text prompt manually.
33
+ - `-k`, `--key KEY`: Set the OpenAI API key
34
+ - `-P`, `--send-path`: Send the PATH environment variable to OpenAI.
35
+
36
+ ## Presets
37
+
38
+ You can save and reuse preset prompts for common or repeated tasks. To create a preset, use the `-s` option followed by a name and the prompt, separated by a comma.
39
+ To use a preset, simply pass its name as an argument when starting gpterminal.
40
+
41
+ ## Contributing
42
+
43
+ Contributions are welcome! Feel free to open an issue or pull request.
44
+
45
+ ## License
46
+
47
+ gpterminal is open-source software licensed under the MIT license.
48
+
49
+ ## Author
50
+
51
+ [Dan Hough](https://danhough.com)
data/bin/gpterminal ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/gpterminal'
4
+
5
+ GPTerminal.new.run
data/lib/client.rb ADDED
@@ -0,0 +1,165 @@
1
+ require "openai"
2
+
3
+ class Client
4
+ attr_reader :openapi_client
5
+ attr_reader :config
6
+
7
+ def initialize(config)
8
+ @config = config
9
+ @openapi_client = OpenAI::Client.new(access_token: config["openapi_key"])
10
+ end
11
+
12
+ def first_prompt(prompt)
13
+ system_prompt = <<~PROMPT
14
+ You are a command-line application being executed inside of a directory in a macOS environment, on the user's terminal command line.
15
+
16
+ You are executed by running `gpterminal` in the terminal, and you are provided with a prompt to respond to with the -p flag.
17
+
18
+ Users can add a preset prompt by running `gpterminal -s <name>,<prompt>`.
19
+
20
+ The eventual output to the user would be a list of commands that they can run in their terminal to accomplish a task.
21
+
22
+ You have the ability to run any command that this system can run, and you can read the output of those commands.
23
+
24
+ The user is trying to accomplish a task using the terminal, but they are not sure how to do it.
25
+ PROMPT
26
+
27
+ if @config["send_path"]
28
+ system_prompt += <<~PROMPT
29
+ The user's PATH environment variable is:
30
+ #{ENV["PATH"]}
31
+ PROMPT
32
+ end
33
+
34
+ full_prompt = <<~PROMPT
35
+ Your FIRST response should be a list of commands that will be automatically executed to gather more information about the user's system.
36
+ - The commands MUST NOT make any changes to the user's system.
37
+ - The commands MUST NOT make any changes to any files on the user's system.
38
+ - The commands MUST NOT write to any files using the > or >> operators.
39
+ - The commands MUST NOT use the touch command.
40
+ - The commands MUST NOT use echo or any other command to write into files using the > or >> operators.
41
+ - The commands MUST NOT send any data to any external servers.
42
+ - The commands MUST NOT contain any placeholders in angle brackets like <this>.
43
+ - The commands MUST NOT contain any plain language instructions, or backticks indicating where the commands begin or end.
44
+ - The commands MAY gather information about the user's system, such as the version of a software package, or the contents of a file.
45
+ - The commands CAN pipe their output into other commands.
46
+ - The commands SHOULD tend to gather more verbose information INSTEAD OF more concise information.
47
+ This will help you to provide a more accurate response to the user's goal.
48
+ Therefore your FIRST response MUST contain ONLY a list of commands and nothing else.
49
+
50
+ VALID example response. These commands are examples of commands which CAN be included in your FIRST response:
51
+
52
+ for file in *; do cat "$file"; done
53
+ which ls
54
+ which git
55
+ which brew
56
+ git diff
57
+ git status
58
+
59
+ INVALID example response. These commands are examples of commands which MUST NOT be included in your FIRST response:
60
+
61
+ touch file.txt
62
+ git add .
63
+ git push
64
+
65
+ If you cannot create a VALID response, simply return the string "$$cannot_compute$$" and the user will be asked to provide a new prompt.
66
+ If you do not need to gather more information, simply return the string "$$no_gathering_needed$$" and the next step will be executed.
67
+ You probably will need to gather information.
68
+ If you need to gather information directly from the user, you will be able to do so in the next step.
69
+
70
+ The user's goal prompt is:
71
+ "#{prompt}"
72
+ Commands to execute to gather more information about the user's system before providing the response which will accomplish the user's goal:
73
+ PROMPT
74
+
75
+ @messages = [
76
+ { role: "system", content: system_prompt },
77
+ { role: "user", content: full_prompt }
78
+ ]
79
+
80
+ response = openapi_client.chat(
81
+ parameters: {
82
+ model: "gpt-3.5-turbo",
83
+ messages: @messages,
84
+ temperature: 0.6,
85
+ }
86
+ )
87
+ content = response.dig("choices", 0, "message", "content")
88
+
89
+ @messages << { role: "assistant", content: content }
90
+
91
+ content
92
+ end
93
+
94
+ def offer_information_prompt(prompt)
95
+ full_prompt = <<~PROMPT
96
+ This is the output of the command you provided to the user in the previous step.
97
+
98
+ #{prompt}
99
+
100
+ Before you provide the user with the next command, you have the opportunity to ask the user to provide more information so you can better tailor your response to their needs.
101
+
102
+ If you would like to ask the user for more information, please provide a prompt that asks the user for the information you need.
103
+ - Your prompt MUST ONLY contain one question. You will be able to ask another question in the next step.
104
+ If you have all the information you need, simply return the string "$$no_more_information_needed$$" and the next step will be executed.
105
+ PROMPT
106
+
107
+ @messages << { role: "user", content: full_prompt }
108
+
109
+ response = openapi_client.chat(
110
+ parameters: {
111
+ model: "gpt-4",
112
+ messages: @messages,
113
+ temperature: 0.6,
114
+ }
115
+ )
116
+
117
+ content = response.dig("choices", 0, "message", "content")
118
+
119
+ @messages << { role: "assistant", content: content }
120
+
121
+ content
122
+ end
123
+
124
+ def final_prompt(prompt)
125
+ full_prompt = <<~PROMPT
126
+ This is the output of the command you provided to the user in the previous step.
127
+
128
+ #{prompt}
129
+
130
+ Your NEXT response should be a list of commands that will be automatically executed to fulfill the user's goal.
131
+ - The commands may make changes to the user's system.
132
+ - The commands may install new software using package managers like Homebrew
133
+ - The commands MUST all start with a valid command that you would run in the terminal
134
+ - The commands MUST NOT contain any placeholders in angle brackets like <this>.
135
+ - The response MUST NOT contain any plain language instructions, or backticks indicating where the commands begin or end.
136
+ - THe response MUST NOT start or end with backticks.
137
+ - The response MUST NOT end with a newline character.
138
+ Therefore your NEXT response MUST contain ONLY a list of commands and nothing else.
139
+
140
+ VALID example response. These commands are examples of commands which CAN be included in your FINAL response:
141
+
142
+ ls
143
+ mkdir new_directory
144
+ brew install git
145
+ git commit -m "This is a great commit message"
146
+
147
+ If you cannot keep to this restriction, simply return the string "$$cannot_compute$$" and the user will be asked to provide a new prompt.
148
+ PROMPT
149
+
150
+ @messages << { role: "user", content: full_prompt }
151
+
152
+ response = openapi_client.chat(
153
+ parameters: {
154
+ model: "gpt-4",
155
+ messages: @messages,
156
+ temperature: 0.6,
157
+ }
158
+ )
159
+ content = response.dig("choices", 0, "message", "content")
160
+
161
+ @messages << { role: "assistant", content: content }
162
+
163
+ content
164
+ end
165
+ end
data/lib/config.rb ADDED
@@ -0,0 +1,41 @@
1
+ require 'yaml'
2
+
3
+ module AppConfig
4
+ CONFIG_FILE = File.join(Dir.home, '.gpterminal', 'config.yml').freeze
5
+
6
+ # Check if the directory exists, if not, create it
7
+ unless File.directory?(File.dirname(CONFIG_FILE))
8
+ Dir.mkdir(File.dirname(CONFIG_FILE))
9
+ end
10
+
11
+ def self.load_config
12
+ YAML.load_file(CONFIG_FILE)
13
+ rescue Errno::ENOENT
14
+ default_config
15
+ end
16
+
17
+ def self.save_config(config)
18
+ puts "Saving config to #{CONFIG_FILE}"
19
+ puts "It will look like:"
20
+ puts config.to_yaml
21
+ File.write(CONFIG_FILE, config.to_yaml)
22
+ end
23
+
24
+ def self.add_openapi_key(config, openapi_key)
25
+ config['openapi_key'] = openapi_key
26
+ save_config(config)
27
+ end
28
+
29
+ def self.add_preset(config, preset_name, preset_prompt)
30
+ # This is a YAML file so we need to make sure the presets key exists
31
+ config['presets'] ||= {}
32
+ config['presets'][preset_name] = preset_prompt
33
+ save_config(config)
34
+ end
35
+
36
+ def self.default_config
37
+ {
38
+ 'openapi_key' => ''
39
+ }
40
+ end
41
+ end
data/lib/gpterminal.rb ADDED
@@ -0,0 +1,176 @@
1
+ require 'optparse'
2
+ require 'colorize'
3
+
4
+ require_relative 'config'
5
+ require_relative 'client'
6
+
7
+ class GPTerminal
8
+ def initialize
9
+ @config = load_config
10
+ @options = parse_options
11
+ @client = Client.new(@config)
12
+ end
13
+
14
+ def run
15
+ prompt = determine_prompt
16
+ message = @client.first_prompt(prompt)
17
+
18
+ if message.downcase == '$$cannot_compute$$'
19
+ puts 'Sorry, a command could not be generated for that prompt. Try another.'.colorize(:red)
20
+ exit
21
+ end
22
+
23
+ if message.downcase == '$$no_gathering_needed$$'
24
+ puts 'No information gathering needed'.colorize(:magenta)
25
+ output = "No information gathering was needed."
26
+ else
27
+ puts 'Information gathering command:'.colorize(:magenta)
28
+ puts message.gsub(/^/, "#{" $".colorize(:blue)} ")
29
+ puts 'Do you want to execute this command? (Y/n)'.colorize(:yellow)
30
+ continue = STDIN.gets.chomp
31
+
32
+ unless continue.downcase == 'y'
33
+ exit
34
+ end
35
+
36
+ puts 'Running command...'
37
+ output = `#{message}`
38
+
39
+ puts 'Output:'
40
+ puts output
41
+ end
42
+
43
+ output = offer_more_information(output)
44
+
45
+ while output.downcase != '$$no_more_information_needed$$'
46
+ puts "You have been asked to provide more information with this command:".colorize(:magenta)
47
+ puts output.gsub(/^/, "#{" >".colorize(:blue)} ")
48
+ puts "What is your response? (Type 'skip' to skip this step and force the final command to be generated)".colorize(:yellow)
49
+
50
+ response = STDIN.gets.chomp
51
+
52
+ if response.downcase == 'skip'
53
+ output = '$$no_more_information_needed$$'
54
+ else
55
+ output = offer_more_information(response)
56
+ end
57
+ end
58
+
59
+ puts 'Requesting the next command...'.colorize(:magenta)
60
+
61
+ message = @client.final_prompt(output)
62
+
63
+ puts 'Generated command to accomplish your goal:'.colorize(:magenta)
64
+ puts message.gsub(/^/, "#{" $".colorize(:green)} ")
65
+
66
+ puts 'Do you want to execute this command? (Y/n)'.colorize(:yellow)
67
+
68
+ continue = STDIN.gets.chomp
69
+
70
+ unless continue.downcase == 'y'
71
+ exit
72
+ end
73
+
74
+ output = `#{message}`
75
+
76
+ puts 'Output:'
77
+ puts output
78
+ end
79
+
80
+ private
81
+
82
+ def load_config
83
+ unless File.exist?(AppConfig::CONFIG_FILE)
84
+ puts 'Welcome to GPTerminal! It looks like this is your first time using this application.'.colorize(:magenta)
85
+
86
+ new_config = {}
87
+ print "Before we get started, we need to configure the application. All the info you provide will be saved in #{AppConfig::CONFIG_FILE}.".colorize(:magenta)
88
+
89
+ print "Enter your OpenAI API key's \"SECRET KEY\" value: ".colorize(:yellow)
90
+ new_config['openapi_key'] = STDIN.gets.chomp
91
+
92
+ print "Your PATH environment variable is: #{ENV['PATH']}".colorize(:magenta)
93
+ print 'Are you happy for your PATH to be sent to OpenAI to help with command generation? (Y/n) '.colorize(:yellow)
94
+
95
+ if STDIN.gets.chomp.downcase == 'y'
96
+ new_config['send_path'] = true
97
+ else
98
+ new_config['send_path'] = false
99
+ end
100
+
101
+ AppConfig.save_config(new_config)
102
+
103
+ new_config
104
+ else
105
+ AppConfig.load_config
106
+ end
107
+ end
108
+
109
+ def parse_options
110
+ options = {}
111
+ OptionParser.new do |opts|
112
+ opts.banner = "Usage: gpterminal [preset] [options]"
113
+
114
+ opts.on("-p", "--prompt PROMPT", "Set a custom prompt") do |v|
115
+ options[:prompt] = v
116
+ end
117
+
118
+ opts.on("-s", "--save NAME,PROMPT", "Create a custom preset prompt") do |list|
119
+ options[:preset_prompt] = list.split(',', 2)
120
+ end
121
+
122
+ opts.on("-h", "--help", "Prints this help") do
123
+ puts opts
124
+ exit
125
+ end
126
+
127
+ opts.on("-k", "--key KEY", "Set the OpenAI API key") do |v|
128
+ AppConfig.add_openapi_key(@config, v)
129
+ puts "OpenAI API key saved"
130
+ exit
131
+ end
132
+
133
+ opts.on("-P", "--send-path", "Send the PATH environment variable to OpenAI") do
134
+ @config['send_path'] = true
135
+ AppConfig.save_config(@config)
136
+ puts "Your PATH environment variable will be sent to OpenAI to help with command generation"
137
+ exit
138
+ end
139
+ end.parse!
140
+
141
+ options
142
+ end
143
+
144
+ def determine_prompt
145
+ if @options[:preset_prompt]
146
+ name = @options[:preset_prompt][0]
147
+ prompt = @options[:preset_prompt][1]
148
+ AppConfig.add_preset(@config, name, prompt)
149
+ puts "Preset prompt '#{name}' saved with prompt '#{prompt}'".colorize(:green)
150
+ exit
151
+ end
152
+
153
+ if ARGV.length == 1
154
+ # collect a prompt from the preset prompts
155
+ prompt = @config['presets'][ARGV[0]]
156
+
157
+ unless prompt
158
+ puts "Preset prompt not found: #{ARGV[0]}".colorize(:red)
159
+ exit
160
+ end
161
+
162
+ puts "Using preset prompt '#{prompt}'".colorize(:magenta)
163
+ elsif @options[:prompt]
164
+ prompt = @options[:prompt]
165
+ else
166
+ puts 'Enter a prompt to generate text from:'.colorize(:yellow)
167
+ prompt = STDIN.gets.chomp
168
+ end
169
+
170
+ prompt
171
+ end
172
+
173
+ def offer_more_information(output)
174
+ output = @client.offer_information_prompt(output)
175
+ end
176
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gpterminal
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Dan Hough
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-02-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ruby-openai
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "<"
18
+ - !ruby/object:Gem::Version
19
+ version: '7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "<"
25
+ - !ruby/object:Gem::Version
26
+ version: '7'
27
+ description: gpterminal is a powerful, flexible and dangerous command-line tool designed
28
+ to help you generate commands for your terminal using OpenAI's Chat Completions
29
+ email:
30
+ - daniel.hough@gmail.com
31
+ executables:
32
+ - gpterminal
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - LICENSE
37
+ - README.md
38
+ - bin/gpterminal
39
+ - lib/client.rb
40
+ - lib/config.rb
41
+ - lib/gpterminal.rb
42
+ homepage: https://github.com/basicallydan/gpterminal
43
+ licenses:
44
+ - MIT
45
+ metadata:
46
+ homepage_uri: https://github.com/basicallydan/gpterminal
47
+ source_code_uri: https://github.com/basicallydan/gpterminal
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubygems_version: 3.5.3
64
+ signing_key:
65
+ specification_version: 4
66
+ summary: A CLI for generating CLI commands with ChatGPT's help.
67
+ test_files: []