ruby-shell 3.4.5 → 3.4.7
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/PLUGIN_GUIDE.md +107 -0
- data/README.md +1 -0
- data/bin/rsh +107 -38
- metadata +5 -6
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 70cf831b842cb78920de4f541751dc71b1ec87d4d2619eb813d726aa9abe844d
         | 
| 4 | 
            +
              data.tar.gz: 3a1baee44aa63c583f62485d6df477b5aeca1f04801ce2dd6c9b92a35795e27e
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 96e26e3cb1788d7aa04eea7d8aabf034ac59f904fcc808151c3297d6a896794330be4e92c372c71f96376605e9943cd734c8871373fe6c4bf07e5fb3a0bbd891
         | 
| 7 | 
            +
              data.tar.gz: ef2907de7d2758f996beb911677430b1e1e674b388ebfa726d2dbead4e8b9670dc03baa7009b534ca268064ae4feaf8c1c6dc474552c1f6a6e60a86068cbd147
         | 
    
        data/PLUGIN_GUIDE.md
    CHANGED
    
    | @@ -661,6 +661,113 @@ Planned for v4.0: | |
| 661 661 |  | 
| 662 662 | 
             
            ---
         | 
| 663 663 |  | 
| 664 | 
            +
            ## Included Plugins
         | 
| 665 | 
            +
             | 
| 666 | 
            +
            rsh comes with several useful plugins out of the box. Enable them with `:plugins "enable", "pluginname"`.
         | 
| 667 | 
            +
             | 
| 668 | 
            +
            ### venv - Virtual Environment Indicators
         | 
| 669 | 
            +
             | 
| 670 | 
            +
            Shows active Python venv, Ruby rbenv/rvm, Node nvm, and Conda environments in your prompt.
         | 
| 671 | 
            +
             | 
| 672 | 
            +
            **Features:**
         | 
| 673 | 
            +
            - Detects Python virtualenv: `(py:myenv)`
         | 
| 674 | 
            +
            - Shows Ruby rbenv/rvm versions: `(rb:3.2.0)`
         | 
| 675 | 
            +
            - Displays Node version: `(node:18.0.0)`
         | 
| 676 | 
            +
            - Shows Conda environments: `(conda:base)`
         | 
| 677 | 
            +
            - Color-coded indicators (green for Python, red for Ruby, yellow for Node, cyan for Conda)
         | 
| 678 | 
            +
             | 
| 679 | 
            +
            **Usage:** Simply enable the plugin, indicators appear automatically when in an active environment.
         | 
| 680 | 
            +
             | 
| 681 | 
            +
            ### extract - Universal Archive Handler
         | 
| 682 | 
            +
             | 
| 683 | 
            +
            Extract any archive format with a single command.
         | 
| 684 | 
            +
             | 
| 685 | 
            +
            **Features:**
         | 
| 686 | 
            +
            - Handles: `.tar.gz`, `.tar.bz2`, `.tar.xz`, `.zip`, `.rar`, `.7z`, `.gz`, `.bz2`, `.xz`, `.Z`, `.deb`, `.rpm`
         | 
| 687 | 
            +
            - Auto-detects format from extension
         | 
| 688 | 
            +
            - Success/error feedback with color
         | 
| 689 | 
            +
             | 
| 690 | 
            +
            **Commands:**
         | 
| 691 | 
            +
            - `extract file.tar.gz` - Extract any supported archive
         | 
| 692 | 
            +
             | 
| 693 | 
            +
            **Example:**
         | 
| 694 | 
            +
            ```ruby
         | 
| 695 | 
            +
            extract project.tar.gz
         | 
| 696 | 
            +
            extract backup.zip
         | 
| 697 | 
            +
            extract archive.7z
         | 
| 698 | 
            +
            ```
         | 
| 699 | 
            +
             | 
| 700 | 
            +
            ### docker - Container Integration
         | 
| 701 | 
            +
             | 
| 702 | 
            +
            Quick Docker container management with prompt indicators.
         | 
| 703 | 
            +
             | 
| 704 | 
            +
            **Features:**
         | 
| 705 | 
            +
            - Shows running container count in prompt: `[docker:3]`
         | 
