n2b 0.1.5 → 0.2.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: d58cae30259d2c0399fe26b0170d1c59eb9e9ed89ba3831668e6931571d4407e
4
- data.tar.gz: f8f05b3fa22da7257ba7bbca704537d21f4e7ec80f7304225935e32cd1596d50
3
+ metadata.gz: a2ca1c8cbf2140daf33550d891f390a389f2150f0c960228da34eb421cdd577d
4
+ data.tar.gz: eb67739e750eec2f0257469397b97f183ac96a2640ba8d8df67f2f7ba8f73b42
5
5
  SHA512:
6
- metadata.gz: c2a96d2da2390481dea2cf5044d64164e58d4a168e5f5367df4162ebfa8867c01c078af42c186bfad8518546b602cce65029b07c7251534530847beeed0febef
7
- data.tar.gz: 4eb2e07f2021e19e3beea4dcff79c3e226eb03198ba574a54f62e13df248a625bcd51764e23b0aa956e11215d726705be2bc877defb9d302eb5cde6452f4b82d
6
+ metadata.gz: 1130910ebe431f6136d5ec1a3f268d898f897ead246176b8558b7742a77e21df788e2e127f8347fcd3d579f0694b7f3240f3a131ac58f65e634b1e8f1cee137c
7
+ data.tar.gz: 7bed17c961f2fe3d15b418aee78daf8e2a45e64b4e7298db9b183a73c32f03ab097316e819b6926480e5690cac5463b36aef031eeb39d664e4bc6a541617257e
data/README.md CHANGED
@@ -1,9 +1,12 @@
1
1
  # N2B: Natural Language to Bash Commands Converter
2
2
 
3
3
  N2B (Natural to Bash) is a Ruby gem that converts natural language instructions into executable shell commands using the Claude AI or OpenAI API. It's designed to help users quickly generate shell commands without needing to remember exact syntax.
4
+ Also it has the n2r method which can help you with any Ruby or Rails related issues
4
5
 
5
6
  ## Features
6
7
 
8
+ ### N2B
9
+
7
10
  - Convert natural language to shell commands
8
11
  - Support for multiple Claude AI models (Haiku, Sonnet, Sonnet 3.5)
9
12
  - Support for OpenAI models
@@ -12,7 +15,13 @@ N2B (Natural to Bash) is a Ruby gem that converts natural language instructions
12
15
  - Shell history integration
13
16
  - Command history tracking for improved context
14
17
 
15
- ## Quick Example
18
+ ### N2R
19
+ - Convert natural language to ruby code or explain it
20
+ - analyze an exception and find the cause
21
+ - analyze existing ruby files
22
+
23
+
24
+ ## Quick Example N2B
16
25
 
17
26
  ```
18
27
  n2b init a new github repo called abc, add local files, transmit
@@ -31,6 +40,27 @@ git push -u origin main
31
40
  Explanation:
32
41
  These commands initialize a new Git repository, add a remote GitHub repository named 'abc', stage all local files, create an initial commit, and push the changes to GitHub. Replace 'yourusername' with your actual GitHub username. Note that you'll need to create the repository on GitHub first before running these commands. Also, ensure you have Git installed and configured with your GitHub credentials.
33
42
  ```
43
+
44
+ ## Quick example n2r
45
+
46
+ ```
47
+ irb
48
+ require 'n2b'
49
+ n2r 4544 # results in exception
50
+ n2r "what is the bug",exception:_
51
+ ```
52
+
53
+ result
54
+ ```
55
+ input_string.to_s.scan(/[\/\w.-]+\.rb(?=\s|:|$)/)
56
+ ```
57
+ Explanation
58
+ The error `undefined method 'scan' for 7767:Integer` occurs because the method `scan` is being called on an integer instead of a string. To fix the issue, we need to ensure that `input_string` is a string before calling the `scan` method on it. Here's the corrected part of the code that converts `input_string` to a string before using `scan`:
59
+
60
+ ```ruby
61
+ input_string.to_s.scan(/[\/\w.-]+\.rb(?=\s|:|$)/)
62
+ ```
63
+
34
64
  ------------------------
