ruby-shell 2.7.0 → 2.9.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 +4 -4
- data/README.md +35 -2
- data/bin/rsh +369 -34
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3bfe09de4762828310479cc0535ec0942b575fc331d4f16bfc1614861cf14b8c
|
4
|
+
data.tar.gz: 264e440a9257eb49e7985a034fbfc8ec7a3be5fb1efc9e046d4ef7cf94116872
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bc12e932035ded646ff4459c7d3ad299582102c3d208b8ddb1b529691f8cc21d02ec3e7d7c21b4a346b716eba495540361291b5e483438b712afe9c6fb0ea381
|
7
|
+
data.tar.gz: bf29a4a08d04835d7275a09c9bb0970d597a72a207a68e57e42755b433613dddac4d93bcae3a0152b74f6a8093562b8f748d8f33844bb736f0fdff66da83bfeb
|
data/README.md
CHANGED
@@ -33,7 +33,22 @@ Or simply `gem install ruby-shell`.
|
|
33
33
|
* All colors are themeable in .rshrc (see github link for possibilities)
|
34
34
|
* Copy current command line to primary selection (paste w/middle button) with `Ctrl-y`
|
35
35
|
|
36
|
-
## NEW in v2.
|
36
|
+
## NEW in v2.9.0 - AI Integration ⭐
|
37
|
+
* **AI-powered command assistance**: Get help with commands using natural language
|
38
|
+
* **`@ <question>`**: Ask questions and get AI-generated text responses
|
39
|
+
* **`@@ <request>`**: Describe what you want to do, and AI suggests the command
|
40
|
+
* **Smart command suggestion**: AI suggestions appear directly on the command line, ready to execute
|
41
|
+
* **Local AI support**: Works with Ollama for privacy-focused local AI
|
42
|
+
* **External AI support**: Configure OpenAI or other providers via `.rshrc`
|
43
|
+
* **Syntax highlighting**: @ and @@ commands are highlighted in blue
|
44
|
+
|
45
|
+
## Enhanced Help System & Nick Management (v2.8.0)
|
46
|
+
* **Two-column help display**: Compact, organized help that fits on one screen
|
47
|
+
* **New `:info` command**: Shows introduction and feature overview
|
48
|
+
* **`:nickdel` and `:gnickdel`**: Intuitive commands to delete nicks and gnicks
|
49
|
+
* **Improved help organization**: Quick reference for keyboard shortcuts, commands, and features
|
50
|
+
|
51
|
+
## Ruby Functions (v2.7.0)
|
37
52
|
* **Define Ruby functions as shell commands**: `:defun 'weather(*args) = system("curl -s wttr.in/#{args[0] || \"oslo\"}")'`
|
38
53
|
* **Call like any shell command**: `weather london`
|
39
54
|
* **Full Ruby power**: Access to Ruby stdlib, file operations, JSON parsing, web requests, etc.
|
@@ -58,13 +73,16 @@ Special functions/integrations:
|
|
58
73
|
Special commands:
|
59
74
|
* `:nick 'll = ls -l'` to make a command alias (ll) point to a command (ls -l)
|
60
75
|
* `:gnick 'h = /home/me'` to make a general alias (h) point to something (/home/me)
|
76
|
+
* `:nickdel 'name'` to delete a command nick (or use `:nick '-name'`)
|
77
|
+
* `:gnickdel 'name'` to delete a general nick (or use `:gnick '-name'`)
|
61
78
|
* `:nick?` will list all command nicks and general nicks (you can edit your nicks in .rshrc)
|
62
79
|
* `:history` will list the command history, while `:rmhistory` will delete the history
|
63
80
|
* `:jobs` will list background jobs, `:fg [job_id]` brings jobs to foreground, `:bg [job_id]` resumes stopped jobs
|
64
81
|
* `:defun 'func(args) = code'` defines Ruby functions callable as shell commands
|
65
82
|
* `:defun?` lists all user-defined functions, `:defun '-func'` removes functions
|
83
|
+
* `:info` shows introduction and feature overview
|
66
84
|
* `:version` Shows the rsh version number and the last published gem file version
|
67
|
-
* `:help` will display
|
85
|
+
* `:help` will display a compact command reference in two columns
|
68
86
|
|
69
87
|
Background jobs:
|
70
88
|
* Use `command &` to run commands in background
|
@@ -72,6 +90,21 @@ Background jobs:
|
|
72
90
|
* Use `:fg` or `:fg job_id` to bring jobs to foreground
|
73
91
|
* Use `Ctrl-Z` to suspend running jobs, `:bg job_id` to resume them
|
74
92
|
|
93
|
+
## AI Configuration
|
94
|
+
The AI features work out of the box with Ollama for local AI processing. To set up:
|
95
|
+
|
96
|
+
### Local AI (Recommended)
|
97
|
+
1. Install Ollama: `curl -fsSL https://ollama.com/install.sh | sh`
|
98
|
+
2. Pull a model: `ollama pull llama3.2`
|
99
|
+
3. That's it! Use `@ What is the capital of France?` or `@@ list files by size`
|
100
|
+
|
101
|
+
### External AI (OpenAI)
|
102
|
+
Add to your `.rshrc`:
|
103
|
+
```ruby
|
104
|
+
@aimodel = "gpt-4"
|
105
|
+
@aikey = "your-api-key-here"
|
106
|
+
```
|
107
|
+
|
75
108
|
## Moving around
|
76
109
|
While you `cd` around to different directories, you can see the last 10 directories visited via the command `:dirs` or the convenient shortcut `#`. Entering the number in the list (like `6` and ENTER) will jump you to that directory. Entering `-` will jump you back to the previous dir (equivalent of `1`. Entering `~` will get you to your home dir. If you want to bookmark a special directory, you can do that via a general nick like this: `:gnick "x = /path/to/a/dir/"` - this would bookmark the directory to the single letter `x`.
|
77
110
|
|
data/bin/rsh
CHANGED
@@ -8,7 +8,7 @@
|
|
8
8
|
# Web_site: http://isene.com/
|
9
9
|
# Github: https://github.com/isene/rsh
|
10
10
|
# License: Public domain
|
11
|
-
@version = "2.
|
11
|
+
@version = "2.9.0" # Feature release: AI integration with @ and @@ commands
|
12
12
|
|
13
13
|
# MODULES, CLASSES AND EXTENSIONS
|
14
14
|
class String # Add coloring to strings (with escaping for Readline)
|
@@ -109,20 +109,22 @@ begin # Initialization
|
|
109
109
|
@dirs = ["."]*10
|
110
110
|
@jobs = {} # Background jobs tracking
|
111
111
|
@job_id = 0 # Job counter
|
112
|
+
@ai_suggestion = nil # Store AI command suggestion
|
112
113
|
@last_exit = 0 # Last command exit status
|
113
114
|
def pre_cmd; end # User-defined function to be run BEFORE command execution
|
114
115
|
def post_cmd; end # User-defined function to be run AFTER command execution
|
115
116
|
end
|
116
117
|
|
117
118
|
# HELP TEXT
|
118
|
-
@
|
119
|
+
@info = <<~INFO
|
119
120
|
|
120
121
|
Hello #{@user}, welcome to rsh - the Ruby SHell.
|
121
122
|
|
122
123
|
rsh does not attempt to compete with the grand old shells like bash and zsh.
|
123
124
|
It serves the specific needs and wants of its author. If you like it, then feel free
|
124
|
-
to ask for more or different features here: https://github.com/isene/rsh.
|
125
|
-
|
125
|
+
to ask for more or different features here: https://github.com/isene/rsh.
|
126
|
+
|
127
|
+
Features:
|
126
128
|
* Aliases (called nicks in rsh) - both for commands and general nicks
|
127
129
|
* Syntax highlighting, matching nicks, system commands and valid dirs/files
|
128
130
|
* Tab completions for nicks, system commands, command switches and dirs/files
|
@@ -137,41 +139,19 @@ end
|
|
137
139
|
* rsh specific commands and full set of Ruby commands available via :<command>
|
138
140
|
* All colors are themeable in .rshrc (see github link for possibilities)
|
139
141
|
* Copy current command line to primary selection (paste w/middle button) with `Ctrl-y`
|
140
|
-
|
141
|
-
Special functions/integrations:
|
142
|
-
* Use `r` to launch rtfm (https://github.com/isene/RTFM) - if you have it installed
|
143
|
-
* Use `f` to launch fzf (https://github.com/junegunn/fzf) - if you have it installed
|
144
|
-
* Use `=` followed by xrpn commands separated by commas or double-spaces (https://github.com/isene/xrpn)
|
145
|
-
* Use `:` followed by a Ruby expression to access the whole world of Ruby
|
146
|
-
|
147
|
-
Special commands:
|
148
|
-
* `:nick 'll = ls -l'` to make a command alias (ll) point to a command (ls -l)
|
149
|
-
* `:gnick 'h = /home/me'` to make a general alias (h) point to something (/home/me)
|
150
|
-
* `:nick?` will list all command nicks and general nicks (you can edit your nicks in .rshrc)
|
151
|
-
* `:history` will list the command history, while `:rmhistory` will delete the history
|
152
|
-
* `:jobs` will list background jobs, `:fg [job_id]` brings jobs to foreground, `:bg [job_id]` resumes stopped jobs
|
153
|
-
* `:defun 'func(args) = code'` defines Ruby functions callable as shell commands
|
154
|
-
* `:defun?` lists all user-defined functions, `:defun '-func'` removes functions
|
155
|
-
* `:version` Shows the rsh version number and the last published gem file version
|
156
|
-
* `:help` will display this help text
|
157
|
-
|
158
|
-
Background jobs:
|
159
|
-
* Use `command &` to run commands in background
|
160
|
-
* Use `:jobs` to list active background jobs
|
161
|
-
* Use `:fg` or `:fg job_id` to bring jobs to foreground
|
162
|
-
* Use `Ctrl-Z` to suspend running jobs, `:bg job_id` to resume them
|
142
|
+
* AI integration: Use @ for text responses and @@ for command suggestions (requires ollama or OpenAI)
|
163
143
|
|
164
|
-
|
165
|
-
* Define with `:defun 'myls(*args) = Dir.glob("*").each {|f| puts f}'`
|
166
|
-
* Call like any shell command: `myls` or `myls arg1 arg2`
|
167
|
-
* Functions have full access to Ruby stdlib and rsh internals
|
168
|
-
* Remove with `:defun '-myls'` and list with `:defun?`
|
144
|
+
Use `:help` for command reference.
|
169
145
|
|
146
|
+
INFO
|
147
|
+
|
148
|
+
@help = <<~HELP
|
149
|
+
|
170
150
|
HELP
|
171
151
|
|
172
152
|
# GENERIC FUNCTIONS
|
173
153
|
def firstrun
|
174
|
-
puts @
|
154
|
+
puts @info
|
175
155
|
puts "Since there is no rsh configuration file (.rshrc), I will help you set it up to suit your needs.\n\n"
|
176
156
|
puts "The prompt you see now is the very basic rsh prompt:"
|
177
157
|
print "#{@prompt} (press ENTER)"
|
@@ -256,6 +236,12 @@ def getstr # A custom Readline-like function
|
|
256
236
|
@pos = 0
|
257
237
|
chr = ""
|
258
238
|
@history.unshift("")
|
239
|
+
# Check if we have an AI suggestion to pre-fill
|
240
|
+
if @ai_suggestion
|
241
|
+
@history[0] = @ai_suggestion
|
242
|
+
@pos = @ai_suggestion.length
|
243
|
+
@ai_suggestion = nil
|
244
|
+
end
|
259
245
|
@row0, p = @c.pos
|
260
246
|
while chr != "ENTER" # Keep going with readline until user presses ENTER
|
261
247
|
@ci = nil
|
@@ -569,6 +555,14 @@ def hist_clean # Clean up @history
|
|
569
555
|
end
|
570
556
|
def cmd_check(str) # Check if each element on the readline matches commands, nicks, paths; color them
|
571
557
|
return if str.nil?
|
558
|
+
|
559
|
+
# Special handling for @ and @@ commands
|
560
|
+
if str =~ /^(@@?)\s+(.*)$/
|
561
|
+
prefix = $1
|
562
|
+
rest = $2
|
563
|
+
return prefix.c(4) + " " + rest # Color @ or @@ in blue (4), rest uncolored
|
564
|
+
end
|
565
|
+
|
572
566
|
str.gsub(/(?:\S'[^']*'|[^ '])+/) do |el|
|
573
567
|
if @exe.include?(el)
|
574
568
|
el.c(@c_cmd)
|
@@ -610,7 +604,97 @@ end
|
|
610
604
|
|
611
605
|
# RSH FUNCTIONS
|
612
606
|
def help
|
613
|
-
|
607
|
+
# Get terminal width
|
608
|
+
term_width = @maxcol || 80
|
609
|
+
col_width = 48 # Fixed width for left column
|
610
|
+
|
611
|
+
# Helper function to strip ANSI codes for length calculation
|
612
|
+
def strip_ansi(str)
|
613
|
+
str.gsub(/\001?\e\[[0-9;]*m\002?/, '')
|
614
|
+
end
|
615
|
+
|
616
|
+
left_col = []
|
617
|
+
right_col = []
|
618
|
+
|
619
|
+
# Left column content
|
620
|
+
left_col << "KEYBOARD SHORTCUTS:".c(@c_prompt).b
|
621
|
+
left_col << "RIGHT/Ctrl-F Accept suggestion"
|
622
|
+
left_col << "UP/DOWN Navigate history"
|
623
|
+
left_col << "TAB Tab complete"
|
624
|
+
left_col << "Ctrl-Y Copy to clipboard"
|
625
|
+
left_col << "Ctrl-D Exit + save .rshrc"
|
626
|
+
left_col << "Ctrl-E Exit without save"
|
627
|
+
left_col << "Ctrl-L Clear screen"
|
628
|
+
left_col << "Ctrl-Z Suspend job"
|
629
|
+
left_col << "Ctrl-C/G Clear line"
|
630
|
+
left_col << "Ctrl-K Delete history item"
|
631
|
+
left_col << "Ctrl-U Clear line"
|
632
|
+
left_col << "Ctrl-W Delete previous word"
|
633
|
+
left_col << ""
|
634
|
+
left_col << "SPECIAL COMMANDS:".c(@c_prompt).b
|
635
|
+
left_col << ":nick 'll = ls -l' Command alias"
|
636
|
+
left_col << ":gnick 'h = /home' General alias"
|
637
|
+
left_col << ":nickdel 'name' Delete nick"
|
638
|
+
left_col << ":gnickdel 'name' Delete gnick"
|
639
|
+
left_col << ":nick? List all nicks"
|
640
|
+
left_col << ":history Show history"
|
641
|
+
left_col << ":rmhistory Clear history"
|
642
|
+
left_col << ":info About rsh"
|
643
|
+
left_col << ":version Version info"
|
644
|
+
left_col << ":help This help"
|
645
|
+
|
646
|
+
# Right column content
|
647
|
+
right_col << "RUBY FUNCTIONS:".c(@c_prompt).b
|
648
|
+
right_col << ":defun 'f(x) = x*2' Define function"
|
649
|
+
right_col << ":defun? List functions"
|
650
|
+
right_col << ":defun '-f' Remove function"
|
651
|
+
right_col << "Call as: f 5 (returns 10)"
|
652
|
+
right_col << ""
|
653
|
+
right_col << "JOB CONTROL:".c(@c_prompt).b
|
654
|
+
right_col << "command & Background job"
|
655
|
+
right_col << ":jobs List jobs"
|
656
|
+
right_col << ":fg [id] Foreground job"
|
657
|
+
right_col << ":bg [id] Resume in bg"
|
658
|
+
right_col << ""
|
659
|
+
right_col << "INTEGRATIONS:".c(@c_prompt).b
|
660
|
+
right_col << "r Launch rtfm"
|
661
|
+
right_col << "f Launch fzf"
|
662
|
+
right_col << "= <expr> xrpn calculator"
|
663
|
+
right_col << ":<ruby code> Execute Ruby"
|
664
|
+
right_col << ""
|
665
|
+
right_col << "AI FEATURES:".c(@c_prompt).b
|
666
|
+
right_col << "@ <question> AI text response"
|
667
|
+
right_col << "@@ <request> AI command → prompt"
|
668
|
+
right_col << ""
|
669
|
+
right_col << "EXPANSIONS:".c(@c_prompt).b
|
670
|
+
right_col << "~ Home directory"
|
671
|
+
right_col << "$VAR, ${VAR} Environment var"
|
672
|
+
right_col << "$? Exit status"
|
673
|
+
right_col << "$(cmd), `cmd` Command subst"
|
674
|
+
right_col << "{a,b,c} Brace expansion"
|
675
|
+
right_col << "cmd1 && cmd2 Conditional"
|
676
|
+
right_col << "cmd1 || cmd2 Alternative"
|
677
|
+
|
678
|
+
# Pad columns to same length
|
679
|
+
max_lines = [left_col.length, right_col.length].max
|
680
|
+
left_col.fill("", left_col.length...max_lines)
|
681
|
+
right_col.fill("", right_col.length...max_lines)
|
682
|
+
|
683
|
+
# Print in two columns
|
684
|
+
puts
|
685
|
+
max_lines.times do |i|
|
686
|
+
left_text = left_col[i].to_s
|
687
|
+
right_text = right_col[i].to_s
|
688
|
+
# Calculate padding based on visible characters (without ANSI codes)
|
689
|
+
visible_length = strip_ansi(left_text).length
|
690
|
+
padding = col_width - visible_length
|
691
|
+
padding = 0 if padding < 0
|
692
|
+
puts " #{left_text}#{' ' * padding} #{right_text}"
|
693
|
+
end
|
694
|
+
puts
|
695
|
+
end
|
696
|
+
def info
|
697
|
+
puts @info
|
614
698
|
end
|
615
699
|
def version
|
616
700
|
puts "rsh version = #{@version} (latest RubyGems version is #{Gem.latest_version_for("ruby-shell").version} - https://github.com/isene/rsh)"
|
@@ -651,6 +735,16 @@ def nick? # Show nicks
|
|
651
735
|
puts " General nicks:".c(@c_gnick)
|
652
736
|
@gnick.sort.each {|key, value| puts " #{key} = #{value}"}
|
653
737
|
end
|
738
|
+
def nickdel(nick_name) # Delete a command nick
|
739
|
+
@nick.delete(nick_name)
|
740
|
+
rshrc
|
741
|
+
puts "Nick '#{nick_name}' deleted"
|
742
|
+
end
|
743
|
+
def gnickdel(nick_name) # Delete a general/global nick
|
744
|
+
@gnick.delete(nick_name)
|
745
|
+
rshrc
|
746
|
+
puts "General nick '#{nick_name}' deleted"
|
747
|
+
end
|
654
748
|
def dirs
|
655
749
|
puts "Past direactories:"
|
656
750
|
@dirs.each_with_index do |e,i|
|
@@ -785,6 +879,226 @@ def expand_braces(str)
|
|
785
879
|
end
|
786
880
|
end
|
787
881
|
|
882
|
+
# AI INTEGRATION FUNCTIONS
|
883
|
+
def get_ollama_model
|
884
|
+
begin
|
885
|
+
# Try to get list of available models
|
886
|
+
output = `ollama list 2>/dev/null`
|
887
|
+
return nil if output.empty? || $?.exitstatus != 0
|
888
|
+
|
889
|
+
# Parse the output to find a suitable model
|
890
|
+
lines = output.split("\n")
|
891
|
+
return nil if lines.length < 2
|
892
|
+
|
893
|
+
# Skip header line and get first available model
|
894
|
+
model_line = lines[1]
|
895
|
+
return nil if model_line.nil?
|
896
|
+
|
897
|
+
# Extract model name (first column)
|
898
|
+
model_name = model_line.split(/\s+/)[0]
|
899
|
+
return model_name
|
900
|
+
rescue => e
|
901
|
+
return nil
|
902
|
+
end
|
903
|
+
end
|
904
|
+
|
905
|
+
def ai_query(prompt)
|
906
|
+
# Get AI model configuration
|
907
|
+
model = @aimodel || nil
|
908
|
+
key = @aikey || nil
|
909
|
+
|
910
|
+
if model.nil? || model.empty?
|
911
|
+
# Try ollama first
|
912
|
+
if File.exist?("/usr/local/bin/ollama") || File.exist?("/usr/bin/ollama") || system("command -v ollama >/dev/null 2>&1")
|
913
|
+
begin
|
914
|
+
require 'json'
|
915
|
+
require 'net/http'
|
916
|
+
|
917
|
+
# First, get available models
|
918
|
+
ollama_model = get_ollama_model()
|
919
|
+
return ai_setup_help unless ollama_model
|
920
|
+
|
921
|
+
uri = URI('http://localhost:11434/api/generate')
|
922
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
923
|
+
http.read_timeout = 30
|
924
|
+
|
925
|
+
request = Net::HTTP::Post.new(uri)
|
926
|
+
request['Content-Type'] = 'application/json'
|
927
|
+
request.body = {
|
928
|
+
model: ollama_model,
|
929
|
+
prompt: prompt,
|
930
|
+
stream: false,
|
931
|
+
options: {
|
932
|
+
num_predict: 200
|
933
|
+
}
|
934
|
+
}.to_json
|
935
|
+
|
936
|
+
response = http.request(request)
|
937
|
+
if response.code == '200'
|
938
|
+
result = JSON.parse(response.body)
|
939
|
+
return result['response']
|
940
|
+
else
|
941
|
+
return ai_setup_help
|
942
|
+
end
|
943
|
+
rescue => e
|
944
|
+
return ai_setup_help
|
945
|
+
end
|
946
|
+
else
|
947
|
+
return ai_setup_help
|
948
|
+
end
|
949
|
+
else
|
950
|
+
# Use external model
|
951
|
+
if model =~ /^gpt/i && key
|
952
|
+
begin
|
953
|
+
require 'json'
|
954
|
+
require 'net/http'
|
955
|
+
uri = URI('https://api.openai.com/v1/chat/completions')
|
956
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
957
|
+
http.use_ssl = true
|
958
|
+
http.read_timeout = 30
|
959
|
+
|
960
|
+
request = Net::HTTP::Post.new(uri)
|
961
|
+
request['Content-Type'] = 'application/json'
|
962
|
+
request['Authorization'] = "Bearer #{key}"
|
963
|
+
request.body = {
|
964
|
+
model: model,
|
965
|
+
messages: [{role: 'user', content: prompt}],
|
966
|
+
max_tokens: 200,
|
967
|
+
temperature: 0.7
|
968
|
+
}.to_json
|
969
|
+
|
970
|
+
response = http.request(request)
|
971
|
+
if response.code == '200'
|
972
|
+
result = JSON.parse(response.body)
|
973
|
+
return result['choices'][0]['message']['content']
|
974
|
+
else
|
975
|
+
return "Error: #{response.code} - #{response.body}"
|
976
|
+
end
|
977
|
+
rescue => e
|
978
|
+
return "Error connecting to OpenAI: #{e.message}"
|
979
|
+
end
|
980
|
+
else
|
981
|
+
return "Unsupported model: #{model}. Currently only ollama and OpenAI models are supported."
|
982
|
+
end
|
983
|
+
end
|
984
|
+
end
|
985
|
+
|
986
|
+
def ai_command_suggest(prompt)
|
987
|
+
# Get AI model configuration
|
988
|
+
model = @aimodel || nil
|
989
|
+
key = @aikey || nil
|
990
|
+
|
991
|
+
# Modify prompt to request command output
|
992
|
+
cmd_prompt = "You are a Linux/Unix command line expert. Given this request: '#{prompt}', output ONLY the exact shell command that would accomplish this task. Output just the command itself with no explanation, no backticks, no markdown. For example, if asked 'list files' you would output: ls"
|
993
|
+
|
994
|
+
if model.nil? || model.empty?
|
995
|
+
# Try ollama first
|
996
|
+
if File.exist?("/usr/local/bin/ollama") || File.exist?("/usr/bin/ollama") || system("command -v ollama >/dev/null 2>&1")
|
997
|
+
begin
|
998
|
+
require 'json'
|
999
|
+
require 'net/http'
|
1000
|
+
|
1001
|
+
# First, get available models
|
1002
|
+
ollama_model = get_ollama_model()
|
1003
|
+
unless ollama_model
|
1004
|
+
puts ai_setup_help
|
1005
|
+
return nil
|
1006
|
+
end
|
1007
|
+
|
1008
|
+
uri = URI('http://localhost:11434/api/generate')
|
1009
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
1010
|
+
http.read_timeout = 30
|
1011
|
+
|
1012
|
+
request = Net::HTTP::Post.new(uri)
|
1013
|
+
request['Content-Type'] = 'application/json'
|
1014
|
+
request.body = {
|
1015
|
+
model: ollama_model,
|
1016
|
+
prompt: cmd_prompt,
|
1017
|
+
stream: false,
|
1018
|
+
options: {
|
1019
|
+
num_predict: 50,
|
1020
|
+
temperature: 0.3
|
1021
|
+
}
|
1022
|
+
}.to_json
|
1023
|
+
|
1024
|
+
response = http.request(request)
|
1025
|
+
if response.code == '200'
|
1026
|
+
result = JSON.parse(response.body)
|
1027
|
+
cmd = result['response'].strip.split("\n")[0]
|
1028
|
+
return cmd
|
1029
|
+
else
|
1030
|
+
puts ai_setup_help
|
1031
|
+
return nil
|
1032
|
+
end
|
1033
|
+
rescue => e
|
1034
|
+
puts ai_setup_help
|
1035
|
+
return nil
|
1036
|
+
end
|
1037
|
+
else
|
1038
|
+
puts ai_setup_help
|
1039
|
+
return nil
|
1040
|
+
end
|
1041
|
+
else
|
1042
|
+
# Use external model
|
1043
|
+
if model =~ /^gpt/i && key
|
1044
|
+
begin
|
1045
|
+
require 'json'
|
1046
|
+
require 'net/http'
|
1047
|
+
uri = URI('https://api.openai.com/v1/chat/completions')
|
1048
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
1049
|
+
http.use_ssl = true
|
1050
|
+
http.read_timeout = 30
|
1051
|
+
|
1052
|
+
request = Net::HTTP::Post.new(uri)
|
1053
|
+
request['Content-Type'] = 'application/json'
|
1054
|
+
request['Authorization'] = "Bearer #{key}"
|
1055
|
+
request.body = {
|
1056
|
+
model: model,
|
1057
|
+
messages: [{role: 'user', content: cmd_prompt}],
|
1058
|
+
max_tokens: 50,
|
1059
|
+
temperature: 0.3
|
1060
|
+
}.to_json
|
1061
|
+
|
1062
|
+
response = http.request(request)
|
1063
|
+
if response.code == '200'
|
1064
|
+
result = JSON.parse(response.body)
|
1065
|
+
cmd = result['choices'][0]['message']['content'].strip.split("\n")[0]
|
1066
|
+
return cmd
|
1067
|
+
else
|
1068
|
+
puts "Error: #{response.code} - #{response.body}"
|
1069
|
+
return nil
|
1070
|
+
end
|
1071
|
+
rescue => e
|
1072
|
+
puts "Error connecting to OpenAI: #{e.message}"
|
1073
|
+
return nil
|
1074
|
+
end
|
1075
|
+
else
|
1076
|
+
puts "Unsupported model: #{model}. Currently only ollama and OpenAI models are supported."
|
1077
|
+
return nil
|
1078
|
+
end
|
1079
|
+
end
|
1080
|
+
end
|
1081
|
+
|
1082
|
+
def ai_setup_help
|
1083
|
+
help_text = <<~HELP
|
1084
|
+
|
1085
|
+
AI is not configured. To use AI features, you have two options:
|
1086
|
+
|
1087
|
+
1. Install Ollama (recommended for local AI):
|
1088
|
+
curl -fsSL https://ollama.com/install.sh | sh
|
1089
|
+
ollama pull llama3.2 # or any model you prefer
|
1090
|
+
|
1091
|
+
2. Configure external AI model in ~/.rshrc:
|
1092
|
+
@aimodel = "gpt-4"
|
1093
|
+
@aikey = "your-api-key-here"
|
1094
|
+
|
1095
|
+
Once configured:
|
1096
|
+
- Use @ for AI text responses: @ What is the GDP of Norway?
|
1097
|
+
- Use @@ for AI command suggestions: @@ list files sorted by size
|
1098
|
+
HELP
|
1099
|
+
return help_text.c(@c_path)
|
1100
|
+
end
|
1101
|
+
|
788
1102
|
# INITIAL SETUP
|
789
1103
|
begin # Load .rshrc and populate @history
|
790
1104
|
trap "SIGINT" do end
|
@@ -861,6 +1175,10 @@ loop do
|
|
861
1175
|
hi = @history[@cmd.sub(/^!(\d+)$/, '\1').to_i+1]
|
862
1176
|
@cmd = hi if hi
|
863
1177
|
end
|
1178
|
+
# Move cursor to end of line and print the full command before clearing
|
1179
|
+
@c.row(@row0)
|
1180
|
+
@c.clear_line
|
1181
|
+
print @prompt + @cmd
|
864
1182
|
print "\n"; @c.clear_screen_down
|
865
1183
|
if @cmd == "r" # Integration with rtfm (https://github.com/isene/RTFM)
|
866
1184
|
t = Time.now
|
@@ -877,6 +1195,23 @@ loop do
|
|
877
1195
|
@cmd.gsub!(" ", ",")
|
878
1196
|
@cmd = "echo \"#{@cmd[1...]},prx,off\" | xrpn"
|
879
1197
|
end
|
1198
|
+
# AI integration with @ and @@
|
1199
|
+
if @cmd =~ /^@@\s+(.+)/ # AI command suggestion
|
1200
|
+
prompt = $1
|
1201
|
+
response = ai_command_suggest(prompt)
|
1202
|
+
if response
|
1203
|
+
# Store the suggestion for the next prompt
|
1204
|
+
@ai_suggestion = response
|
1205
|
+
# Also add to history for record keeping
|
1206
|
+
@history.unshift(response)
|
1207
|
+
end
|
1208
|
+
next
|
1209
|
+
elsif @cmd =~ /^@\s+(.+)/ # AI text response
|
1210
|
+
prompt = $1
|
1211
|
+
response = ai_query(prompt)
|
1212
|
+
puts response if response
|
1213
|
+
next
|
1214
|
+
end
|
880
1215
|
if @cmd.match(/^\s*:/) # Ruby commands are prefixed with ":"
|
881
1216
|
begin
|
882
1217
|
eval(@cmd[1..-1])
|
metadata
CHANGED
@@ -1,20 +1,21 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-shell
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Geir Isene
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-07-
|
11
|
+
date: 2025-07-28 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: 'A shell written in Ruby with extensive tab completions, aliases/nicks,
|
14
|
-
history, syntax highlighting, theming, auto-cd, auto-opening files and more.
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
history, syntax highlighting, theming, auto-cd, auto-opening files and more. UPDATE
|
15
|
+
v2.9.0: AI integration! Use @ for AI text responses and @@ for AI command suggestions.
|
16
|
+
Works with local Ollama or external providers like OpenAI. v2.8.0: Enhanced help
|
17
|
+
system, :info command, and easier nick management. v2.7.0: Ruby Functions, job control,
|
18
|
+
command substitution, and more.'
|
18
19
|
email: g@isene.com
|
19
20
|
executables:
|
20
21
|
- rsh
|