| 706 | 
            +
            - Quick container management commands
         | 
| 707 | 
            +
            - Docker Compose shortcuts
         | 
| 708 | 
            +
             | 
| 709 | 
            +
            **Commands:**
         | 
| 710 | 
            +
            - `dps` - List running containers (docker ps)
         | 
| 711 | 
            +
            - `dpsa` - List all containers (docker ps -a)
         | 
| 712 | 
            +
            - `dex <container>` - Execute bash in container
         | 
| 713 | 
            +
            - `dlogs <container> [lines]` - Show container logs (default 50 lines)
         | 
| 714 | 
            +
            - `dstop` - Stop all running containers
         | 
| 715 | 
            +
            - `dclean` - Remove all stopped containers
         | 
| 716 | 
            +
            - `dcup` - Docker compose up -d
         | 
| 717 | 
            +
            - `dcdown` - Docker compose down
         | 
| 718 | 
            +
            - `dcrestart` - Docker compose restart
         | 
| 719 | 
            +
             | 
| 720 | 
            +
            **Example:**
         | 
| 721 | 
            +
            ```ruby
         | 
| 722 | 
            +
            dps                    # List running containers
         | 
| 723 | 
            +
            dex web_1              # Enter bash in web_1 container
         | 
| 724 | 
            +
            dlogs nginx 100        # Show last 100 log lines
         | 
| 725 | 
            +
            ```
         | 
| 726 | 
            +
             | 
| 727 | 
            +
            ### clipboard - Cross-Platform Clipboard
         | 
| 728 | 
            +
             | 
| 729 | 
            +
            Copy and paste text to/from system clipboard.
         | 
| 730 | 
            +
             | 
| 731 | 
            +
            **Features:**
         | 
| 732 | 
            +
            - Auto-detects clipboard tool (xclip, xsel, wl-copy/paste, pbcopy/paste, clip.exe)
         | 
| 733 | 
            +
            - Works on Linux (X11/Wayland), macOS, and Windows WSL
         | 
| 734 | 
            +
            - Copy last command, arbitrary text, or file contents
         | 
| 735 | 
            +
             | 
| 736 | 
            +
            **Commands:**
         | 
| 737 | 
            +
            - `clip [text]` - Copy text to clipboard (copies last command if no args)
         | 
| 738 | 
            +
            - `clipp` - Paste from clipboard and display
         | 
| 739 | 
            +
            - `clipf <file>` - Copy file contents to clipboard
         | 
| 740 | 
            +
             | 
| 741 | 
            +
            **Example:**
         | 
| 742 | 
            +
            ```ruby
         | 
| 743 | 
            +
            clip                   # Copy last command
         | 
| 744 | 
            +
            clip some text here    # Copy this text
         | 
| 745 | 
            +
            clipf config.yml       # Copy file contents
         | 
| 746 | 
            +
            clipp                  # Show clipboard contents
         | 
| 747 | 
            +
            ```
         | 
| 748 | 
            +
             | 
| 749 | 
            +
            ### git_prompt - Git Branch Indicator
         | 
| 750 | 
            +
             | 
| 751 | 
            +
            Shows current git branch in prompt when in a git repository.
         | 
| 752 | 
            +
             | 
| 753 | 
            +
            **Features:**
         | 
| 754 | 
            +
            - Displays current branch: `[main]`
         | 
| 755 | 
            +
            - Only shows when in a git repo
         | 
| 756 | 
            +
            - Yellow color coding
         | 
| 757 | 
            +
             | 
| 758 | 
            +
            **Usage:** Enable and branch appears automatically in prompt.
         | 
| 759 | 
            +
             | 
| 760 | 
            +
            ### command_logger - Command Logging
         | 
| 761 | 
            +
             | 
| 762 | 
            +
            Logs all commands to a file for audit/review.
         | 
| 763 | 
            +
             | 
| 764 | 
            +
            **Features:**
         | 
| 765 | 
            +
            - Logs commands with timestamp
         | 
| 766 | 
            +
            - Customizable log file location
         | 
| 767 | 
            +
            - Useful for command history analysis
         | 