35
65
  ## Installation
36
66
 
@@ -43,8 +73,8 @@ Before using n2b, you need to configure it with your Claude API key and preferen
43
73
  n2b -c
44
74
 
45
75
  This will prompt you to enter:
46
- - Your Claude API key
47
- - Preferred Claude model (haiku, sonnet, or sonnet35)
76
+ - Your Claude API or OpenAI key
77
+ - Preferred model (e.g. haiku, sonnet, or sonnet35)
48
78
  - Privacy settings (whether to send shell history, past requests, current directory)
49
79
  - Whether to append generated commands to your shell history
50
80
 
@@ -75,6 +105,11 @@ Examples:
75
105
 
76
106
  ```n2b -c ```
77
107
 
108
+
109
+ n2r in ruby or rails console
110
+ n2r "your question", files:['file1.rb', 'file2.rb'], exception: AnError
111
+ only question is mandatory
112
+
78
113
  ## How It Works
79
114
 
80
115
  1. N2B takes your natural language input and sends it to the Claude AI API.
@@ -97,6 +132,15 @@ Always sent to llm
97
132
  - shell type
98
133
  - operating system
99
134
 
135
+ ## Rails
136
+
137
+ in rails console use
138
+
139
+ ```
140
+ include N2B::IRB
141
+ ```
142
+
143
+ to get n2r
100
144
 
101
145
  ## Limitations
102
146
 
