n2b 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -1
- data/lib/n2b/llm/claude.rb +50 -0
- data/lib/n2b/llm/open_ai.rb +53 -0
- data/lib/n2b/version.rb +1 -1
- data/lib/n2b.rb +28 -46
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a202dae3ba4645542ae044ca8c2e924c34673907ecbcc4bd123ac054fb1493eb
|
4
|
+
data.tar.gz: b308ead565a4ce70433f52551370246d2c4f1043e76d314f0ba5d4f7224f7a92
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9bcb6e988ca781399ff84c8189aa37c4ddface4cc6dc5474b9f22793b10b5de10c82bba7c8d2475bf0f0a030ea4c638fb4136ec2b5d98871a84affb60a5a5ab7
|
7
|
+
data.tar.gz: 598a4d9a0a684a8d95bbb644d2ba799b32e617868be56cc4e5d661d4ee6646cd42389c0315a60175546faadcdb0c4fde1742ca9270e3b9a9500d3f77c933d935
|
data/README.md
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
# N2B: Natural Language to Bash Commands Converter
|
2
2
|
|
3
|
-
N2B (Natural to Bash) is a Ruby gem that converts natural language instructions into executable shell commands using the Claude AI API. It's designed to help users quickly generate shell commands without needing to remember exact syntax.
|
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
4
|
|
5
5
|
## Features
|
6
6
|
|
7
7
|
- Convert natural language to shell commands
|
8
8
|
- Support for multiple Claude AI models (Haiku, Sonnet, Sonnet 3.5)
|
9
|
+
- Support for OpenAI models
|
9
10
|
- Option to execute generated commands directly
|
10
11
|
- Configurable privacy settings
|
11
12
|
- Shell history integration
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module N2M
|
2
|
+
module Llm
|
3
|
+
class Claude
|
4
|
+
API_URI = URI.parse('https://api.anthropic.com/v1/messages')
|
5
|
+
MODELS = { 'haiku' => 'claude-3-haiku-20240307', 'sonnet' => 'claude-3-sonnet-20240229', 'sonnet35' => 'claude-3-5-sonnet-20240620' }
|
6
|
+
|
7
|
+
def initialize(config)
|
8
|
+
@config = config
|
9
|
+
end
|
10
|
+
|
11
|
+
def make_request( content)
|
12
|
+
uri = URI.parse('https://api.anthropic.com/v1/messages')
|
13
|
+
request = Net::HTTP::Post.new(uri)
|
14
|
+
request.content_type = 'application/json'
|
15
|
+
request['X-API-Key'] = @config['access_key']
|
16
|
+
request['anthropic-version'] = '2023-06-01'
|
17
|
+
|
18
|
+
request.body = JSON.dump({
|
19
|
+
"model" => MODELS[@config['model']],
|
20
|
+
"max_tokens" => 1024,
|
21
|
+
"messages" => [
|
22
|
+
{
|
23
|
+
"role" => "user",
|
24
|
+
"content" => content
|
25
|
+
}
|
26
|
+
]
|
27
|
+
})
|
28
|
+
|
29
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
30
|
+
http.request(request)
|
31
|
+
end
|
32
|
+
# check for errors
|
33
|
+
if response.code != '200'
|
34
|
+
puts "Error: #{response.code} #{response.message}"
|
35
|
+
puts response.body
|
36
|
+
exit 1
|
37
|
+
end
|
38
|
+
answer = JSON.parse(response.body)['content'].first['text']
|
39
|
+
begin
|
40
|
+
# removee everything before the first { and after the last }
|
41
|
+
answer = answer.sub(/.*\{(.*)\}.*/m, '{\1}')
|
42
|
+
answer = JSON.parse(answer)
|
43
|
+
rescue JSON::ParserError
|
44
|
+
answer = { 'commands' => answer.split("\n"), explanation: answer}
|
45
|
+
end
|
46
|
+
answer
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'json'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
module N2M
|
6
|
+
module Llm
|
7
|
+
class OpenAi
|
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' }
|
10
|
+
|
11
|
+
def initialize(config)
|
12
|
+
@config = config
|
13
|
+
end
|
14
|
+
|
15
|
+
def make_request(content)
|
16
|
+
request = Net::HTTP::Post.new(API_URI)
|
17
|
+
request.content_type = 'application/json'
|
18
|
+
request['Authorization'] = "Bearer #{@config['access_key']}"
|
19
|
+
|
20
|
+
request.body = JSON.dump({
|
21
|
+
"model" => MODELS[@config['model']],
|
22
|
+
response_format: { type: 'json_object' },
|
23
|
+
"messages" => [
|
24
|
+
{
|
25
|
+
"role" => "user",
|
26
|
+
"content" => content
|
27
|
+
}]
|
28
|
+
})
|
29
|
+
|
30
|
+
response = Net::HTTP.start(API_URI.hostname, API_URI.port, use_ssl: true) do |http|
|
31
|
+
http.request(request)
|
32
|
+
end
|
33
|
+
|
34
|
+
# check for errors
|
35
|
+
if response.code != '200'
|
36
|
+
puts "Error: #{response.code} #{response.message}"
|
37
|
+
puts response.body
|
38
|
+
exit 1
|
39
|
+
end
|
40
|
+
puts JSON.parse(response.body)
|
41
|
+
answer = JSON.parse(response.body)['choices'].first['message']['content']
|
42
|
+
begin
|
43
|
+
# remove everything before the first { and after the last }
|
44
|
+
answer = answer.sub(/.*\{(.*)\}.*/m, '{\1}')
|
45
|
+
answer = JSON.parse(answer)
|
46
|
+
rescue JSON::ParserError
|
47
|
+
answer = { 'commands' => answer.split("\n"), explanation: answer }
|
48
|
+
end
|
49
|
+
answer
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/n2b/version.rb
CHANGED
data/lib/n2b.rb
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
# lib/n2b.rb
|
2
|
-
require "n2b/version"
|
3
2
|
require 'net/http'
|
4
3
|
require 'uri'
|
5
4
|
require 'json'
|
6
5
|
require 'optparse'
|
7
6
|
require 'yaml'
|
8
7
|
require 'fileutils'
|
9
|
-
|
8
|
+
require 'n2b/version'
|
9
|
+
require 'n2b/llm/claude'
|
10
|
+
require 'n2b/llm/open_ai'
|
10
11
|
module N2B
|
11
12
|
class Error < StandardError; end
|
12
13
|
|
13
14
|
CONFIG_FILE = File.expand_path('~/.n2b/config.yml')
|
14
15
|
HISTORY_FILE = File.expand_path('~/.n2b/history')
|
15
|
-
|
16
|
-
|
16
|
+
|
17
17
|
class CLI
|
18
18
|
def self.run(args)
|
19
19
|
new(args).execute
|
@@ -67,27 +67,36 @@ module N2B
|
|
67
67
|
config = load_config
|
68
68
|
api_key = ENV['CLAUDE_API_KEY'] || config['access_key']
|
69
69
|
model = config['model'] || 'sonnet35'
|
70
|
+
|
70
71
|
if api_key.nil? || api_key == '' || reconfigure
|
71
|
-
print "
|
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
|
79
|
+
|
80
|
+
print "Enter your #{llm} API key: #{ api_key.nil? || api_key.empty? ? '' : '(leave blank to keep the current key '+api_key[0..10]+'...)' }"
|
72
81
|
api_key = $stdin.gets.chomp
|
73
82
|
api_key = config['access_key'] if api_key.empty?
|
74
|
-
print "Choose a model (
|
83
|
+
print "Choose a model (#{ llm_class::MODELS.keys }, #{ llm_class::MODELS.keys.first } default): "
|
75
84
|
model = $stdin.gets.chomp
|
76
|
-
model =
|
77
|
-
config['llm']
|
85
|
+
model = llm_class::MODELS.keys.first if model.empty?
|
86
|
+
config['llm'] = llm
|
78
87
|
config['access_key'] = api_key
|
79
88
|
config['model'] = model
|
80
|
-
unless MODELS.keys.include?(model)
|
81
|
-
puts "Invalid model. Choose from: #{MODELS.keys.join(', ')}"
|
89
|
+
unless llm_class::MODELS.keys.include?(model)
|
90
|
+
puts "Invalid model. Choose from: #{llm_class::MODELS.keys.join(', ')}"
|
82
91
|
exit 1
|
83
92
|
end
|
84
93
|
|
85
94
|
config['privacy'] ||= {}
|
86
|
-
print "Do you want to send your shell history to
|
95
|
+
print "Do you want to send your shell history to #{llm}? (y/n): "
|
87
96
|
config['privacy']['send_shell_history'] = $stdin.gets.chomp == 'y'
|
88
|
-
print "Do you want to send your past requests and answers to
|
97
|
+
print "Do you want to send your past requests and answers to #{llm}? (y/n): "
|
89
98
|
config['privacy']['send_llm_history'] = $stdin.gets.chomp == 'y'
|
90
|
-
print "Do you want to send your current directory to
|
99
|
+
print "Do you want to send your current directory to #{llm}? (y/n): "
|
91
100
|
config['privacy']['send_current_directory'] = $stdin.gets.chomp == 'y'
|
92
101
|
print "Do you want to append the commands to your shell history? (y/n): "
|
93
102
|
config['append_to_shell_history'] = $stdin.gets.chomp == 'y'
|
@@ -115,11 +124,9 @@ module N2B
|
|
115
124
|
end
|
116
125
|
|
117
126
|
def call_llm(prompt, config)
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
request['X-API-Key'] = config['access_key']
|
122
|
-
request['anthropic-version'] = '2023-06-01'
|
127
|
+
|
128
|
+
llm = config['llm'] == 'openai' ? N2M::Llm::OpenAi.new(config) : N2M::Llm::Claude.new(config)
|
129
|
+
|
123
130
|
content = <<-EOF
|
124
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.
|
125
132
|
#{' the user is in directory'+Dir.pwd if config['privacy']['send_current_directory']}.
|
@@ -130,34 +137,9 @@ module N2B
|
|
130
137
|
{ "commands": [ "echo 'Hello, World!'" ], "explanation": "This command prints 'Hello, World!' to the terminal."}
|
131
138
|
EOF
|
132
139
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
"messages" => [
|
137
|
-
{
|
138
|
-
"role" => "user",
|
139
|
-
"content" => content
|
140
|
-
}
|
141
|
-
]
|
142
|
-
})
|
143
|
-
|
144
|
-
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
145
|
-
http.request(request)
|
146
|
-
end
|
147
|
-
# check for errors
|
148
|
-
if response.code != '200'
|
149
|
-
puts "Error: #{response.code} #{response.message}"
|
150
|
-
puts response.body
|
151
|
-
exit 1
|
152
|
-
end
|
153
|
-
answer = JSON.parse(response.body)['content'].first['text']
|
154
|
-
begin
|
155
|
-
# removee everything before the first { and after the last }
|
156
|
-
answer = answer.sub(/.*\{(.*)\}.*/m, '{\1}')
|
157
|
-
answer = JSON.parse(answer)
|
158
|
-
rescue JSON::ParserError
|
159
|
-
answer = { 'commands' => answer.split("\n"), explanation: answer}
|
160
|
-
end
|
140
|
+
|
141
|
+
answer = llm.make_request(content)
|
142
|
+
|
161
143
|
append_to_llm_history_file("#{prompt}\n#{answer}")
|
162
144
|
answer
|
163
145
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: n2b
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefan Nothegger
|
@@ -64,6 +64,8 @@ files:
|
|
64
64
|
- README.md
|
65
65
|
- bin/n2b
|
66
66
|
- lib/n2b.rb
|
67
|
+
- lib/n2b/llm/claude.rb
|
68
|
+
- lib/n2b/llm/open_ai.rb
|
67
69
|
- lib/n2b/version.rb
|
68
70
|
homepage: https://github.com/stefan-kp/n2b
|
69
71
|
licenses:
|
@@ -87,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
87
89
|
- !ruby/object:Gem::Version
|
88
90
|
version: '0'
|
89
91
|
requirements: []
|
90
|
-
rubygems_version: 3.
|
92
|
+
rubygems_version: 3.0.9
|
91
93
|
signing_key:
|
92
94
|
specification_version: 4
|
93
95
|
summary: Convert natural language to bash commands
|