| 768 | 
            +
             | 
| 769 | 
            +
            ---
         | 
| 770 | 
            +
             | 
| 664 771 | 
             
            ## Example Plugin Templates
         | 
| 665 772 |  | 
| 666 773 | 
             
            ### Minimal Plugin
         | 
    
        data/README.md
    CHANGED
    
    | @@ -136,6 +136,7 @@ Special commands: | |
| 136 136 | 
             
            * `:nick` lists all command nicks, `:gnick` lists general nicks
         | 
| 137 137 | 
             
            * `:nick -name` delete a command nick, `:gnick -name` delete a general nick
         | 
| 138 138 | 
             
            * `:history` will list the command history, while `:rmhistory` will delete the history
         | 
| 139 | 
            +
            * `:rehash` rebuilds the executable cache (useful after installing new commands)
         | 
| 139 140 | 
             
            * `:jobs` will list background jobs, `:fg [job_id]` brings jobs to foreground, `:bg [job_id]` resumes stopped jobs
         | 
| 140 141 | 
             
            * `:defun func(args) = code` defines Ruby functions callable as shell commands (persistent!)
         | 
| 141 142 | 
             
            * `:defun` lists all user-defined functions, `:defun -func` removes functions
         | 
    
        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    = "3.4. | 
| 11 | 
            +
            @version    = "3.4.7" # Added :rehash command to manually rebuild executable cache
         | 
| 12 12 |  | 
| 13 13 | 
             
            # MODULES, CLASSES AND EXTENSIONS
         | 
| 14 14 | 
             
            class String # Add coloring to strings (with escaping for Readline)
         | 
| @@ -154,7 +154,7 @@ begin # Initialization | |
| 154 154 | 
             
              @slow_command_threshold = 0             # Threshold for slow command alerts (0 = disabled)
         | 
| 155 155 | 
             
              @plugin_dir = Dir.home + '/.rsh/plugins' # Plugins directory
         | 
| 156 156 | 
             
              @plugins = []                           # Loaded plugin instances
         | 
| 157 | 
            -
              @ | 
| 157 | 
            +
              @plugin_enabled = []                    # List of enabled plugin names (whitelist)
         | 
| 158 158 | 
             
              @plugin_commands = {}                   # Commands added by plugins
         | 
| 159 159 | 
             
              @validation_rules = []                  # Custom validation rules
         | 
| 160 160 | 
             
              @completion_weights = {}                # Completion learning weights
         | 
| @@ -352,7 +352,7 @@ def getstr # A custom Readline-like function | |
| 352 352 | 
             
                # The actual printing og the command line
         | 
| 353 353 | 
             
                @c.row(@row0)
         | 
| 354 354 | 
             
                @c.clear_line
         | 
| 355 | 
            -
                print @ | 
| 355 | 
            +
                print @display_prompt
         | 
| 356 356 | 
             
                @c.clear_screen_down
         | 
| 357 357 | 
             
                row, @pos0 = @c.pos
         | 
| 358 358 | 
             
                #@history[0] = "" if @history[0].nil?
         | 
| @@ -476,11 +476,13 @@ def getstr # A custom Readline-like function | |
| 476 476 | 
             
                  @c.clear_line_after
         | 
| 477 477 | 
             
                when 'WBACK' # Delete one word to the left (Ctrl-W)
         | 
| 478 478 | 
             
                  unless @pos == @pos0
         | 
| 479 | 
            -
                     | 
| 479 | 
            +
                    # Skip over any trailing spaces first
         | 
| 480 | 
            +
                    while @pos > 0 && @history[0][@pos - 1] == " "
         | 
| 480 481 | 
             
                      @pos -= 1
         | 
| 481 482 | 
             
                      @history[0][@pos] = ""
         | 
| 482 483 | 
             
                    end
         | 
| 483 | 
            -
                     | 
| 484 | 
            +
                    # Delete the word (until we hit a space or start)
         | 
| 485 | 
            +
                    while @pos > 0 && @history[0][@pos - 1] != " "
         | 
| 484 486 | 
             
                      @pos -= 1
         | 