data/lib/n2b/base.rb ADDED
@@ -0,0 +1,61 @@
1
+ module N2B
2
+ class Base
3
+
4
+ CONFIG_FILE = File.expand_path('~/.n2b/config.yml')
5
+ HISTORY_FILE = File.expand_path('~/.n2b/history')
6
+
7
+ def load_config
8
+ if File.exist?(CONFIG_FILE)
9
+ YAML.load_file(CONFIG_FILE)
10
+ else
11
+ { }
12
+ end
13
+ end
14
+
15
+ def get_config( reconfigure: false)
16
+ config = load_config
17
+ api_key = ENV['CLAUDE_API_KEY'] || config['access_key']
18
+ model = config['model'] || 'sonnet35'
19
+
20
+ if api_key.nil? || api_key == '' || reconfigure
21
+ print "choose a language model to use (1:claude, 2:openai) #{ config['llm'] }: "
22
+ llm = $stdin.gets.chomp
23
+ llm = config['llm'] if llm.empty?
24
+ unless ['claude', 'openai','1','2'].include?(llm)
25
+ puts "Invalid language model. Choose from: claude, openai"
26
+ exit 1
27
+ end
28
+ llm = 'claude' if llm == '1'
29
+ llm = 'openai' if llm == '2'
30
+ llm_class = llm == 'openai' ? N2M::Llm::OpenAi : N2M::Llm::Claude
31
+
32
+ print "Enter your #{llm} API key: #{ api_key.nil? || api_key.empty? ? '' : '(leave blank to keep the current key '+api_key[0..10]+'...)' }"
33
+ api_key = $stdin.gets.chomp
34
+ api_key = config['access_key'] if api_key.empty?
35
+ print "Choose a model (#{ llm_class::MODELS.keys }, #{ llm_class::MODELS.keys.first } default): "
36
+ model = $stdin.gets.chomp
37
+ model = llm_class::MODELS.keys.first if model.empty?
38
+ config['llm'] = llm
39
+ config['access_key'] = api_key
40
+ config['model'] = model
41
+ unless llm_class::MODELS.keys.include?(model)
42
+ puts "Invalid model. Choose from: #{llm_class::MODELS.keys.join(', ')}"
43
+ exit 1
44
+ end
45
+ puts "configure privacy settings directly in the config file #{CONFIG_FILE}"
46
+ config['privacy'] ||= {}
47
+ config['privacy']['send_shell_history'] = false
48
+ config['privacy']['send_llm_history'] = true
49
+ config['privacy']['send_current_directory'] =true
50
+ config['append_to_shell_history'] = false
51
+ puts "Current configuration: #{config['privacy']}"
52
+ FileUtils.mkdir_p(File.dirname(CONFIG_FILE)) unless File.exist?(File.dirname(CONFIG_FILE))
53
+ File.open(CONFIG_FILE, 'w+') do |f|
54
+ f.write(config.to_yaml )
55
+ end
56
+ end
57
+
58
+ config
59
+ end
60
+ end
61
+ end
data/lib/n2b/cli.rb ADDED
@@ -0,0 +1,190 @@
1
+ module N2B
2
+ class CLI < Base
3
+ def self.run(args)
4
+ new(args).execute
5
+ end
6
+
7
+ def initialize(args)
8
+ @args = args
9
+ @options = parse_options
10
+ end
11
+
12
+ def execute
13
+ config = get_config(reconfigure: @options[:config])
14
+ input_text = @args.join(' ')
15
+ if input_text.empty?
16
+ puts "Enter your natural language command:"
17
+ input_text = $stdin.gets.chomp
18
+ end
19
+
20
+ bash_commands = call_llm(input_text, config)
21
+
22
+ puts "\nTranslated #{get_user_shell} Commands:"
23
+ puts "------------------------"
24
+ puts bash_commands['commands']
25
+ puts "------------------------"
26
+ if bash_commands['explanation']
27
+ puts "Explanation:"
28
+ puts bash_commands['explanation']
29
+ puts "------------------------"
30
+ end
31
+
32
+ if @options[:execute]
33
+ puts "Press Enter to execute these commands, or Ctrl+C to cancel."
34
+ $stdin.gets
35
+ system(bash_commands['commands'].join("\n"))
36
+ else
37
+ add_to_shell_history(bash_commands['commands'].join("\n")) if config['append_to_shell_history']
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+
44
+
45
+ def append_to_llm_history_file(commands)
46
+ File.open(HISTORY_FILE, 'a') do |file|
47
+ file.puts(commands)
48
+ end
49
+ end
50
+
51
+ def read_llm_history_file
52
+ history = File.read(HISTORY_FILE) if File.exist?(HISTORY_FILE)
53
+ history ||= ''
54
+ # limit to 20 most recent commands
55
+ history.split("\n").last(20).join("\n")
56
+ end
57
+
58
+ def call_llm(prompt, config)
59
+
60
+ llm = config['llm'] == 'openai' ? N2M::Llm::OpenAi.new(config) : N2M::Llm::Claude.new(config)
61
+
62
+ content = <<-EOF
63
+ Translate the following natural language command to bash commands: #{prompt}\n\nProvide only the #{get_user_shell} commands for #{ get_user_os }. the commands should be separated by newlines.
64
+ #{' the user is in directory'+Dir.pwd if config['privacy']['send_current_directory']}.
65
+ #{' the user sent past requests to you and got these answers '+read_llm_history_file if config['privacy']['send_llm_history'] }
66
+ #{ "The user has this history for his shell. "+read_shell_history if config['privacy']['send_shell_history'] }
67
+ he is using #{File.basename(get_user_shell)} shell."
68
+ answer only with a valid json object with the key 'commands' and the value as a list of bash commands plus any additional information you want to provide in explanation.
69
+ { "commands": [ "echo 'Hello, World!'" ], "explanation": "This command prints 'Hello, World!' to the terminal."}
70
+ EOF
71
+
72
+
73
+ answer = llm.make_request(content)
74
+
75
+ append_to_llm_history_file("#{prompt}\n#{answer}")
76
+ answer
77
+ end
78
+
79
+ def get_user_shell
80
+ ENV['SHELL'] || `getent passwd #{ENV['USER']}`.split(':')[6]
81
+ end
82
+
83
+ def get_user_os
84
+ case RbConfig::CONFIG['host_os']
85
+ when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
86
+ :windows
87
+ when /darwin|mac os/
88
+ :macos
89
+ when /linux/
90
+ :linux
91
+ when /solaris|bsd/
92
+ :unix
93
+ else
94
+ :unknown
95
+ end
96
+ end
97
+
98
+ def find_history_file(shell)
99
+ case shell
100
+ when 'zsh'
101
+ [
102
+ ENV['HISTFILE'],
103
+ File.expand_path('~/.zsh_history'),
104
+ File.expand_path('~/.zhistory')
105
+ ].find { |f| f && File.exist?(f) }
106
+ when 'bash'
107
+ [
108
+ ENV['HISTFILE'],
109
+ File.expand_path('~/.bash_history')
110
+ ].find { |f| f && File.exist?(f) }
111
+ else
112
+ nil
113
+ end
114
+ end
115
+
116
+ def read_shell_history()
117
+ shell = File.basename(get_user_shell)
118
+ history_file = find_history_file(shell)
119
+ return '' unless history_file
120
+
121
+ File.read(history_file)
122
+ end
123
+
124
+ def add_to_shell_history(commands)
125
+ shell = File.basename(get_user_shell)
126
+ history_file = find_history_file(shell)
127
+
128
+ unless history_file
129
+ puts "Could not find history file for #{shell}. Cannot add commands to history."
130
+ return
131
+ end
132
+
133
+ case shell
134
+ when 'zsh'
135
+ add_to_zsh_history(commands, history_file)
136
+ when 'bash'
137
+ add_to_bash_history(commands, history_file)
138
+ else
139
+ puts "Unsupported shell: #{shell}. Cannot add commands to history."
140
+ return
141
+ end
142
+
143
+ puts "Commands have been added to your #{shell} history file: #{history_file}"
144
+ puts "You may need to start a new shell session or reload your history to see the changes. #{ shell == 'zsh' ? 'For example, run `fc -R` in your zsh session.' : 'history -r for bash' }"
145
+ puts "Then you can access them using the up arrow key or Ctrl+R for reverse search."
146
+ end
147
+
148
+ def add_to_zsh_history(commands, history_file)
149
+ File.open(history_file, 'a') do |file|
150
+ commands.each_line do |cmd|
151
+ timestamp = Time.now.to_i
152
+ file.puts(": #{timestamp}:0;#{cmd.strip}")
153
+ end
154
+ end
155
+ end
156
+
157
+ def add_to_bash_history(commands, history_file)
158
+ File.open(history_file, 'a') do |file|
159
+ commands.each_line do |cmd|
160
+ file.puts(cmd.strip)
161
+ end
162
+ end
163
+ system("history -r") # Attempt to reload history in current session
164
+ end
165
+
166
+
167
+ def parse_options
168
+ options = { execute: false, config: nil }
169
+
170
+ OptionParser.new do |opts|
171
+ opts.banner = "Usage: n2b [options] [natural language command]"
172
+
173
+ opts.on('-x', '--execute', 'Execute the commands after confirmation') do
174
+ options[:execute] = true
175
+ end
176
+
177
+ opts.on('-h', '--help', 'Print this help') do
178
+ puts opts
179
+ exit
180
+ end
181
+
182
+ opts.on('-c', '--config', 'Configure the API key and model') do
183
+ options[:config] = true
184
+ end
185
+ end.parse!(@args)
186
+
187
+ options
188
+ end
189
+ end
190
+ end
data/lib/n2b/irb.rb ADDED
@@ -0,0 +1,78 @@
1
+ module N2B
2
+ module IRB
3
+ MAX_SOURCE_FILES = 4
4
+ def n2r(input_string='', files: [], exception: nil)
5
+ config = N2B::Base.new.get_config
6
+ llm = config['llm'] == 'openai' ? N2M::Llm::OpenAi.new(config) : N2M::Llm::Claude.new(config)
7
+ # detect if inside rails console
8
+ console = case
9
+ when defined?(Rails)
10
+ "You are in a Rails console"
11
+ when defined?(IRB)
12
+ "You are in an IRB console"
13
+ else
14
+ "You are in a standard Ruby console"
15
+ end
16
+ get_defined_classes = ObjectSpace.each_object(Class).to_a
17
+ get_gemfile = File.read('Gemfile') if File.exist?('Gemfile')
18
+ # scan the input for any files that the user has provided
19
+ # look for strings that end with .rb and get the path to the file
20
+ source_files = []
21
+ input_string.scan(/[\w\/.-]+\.rb(?=\s|:|$)/).each do |file|
22
+ full_path = File.expand_path(file) # Resolve the full path
23
+ source_files << full_path if File.exist?(full_path)
24
+ end
25
+ if exception
26
+ source_files += exception.backtrace.map do |line|
27
+ line.split(':').first
28
+ end
29
+ input_string << ' ' << exception.message
30
+ end
31
+ source_files = source_files.reverse.sort_by do |file|
32
+ # Check if the file path starts with the current directory path
33
+ if file.start_with?(Dir.pwd)
34
+ 0 # Prioritize files in or below the current directory
35
+ else
36
+ 1 # Keep other files in their original order
37
+ end
38
+ end
39
+
40
+
41
+ file_content = (files+source_files[0..MAX_SOURCE_FILES-1]).inject({}) do |h,file|
42
+ h[file] = File.read(file) if File.exist?(file)
43
+ h
44
+ end
45
+ content = <<~HEREDOC
46
+ you are a professional ruby programmer
47
+ #{ console}
48
+ The following classes are defined in this session:
49
+ #{ get_defined_classes}
50
+ #{ get_gemfile }
51
+ #{ @n2r_answers ? "user have made #{@n2r_answers} before" : "" }
52
+ your task is to give the user guidance on how perform a task he is asking for
53
+ if he pasts an error or backtrace, you can provide a solution to the problem.
54
+ if you need files you can ask the user to provide them request.
55
+ he can send them with n2r "his question" files: ['file1.rb', 'file2.rb']
56
+ if he sends files and you mention them in the response, provide the file name of the snippets you are referring to.
57
+ answer in a valid json object with the key 'code' with only the ruby code to be executed and a key 'explanation' with a markdown string with the explanation and the code.
58
+ { "code": "puts 'Hello, World!'", "explanation": "### Explanation \n This command ´´´puts 'Hello, world!'´´´ prints 'Hello, World!' to the terminal.", files: ['file1.rb', 'file2.rb']}
59
+ #{input_string}
60
+ #{ "the user provided the following files: #{ file_content.collect{|k,v| "#{k}:#{v}" }.join("\n") }" if file_content }
61
+ }}
62
+ HEREDOC
63
+ @n2r_answers ||= []
64
+ @n2r_answer = llm.make_request(content)
65
+ @n2r_answers << { input: input_string, output: @n2r_answer }
66
+ @n2r_answer['code'].split("\n").each do |line|
67
+ puts line
68
+ end if @n2r_answer['code']
69
+ @n2r_answer['explanation'].split("\n").each do |line|
70
+ puts line
71
+ end
72
+ nil
73
+ end
74
+ end
75
+ end
76
+
77
+ # Include the module in the main object if in console
78
+ include N2B::IRB if defined?(IRB)
@@ -37,11 +37,22 @@ module N2M
37
37
  end