| 485 487 | 
             
                      @history[0][@pos] = ""
         | 
| 486 488 | 
             
                    end
         | 
| @@ -622,7 +624,7 @@ def tab(type) | |
| 622 624 | 
             
                @c_col = @pos0 + @pos
         | 
| 623 625 | 
             
                @c.clear_line
         | 
| 624 626 | 
             
                line_display = cmd_check(@history[0]).to_s
         | 
| 625 | 
            -
                print @ | 
| 627 | 
            +
                print @display_prompt + line_display
         | 
| 626 628 | 
             
                @c.col(@c_col)
         | 
| 627 629 | 
             
                return
         | 
| 628 630 | 
             
              end
         | 
| @@ -645,14 +647,22 @@ def tab(type) | |
| 645 647 | 
             
                  @tabarray = @recordings.keys
         | 
| 646 648 | 
             
                when "plugin_args"
         | 
| 647 649 | 
             
                  # Completions for :plugins command
         | 
| 648 | 
            -
                  @tabarray = %w[reload | 
| 649 | 
            -
                  # Add  | 
| 650 | 
            +
                  @tabarray = %w[reload]
         | 
| 651 | 
            +
                  # Add help for all available plugins
         | 
| 652 | 
            +
                  if Dir.exist?(@plugin_dir)
         | 
| 653 | 
            +
                    available = Dir.glob(@plugin_dir + '/*.rb').map { |f| File.basename(f, '.rb') }
         | 
| 654 | 
            +
                    @tabarray += available.map { |p| "help #{p}" }
         | 
| 655 | 
            +
                    @tabarray += available.map { |p| "info #{p}" }
         | 
| 656 | 
            +
                  end
         | 
| 657 | 
            +
                  # Add plugin names for enable/disable
         | 
| 650 658 | 
             
                  if @plugins.any?
         | 
| 651 659 | 
             
                    @tabarray += @plugins.map { |p| "disable #{p[:name]}" }
         | 
| 652 | 
            -
                    @tabarray += @plugins.map { |p| "info #{p[:name]}" }
         | 
| 653 660 | 
             
                  end
         | 
| 654 | 
            -
                   | 
| 655 | 
            -
             | 
| 661 | 
            +
                  # Show "enable" for disabled plugins (available but not enabled)
         | 
| 662 | 
            +
                  if Dir.exist?(@plugin_dir)
         | 
| 663 | 
            +
                    available = Dir.glob(@plugin_dir + '/*.rb').map { |f| File.basename(f, '.rb') }
         | 
| 664 | 
            +
                    disabled = available - @plugin_enabled
         | 
| 665 | 
            +
                    @tabarray += disabled.map { |p| "enable #{p}" } if disabled.any?
         | 
| 656 666 | 
             
                  end
         | 
| 657 667 | 
             
                when "switch"
         | 
| 658 668 | 
             
                  cmdswitch = @pretab.split(/[|, ]/).last.to_s.strip
         | 
| @@ -839,7 +849,7 @@ def tab(type) | |
| 839 849 | 
             
                    # Escape regex special characters in @tabstr for pattern matching
         | 
| 840 850 | 
             
                    escaped_tabstr = Regexp.escape(@tabstr)
         | 
| 841 851 | 
             
                    tabline   = display_choice.sub(/(.*)#{escaped_tabstr}(.*)/, '\1'.c(choice_color) + @tabstr.u.c(choice_color) + '\2'.c(choice_color))
         | 
| 842 | 
            -
                    print @ | 
| 852 | 
            +
                    print @display_prompt + line1 + tabline + line2       # Print the commandline
         | 
| 843 853 | 
             
                    @pos   = @pretab.length.to_i + tabchoice.length.to_i  # Set the position on that commandline
         | 
| 844 854 | 
             
                    @c_col = @pos0 + @pos                                 # The cursor position must include the prompt as well
         | 
| 845 855 | 
             
                    @c.col(@c_col)                                        # Set the cursor position
         | 
| @@ -1389,8 +1399,8 @@ def rshrc # Write user configuration to .rshrc (portable between machines) | |
| 1389 1399 | 
             
              conf += "@completion_learning = #{@completion_learning}\n" unless @completion_learning
         | 
| 1390 1400 | 
             
              conf.sub!(/^@completion_show_metadata.*(\n|$)/, "")
         | 
| 1391 1401 | 
             
              conf += "@completion_show_metadata = #{@completion_show_metadata}\n" if @completion_show_metadata
         | 
| 1392 | 
            -
              conf.sub!(/^@ | 
| 1393 | 
            -
              conf += "@ | 
| 1402 | 
            +
              conf.sub!(/^@plugin_enabled.*(\n|$)/, "")
         | 
| 1403 | 
            +
              conf += "@plugin_enabled = #{@plugin_enabled}\n" unless @plugin_enabled.empty?
         | 
| 1394 1404 | 
             
              conf.sub!(/^@validation_rules.*(\n|$)/, "")
         | 
| 1395 1405 | 
             
              conf += "@validation_rules = #{@validation_rules}\n" unless @validation_rules.empty?
         | 
| 1396 1406 |  | 
| @@ -1592,6 +1602,14 @@ def rmhistory # Delete history | |
| 1592 1602 | 
             
              @history = []
         | 
| 1593 1603 | 
             
              puts "History deleted."
         | 
| 1594 1604 | 
             
            end
         | 
| 1605 | 
            +
            def rehash # Force rebuild of executable cache
         | 
| 1606 | 
            +
              @exe_cache = nil
         | 
| 1607 | 
            +
              @exe_cache_path = nil
         | 
| 1608 | 
            +
              @exe_cache_time = 0
         | 
| 1609 | 
            +
              @exe_cache_paths = ""
         | 
| 1610 | 
            +
              cache_executables
         | 
| 1611 | 
            +
              puts "Executable cache rebuilt (#{@exe.length} commands cached)."
         | 
| 1612 | 
            +
            end
         | 
| 1595 1613 | 
             
            def nick(nick_str = nil)  # Define a new nick like this: `:nick "ls = ls --color"`
         | 
| 1596 1614 | 
             
              if nick_str.nil? || nick_str.empty?
         | 
| 1597 1615 | 
             
                # List all nicks
         | 
| @@ -2250,8 +2268,8 @@ def load_plugins # Load all plugins from plugin directory | |
| 2250 2268 | 
             
              plugin_files.each do |plugin_file|
         | 
| 2251 2269 | 
             
                plugin_name = File.basename(plugin_file, '.rb')
         | 
| 2252 2270 |  | 
| 2253 | 
            -
                #  | 
| 2254 | 
            -
                next  | 
| 2271 | 
            +
                # Only load if explicitly enabled (whitelist)
         | 
| 2272 | 
            +
                next unless @plugin_enabled.include?(plugin_name)
         | 
| 2255 2273 |  | 
| 2256 2274 | 
             
                begin
         | 
| 2257 2275 | 
             
                  # Load the plugin file
         | 
| @@ -2321,22 +2339,26 @@ def call_plugin_hook(hook_name, *args) # Call a lifecycle hook for all plugins | |
| 2321 2339 | 
             
            end
         | 
| 2322 2340 | 
             
            def plugins(*args) # Plugin management command
         | 
| 2323 2341 | 
             
              if args.empty?
         | 
| 2324 | 
            -
                # List all plugins
         | 
| 2325 | 
            -
                 | 
| 2326 | 
            -
             | 
| 2342 | 
            +
                # List all available plugins
         | 
| 2343 | 
            +
                available_plugins = Dir.exist?(@plugin_dir) ? Dir.glob(@plugin_dir + '/*.rb').map { |f| File.basename(f, '.rb') }.sort : []
         | 
| 2344 | 
            +
             | 
| 2345 | 
            +
                if available_plugins.empty?
         | 
| 2346 | 
            +
                  puts "\nNo plugins found"
         | 
| 2327 2347 | 
             
                  puts "Place .rb files in #{@plugin_dir}"
         | 
| 2328 2348 | 
             
                  return
         | 
| 2329 2349 | 
             
                end
         | 
| 2330 2350 |  | 
| 2331 | 
            -
                puts "\n   | 
| 2332 | 
            -
                 | 
| 2333 | 
            -
                   | 
| 2334 | 
            -
             | 
| 2335 | 
            -
             | 
| 2336 | 
            -
             | 
| 2337 | 
            -
             | 
| 2338 | 
            -
             | 
| 2339 | 
            -
             | 
| 2351 | 
            +
                puts "\n  Available Plugins:".c(@c_prompt).b
         | 
| 2352 | 
            +
                available_plugins.each do |name|
         | 
| 2353 | 
            +
                  if @plugin_enabled.include?(name)
         | 
| 2354 | 
            +
                    status = '[enabled]'.c(@c_path)
         | 
| 2355 | 
            +
                    plugin = @plugins.find { |p| p[:name] == name }
         | 
| 2356 | 
            +
                    class_info = plugin ? " (#{plugin[:class]})" : ""
         | 
| 2357 | 
            +
                  else
         | 
| 2358 | 
            +
                    status = '[disabled]'.c(196)
         | 
| 2359 | 
            +
                    class_info = ""
         | 
| 2360 | 
            +
                  end
         | 
| 2361 | 
            +
                  puts "    #{name.ljust(20)} #{status}#{class_info}"
         | 
| 2340 2362 | 
             
                end
         | 
| 2341 2363 | 
             
                puts
         | 
| 2342 2364 | 
             
              elsif args[0] == 'reload'
         | 
| @@ -2348,15 +2370,15 @@ def plugins(*args) # Plugin management command | |
| 2348 2370 | 
             
              elsif args[0] == 'enable' && args[1]
         | 
| 2349 2371 | 
             
                # Enable a plugin
         | 
| 2350 2372 | 
             
                plugin_name = args[1]
         | 
| 2351 | 
            -
                @ | 
| 2373 | 
            +
                unless @plugin_enabled.include?(plugin_name)
         | 
| 2374 | 
            +
                  @plugin_enabled << plugin_name
         | 
| 2375 | 
            +
                end
         | 
| 2352 2376 | 
             
                puts "Plugin '#{plugin_name}' enabled. Use :plugins reload to load it"
         | 
| 2353 2377 | 
             
                rshrc
         | 
| 2354 2378 | 
             
              elsif args[0] == 'disable' && args[1]
         | 
| 2355 2379 | 
             
                # Disable a plugin
         | 
| 2356 2380 | 
             
                plugin_name = args[1]
         | 
| 2357 | 
            -
                 | 
| 2358 | 
            -
                  @plugin_disabled << plugin_name
         | 
| 2359 | 
            -
                end
         | 
| 2381 | 
            +
                @plugin_enabled.delete(plugin_name)
         | 
| 2360 2382 | 
             
                @plugins.reject! { |p| p[:name] == plugin_name }
         | 
| 2361 2383 | 
             
                puts "Plugin '#{plugin_name}' disabled"
         | 
| 2362 2384 | 
             
                rshrc
         | 
| @@ -2382,6 +2404,51 @@ def plugins(*args) # Plugin management command | |
| 2382 2404 | 
             
                else
         | 
| 2383 2405 | 
             
                  puts "Plugin '#{plugin_name}' not found"
         | 
| 2384 2406 | 
             
                end
         | 
| 2407 | 
            +
              elsif args[0] == 'help' && args[1]
         | 
| 2408 | 
            +
                # Show plugin help
         | 
| 2409 | 
            +
                plugin_name = args[1]
         | 
| 2410 | 
            +
                plugin_file = "#{@plugin_dir}/#{plugin_name}.rb"
         | 
| 2411 | 
            +
             | 
| 2412 | 
            +
                if File.exist?(plugin_file)
         | 
| 2413 | 
            +
                  content = File.read(plugin_file)
         | 
| 2414 | 
            +
             | 
| 2415 | 
            +
                  # Extract help block (lines starting with # at top of file)
         | 
| 2416 | 
            +
                  help_lines = []
         | 
| 2417 | 
            +
                  in_help = true
         | 
| 2418 | 
            +
                  content.each_line do |line|
         | 
| 2419 | 
            +
                    if in_help && line =~ /^#\s*(.*)$/
         | 
| 2420 | 
            +
                      help_text = $1
         | 
| 2421 | 
            +
                      next if help_text =~ /^-+$/ # Skip separator lines
         | 
| 2422 | 
            +
                      help_lines << help_text
         | 
| 2423 | 
            +
                    elsif line.strip.empty?
         | 
| 2424 | 
            +
                      next
         | 
| 2425 | 
            +
                    else
         | 
| 2426 | 
            +
                      in_help = false
         | 
| 2427 | 
            +
                      break
         | 
| 2428 | 
            +
                    end
         | 
| 2429 | 
            +
                  end
         | 
| 2430 | 
            +
             | 
| 2431 | 
            +
                  if help_lines.any?
         | 
| 2432 | 
            +
                    puts "\n#{help_lines.join("\n")}\n"
         | 
| 2433 | 
            +
                  else
         | 
| 2434 | 
            +
                    puts "No help available for plugin '#{plugin_name}'"
         | 
| 2435 | 
            +
                  end
         | 
| 2436 | 
            +
             | 
| 2437 | 
            +
                  # Extract and show available commands
         | 
| 2438 | 
            +
                  commands = []
         | 
| 2439 | 
            +
                  content.scan(/def (\w+)\(/) { |match| commands << match[0] unless match[0] == 'initialize' }
         | 
| 2440 | 
            +
             | 
| 2441 | 
            +
                  if commands.any?
         | 
| 2442 | 
            +
                    puts "\nCommands: #{commands.join(', ')}".c(@c_nick)
         | 
| 2443 | 
            +
                    puts
         | 
| 2444 | 
            +
                  end
         | 
| 2445 | 
            +
                else
         | 
| 2446 | 
            +
                  puts "Plugin '#{plugin_name}' not found"
         | 
| 2447 | 
            +
                  puts "Available plugins:"
         | 
| 2448 | 
            +
                  Dir.glob(@plugin_dir + '/*.rb').each do |file|
         | 
| 2449 | 
            +
                    puts "  #{File.basename(file, '.rb')}"
         | 
| 2450 | 
            +
                  end
         | 
| 2451 | 
            +
                end
         | 
| 2385 2452 | 
             
              else
         | 
| 2386 2453 | 
             
                puts "Usage:"
         | 
| 2387 2454 | 
             
                puts "  :plugins              List all plugins"
         | 
| @@ -2389,6 +2456,7 @@ def plugins(*args) # Plugin management command | |
| 2389 2456 | 
             
                puts "  :plugins enable NAME  Enable a plugin"
         | 
| 2390 2457 | 
             
                puts "  :plugins disable NAME Disable a plugin"
         | 
| 2391 2458 | 
             
                puts "  :plugins info NAME    Show plugin details"
         | 
| 2459 | 
            +
                puts "  :plugins help NAME    Show plugin help/usage"
         | 
| 2392 2460 | 
             
              end
         | 
| 2393 2461 | 
             
            end
         | 
| 2394 2462 | 
             
            def validate_command(cmd) # Syntax validation before execution
         | 
| @@ -2999,7 +3067,7 @@ def load_rshrc_safe | |
| 2999 3067 | 
             
                @session_autosave = 0 unless @session_autosave.is_a?(Integer)
         | 
| 3000 3068 | 
             
                @auto_correct = false unless [@auto_correct].any? { |v| v == true || v == false }
         | 
| 3001 3069 | 
             
                @slow_command_threshold = 0 unless @slow_command_threshold.is_a?(Integer)
         | 
| 3002 | 
            -
                @ | 
| 3070 | 
            +
                @plugin_enabled = [] unless @plugin_enabled.is_a?(Array)
         | 
| 3003 3071 | 
             
                @plugins = [] unless @plugins.is_a?(Array)
         | 
| 3004 3072 | 
             
                @plugin_commands = {} unless @plugin_commands.is_a?(Hash)
         | 
| 3005 3073 | 
             
                @validation_rules = [] unless @validation_rules.is_a?(Array)
         | 
| @@ -3154,7 +3222,7 @@ def load_defaults | |
| 3154 3222 | 
             
              @session_autosave ||= 0
         | 
| 3155 3223 | 
             
              @auto_correct ||= false
         | 
| 3156 3224 | 
             
              @slow_command_threshold ||= 0
         | 
| 3157 | 
            -
              @ | 
| 3225 | 
            +
              @plugin_enabled ||= []
         | 
| 3158 3226 | 
             
              @plugins ||= []
         | 
| 3159 3227 | 
             
              @plugin_commands ||= {}
         | 
| 3160 3228 | 
             
              @validation_rules ||= []
         | 
| @@ -3293,9 +3361,10 @@ loop do | |
| 3293 3361 | 
             
                  load_plugins
         | 
| 3294 3362 | 
             
                  @plugins_loaded = true
         | 
| 3295 3363 | 
             
                end
         | 
| 3296 | 
            -
                #  | 
| 3364 | 
            +
                # Build display prompt with plugin additions (don't modify @prompt)
         | 
| 3297 3365 | 
             
                plugin_prompts = call_plugin_hook(:on_prompt)
         | 
| 3298 | 
            -
                @ | 
| 3366 | 
            +
                @display_prompt = @prompt.dup
         | 
| 3367 | 
            +
                @display_prompt += plugin_prompts.join if plugin_prompts.any?
         | 
| 3299 3368 | 
             
                # Auto-save session if enabled and interval elapsed
         | 
| 3300 3369 | 
             
                if @session_autosave && @session_autosave > 0
         | 
| 3301 3370 | 
             
                  current_time = Time.now.to_i
         | 
| @@ -3342,7 +3411,7 @@ loop do | |
| 3342 3411 | 
             
                # Move cursor to end of line and print the full command before clearing
         | 
| 3343 3412 | 
             
                @c.row(@row0)
         | 
| 3344 3413 | 
             
                @c.clear_line
         | 
| 3345 | 
            -
                print @ | 
| 3414 | 
            +
                print @display_prompt + cmd_check(@cmd)
         | 
| 3346 3415 | 
             
                print "\n"; @c.clear_screen_down
         | 
| 3347 3416 | 
             
                if @cmd == "r" # Integration with rtfm (https://github.com/isene/RTFM)
         | 
| 3348 3417 | 
             
                  t  = Time.now
         | 
| @@ -3397,7 +3466,7 @@ loop do | |
| 3397 3466 | 
             
                                          save_session load_session list_sessions delete_session rmsession
         | 
| 3398 3467 | 
             
                                          validate completion_stats completion_reset
         | 
| 3399 3468 | 
             
                                          record replay
         | 
| 3400 | 
            -
                                          history rmhistory jobs fg bg dirs help info version]
         | 
| 3469 | 
            +
                                          history rmhistory rehash jobs fg bg dirs help info version]
         | 
| 3401 3470 |  | 
| 3402 3471 | 
             
                      # Try to call as rsh method
         | 
| 3403 3472 | 
             
                      if known_commands.include?(cmd_name)
         | 
    
        metadata
    CHANGED
    
    | @@ -1,21 +1,20 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: ruby-shell
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 3.4. | 
| 4 | 
            +
              version: 3.4.7
         | 
| 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-10- | 
| 11 | 
            +
            date: 2025-10-29 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies: []
         | 
| 13 13 | 
             
            description: 'A shell written in Ruby with extensive tab completions, aliases/nicks,
         | 
| 14 14 | 
             
              history, syntax highlighting, theming, auto-cd, auto-opening files and more. UPDATE
         | 
| 15 | 
            -
              v3.4. | 
| 16 | 
            -
               | 
| 17 | 
            -
               | 
| 18 | 
            -
              persistent patterns across sessions!'
         | 
| 15 | 
            +
              v3.4.7: Added :rehash command to manually rebuild executable cache (like zsh''s
         | 
| 16 | 
            +
              rehash builtin). Plus v3.4.6: Plugin help system, 4 new plugins, plugins disabled
         | 
| 17 | 
            +
              by default!'
         | 
| 19 18 | 
             
            email: g@isene.com
         | 
| 20 19 | 
             
            executables:
         | 
| 21 20 | 
             
            - rsh
         |