38
38
  answer = JSON.parse(response.body)['content'].first['text']
39
39
  begin
40
- # removee everything before the first { and after the last }
41
- answer = answer.sub(/.*\{(.*)\}.*/m, '{\1}') unless answer.start_with?('{')
40
+ File.open('llm_response.json', 'w') do |f|
41
+ f.write(answer)
42
+ end
43
+ # remove everything before the first { and after the last }
44
+
45
+ answer = answer.sub(/.*?\{(.*)\}.*/m, '{\1}') unless answer.start_with?('{')
46
+ # gsub all \n with \\n that are inside "
47
+ #
48
+ answer.gsub!(/"([^"]*)"/) { |match| match.gsub(/\n/, "\\n") }
49
+ File.open('llm_response.json', 'w') do |f|
50
+ f.write(answer)
51
+ end
42
52
  answer = JSON.parse(answer)
43
53
  rescue JSON::ParserError
44
- answer = { 'commands' => answer.split("\n"), explanation: answer}
54
+ puts "Error parsing JSON: #{answer}"
55
+ answer = { 'explanation' => answer}
45
56
  end
46
57
  answer
47
58
  end
@@ -6,7 +6,7 @@ module N2M
6
6
  module Llm
7
7
  class OpenAi
8
8
  API_URI = URI.parse('https://api.openai.com/v1/chat/completions')
9
- MODELS = { 'gpt-4o' => 'gpt-4o', 'gpt-35' => 'gpt-3.5-turbo-1106' }
9
+ MODELS = { 'gpt-4o' => 'gpt-4o','gpt-4o-mini'=>'gpt-4o-mini', 'gpt-35' => 'gpt-3.5-turbo-1106' }
10
10
 
11
11
  def initialize(config)
12
12
  @config = config
data/lib/n2b/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # lib/n2b/version.rb
2
2
  module N2B
3
- VERSION = "0.1.5"
3
+ VERSION = "0.2.0"
4
4
  end
data/lib/n2b.rb CHANGED
@@ -8,251 +8,12 @@ require 'fileutils'
8
8
  require 'n2b/version'
9
9
  require 'n2b/llm/claude'
10
10
  require 'n2b/llm/open_ai'
11
- module N2B
12
- class Error < StandardError; end
13
-
14
- CONFIG_FILE = File.expand_path('~/.n2b/config.yml')
15
- HISTORY_FILE = File.expand_path('~/.n2b/history')
16
-
17
- class CLI
18
- def self.run(args)
19
- new(args).execute
20
- end
21
-
22
- def initialize(args)
23
- @args = args
24
- @options = parse_options
25
- end
26
-
27
- def execute
28
- config = get_config(reconfigure: @options[:config])
29
- input_text = @args.join(' ')
30
- if input_text.empty?
31
- puts "Enter your natural language command:"
32
- input_text = $stdin.gets.chomp
33
- end
34
-
35
- bash_commands = call_llm(input_text, config)
36
-
37
- puts "\nTranslated #{get_user_shell} Commands:"
38
- puts "------------------------"
39
- puts bash_commands['commands']
40
- puts "------------------------"
41
- if bash_commands['explanation']
42
- puts "Explanation:"
43
- puts bash_commands['explanation']
44
- puts "------------------------"
45
- end
46
-
47
- if @options[:execute]
48
- puts "Press Enter to execute these commands, or Ctrl+C to cancel."
49
- $stdin.gets
50
- system(bash_commands['commands'].join("\n"))
51
- else
52
- add_to_shell_history(bash_commands['commands'].join("\n")) if config['append_to_shell_history']
53
- end
54
- end
55
-
56
- private
11
+ require 'n2b/base'
12
+ require 'n2b/cli'
57
13
 
58
- def load_config
59
- if File.exist?(CONFIG_FILE)
60
- YAML.load_file(CONFIG_FILE)
61
- else
62
- { }
63
- end
64
- end
65
-
66
- def get_config( reconfigure: false)
67
- config = load_config
68
- api_key = ENV['CLAUDE_API_KEY'] || config['access_key']
69
- model = config['model'] || 'sonnet35'
70
-
71
- if api_key.nil? || api_key == '' || reconfigure
72
- print "choose a language model to use (claude, openai): "
73
- llm = $stdin.gets.chomp
74
- unless ['claude', 'openai'].include?(llm)
75
- puts "Invalid language model. Choose from: claude, openai"
76
- exit 1
77
- end
78
- llm_class = llm == 'openai' ? N2M::Llm::OpenAi : N2M::Llm::Claude
14
+ require 'n2b/irb'
79
15
 
80
- print "Enter your #{llm} API key: #{ api_key.nil? || api_key.empty? ? '' : '(leave blank to keep the current key '+api_key[0..10]+'...)' }"
81
- api_key = $stdin.gets.chomp
82
- api_key = config['access_key'] if api_key.empty?
83
- print "Choose a model (#{ llm_class::MODELS.keys }, #{ llm_class::MODELS.keys.first } default): "
84
- model = $stdin.gets.chomp
85
- model = llm_class::MODELS.keys.first if model.empty?
86
- config['llm'] = llm
87
- config['access_key'] = api_key
88
- config['model'] = model
89
- unless llm_class::MODELS.keys.include?(model)
90
- puts "Invalid model. Choose from: #{llm_class::MODELS.keys.join(', ')}"
91
- exit 1
92
- end
93
-
94
- config['privacy'] ||= {}
95
- print "Do you want to send your shell history to #{llm}? (y/n): "
96
- config['privacy']['send_shell_history'] = $stdin.gets.chomp == 'y'
97
- print "Do you want to send your past requests and answers to #{llm}? (y/n): "
98
- config['privacy']['send_llm_history'] = $stdin.gets.chomp == 'y'
99
- print "Do you want to send your current directory to #{llm}? (y/n): "
100
- config['privacy']['send_current_directory'] = $stdin.gets.chomp == 'y'
101
- print "Do you want to append the commands to your shell history? (y/n): "
102
- config['append_to_shell_history'] = $stdin.gets.chomp == 'y'
103
-
104
- FileUtils.mkdir_p(File.dirname(CONFIG_FILE)) unless File.exist?(File.dirname(CONFIG_FILE))
105
- File.open(CONFIG_FILE, 'w+') do |f|
106
- f.write(config.to_yaml )
107
- end
108
- end
109
-
110
- config
111
- end
112
-
113
- def append_to_llm_history_file(commands)
114
- File.open(HISTORY_FILE, 'a') do |file|
115
- file.puts(commands)
116
- end
117
- end
118
-
119
- def read_llm_history_file
120
- history = File.read(HISTORY_FILE) if File.exist?(HISTORY_FILE)
121
- history ||= ''
122
- # limit to 20 most recent commands
123
- history.split("\n").last(20).join("\n")
124
- end
125
-
126
- def call_llm(prompt, config)
127
-
128
- llm = config['llm'] == 'openai' ? N2M::Llm::OpenAi.new(config) : N2M::Llm::Claude.new(config)
129
-
130
- content = <<-EOF
131
- Translate the following natural language command to bash commands: #{prompt}\n\nProvide only the #{get_user_shell} commands for #{ get_user_os }. the commands should be separated by newlines.
132
- #{' the user is in directory'+Dir.pwd if config['privacy']['send_current_directory']}.
133
- #{' the user sent past requests to you and got these answers '+read_llm_history_file if config['privacy']['send_llm_history'] }
134
- #{ "The user has this history for his shell. "+read_shell_history if config['privacy']['send_shell_history'] }
135
- he is using #{File.basename(get_user_shell)} shell."
136
- answer only with a valid json object with the key 'commands' and the value as a list of bash commands plus any additional information you want to provide in explanation.
137
- { "commands": [ "echo 'Hello, World!'" ], "explanation": "This command prints 'Hello, World!' to the terminal."}
138
- EOF
139
-
140
-
141
- answer = llm.make_request(content)
142
-
143
- append_to_llm_history_file("#{prompt}\n#{answer}")
144
- answer
145
- end
146
-
147
- def get_user_shell
148
- ENV['SHELL'] || `getent passwd #{ENV['USER']}`.split(':')[6]
149
- end
150
-
151
- def get_user_os
152
- case RbConfig::CONFIG['host_os']
153
- when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
154
- :windows
155
- when /darwin|mac os/
156
- :macos
157
- when /linux/
158
- :linux
159
- when /solaris|bsd/
160
- :unix
161
- else
162
- :unknown
163
- end
164
- end
165
-
166
- def find_history_file(shell)
167
- case shell
168
- when 'zsh'
169
- [
170
- ENV['HISTFILE'],
171
- File.expand_path('~/.zsh_history'),
172
- File.expand_path('~/.zhistory')
173
- ].find { |f| f && File.exist?(f) }
174
- when 'bash'
175
- [
176
- ENV['HISTFILE'],
177
- File.expand_path('~/.bash_history')
178
- ].find { |f| f && File.exist?(f) }
179
- else
180
- nil
181
- end
182
- end
183
-
184
- def read_shell_history()
185
- shell = File.basename(get_user_shell)
186
- history_file = find_history_file(shell)
187
- return '' unless history_file
188
-
189
- File.read(history_file)
190
- end
191
-
192
- def add_to_shell_history(commands)
193
- shell = File.basename(get_user_shell)
194
- history_file = find_history_file(shell)
195
-
196
- unless history_file
197
- puts "Could not find history file for #{shell}. Cannot add commands to history."
198
- return
199
- end
200
-
201
- case shell
202
- when 'zsh'
203
- add_to_zsh_history(commands, history_file)
204
- when 'bash'
205
- add_to_bash_history(commands, history_file)
206
- else
207
- puts "Unsupported shell: #{shell}. Cannot add commands to history."
208
- return
209
- end
210
-
211
- puts "Commands have been added to your #{shell} history file: #{history_file}"
212
- puts "You may need to start a new shell session or reload your history to see the changes. #{ shell == 'zsh' ? 'For example, run `fc -R` in your zsh session.' : 'history -r for bash' }"
213
- puts "Then you can access them using the up arrow key or Ctrl+R for reverse search."
214
- end
215
-
216
- def add_to_zsh_history(commands, history_file)
217
- File.open(history_file, 'a') do |file|
218
- commands.each_line do |cmd|
219
- timestamp = Time.now.to_i
220
- file.puts(": #{timestamp}:0;#{cmd.strip}")
221
- end
222
- end
223
- end
224
-
225
- def add_to_bash_history(commands, history_file)
226
- File.open(history_file, 'a') do |file|
227
- commands.each_line do |cmd|
228
- file.puts(cmd.strip)
229
- end
230
- end
231
- system("history -r") # Attempt to reload history in current session
232
- end
233
-
234
-
235
- def parse_options
236
- options = { execute: false, config: nil }
237
-
238
- OptionParser.new do |opts|
239
- opts.banner = "Usage: n2b [options] [natural language command]"
240
-
241
- opts.on('-x', '--execute', 'Execute the commands after confirmation') do
242
- options[:execute] = true
243
- end
244
-
245
- opts.on('-h', '--help', 'Print this help') do
246
- puts opts
247
- exit
248
- end
249
-
250
- opts.on('-c', '--config', 'Configure the API key and model') do
251
- options[:config] = true
252
- end
253
- end.parse!(@args)
16
+ module N2B
17
+ class Error < StandardError; end
254
18
 
255
- options
256
- end
257
- end
258
19
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: n2b
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Nothegger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-07-20 00:00:00.000000000 Z
11
+ date: 2024-07-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -53,7 +53,8 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '13.0'
55
55
  description: A tool to convert natural language instructions to bash commands using
56
- Claude API or OpenAI's GPT.
56
+ Claude API or OpenAI's GPT. also is q quick helper in the console to provide ruby
57
+ code snippets and explanations or debug exceptions.
57
58
  email:
58
59
  - stefan@kaproblem.com
59
60
  executables:
@@ -64,6 +65,9 @@ files:
64
65
  - README.md
65
66
  - bin/n2b
66
67
  - lib/n2b.rb
68
+ - lib/n2b/base.rb
69
+ - lib/n2b/cli.rb
70
+ - lib/n2b/irb.rb
67
71
  - lib/n2b/llm/claude.rb
68
72
  - lib/n2b/llm/open_ai.rb
69
73
  - lib/n2b/version.rb
@@ -92,5 +96,5 @@ requirements: []
92
96
  rubygems_version: 3.0.9
93
97
  signing_key:
94
98
  specification_version: 4
95
- summary: Convert natural language to bash commands
99
+ summary: Convert natural language to bash commands or ruby code and help with debugging.
96
100
  test_files: []