ruby-shell 3.4.4 → 3.4.6
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 +21 -0
- data/bin/rsh +119 -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: 8180bf48f336b9667d34fc01d5df5ddf7a3412ff5f35cfc443d10846a81f6686
         | 
| 4 | 
            +
              data.tar.gz: acd063a28be3a5bfe7ccb9ec6623ddb7e061deba44806725c1b76781fcf45acb
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 05d07658f8356edad6da19da615170e2423785fc4a3f9074cfb5b9ef3a7f602b3f109172cb6ba799f9b2bc0bbc7363a9c0ff29b4d9d27cfebb05b2883156aad6
         | 
| 7 | 
            +
              data.tar.gz: 6e1d7c8d6c16c0ce6f2d7cf67a5229b3a31f3e0d096438fd59877c3647dd5f8b1530ff0b0c46cd95bd377d25663e79e7f0cbef8dc8599f61cb281a395c54bc94
         | 
    
        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
    
    | @@ -476,6 +476,27 @@ Also, a special variable for better LS_COLOR setup: | |
| 476 476 | 
             
            ```
         | 
| 477 477 | 
             
            Point `@lscolors` to a file that sets your LS_COLORS variable. Use [my extended LS_COLORS setup](https://github.com/isene/LS_COLORS) to make this really fancy.
         | 
| 478 478 |  | 
| 479 | 
            +
            ### Directory Colors in Prompt
         | 
| 480 | 
            +
             | 
| 481 | 
            +
            **rsh is fully LS_COLORS compliant** - both tab completion and prompt paths use LS_COLORS for consistent theming.
         | 
| 482 | 
            +
             | 
| 483 | 
            +
            You can override directory colors in the prompt using pattern matching (like RTFM's @topmatch):
         | 
| 484 | 
            +
            ```ruby
         | 
| 485 | 
            +
            @dir_colors = [
         | 
| 486 | 
            +
              ["PassionFruit", 171],  # Paths containing "PassionFruit" -> magenta
         | 
| 487 | 
            +
              ["Dualog", 72],         # Paths containing "Dualog" -> cyan
         | 
| 488 | 
            +
              ["/G", 172],            # Paths containing "/G" -> orange
         | 
| 489 | 
            +
            ]
         | 
| 490 | 
            +
            ```
         | 
| 491 | 
            +
             | 
| 492 | 
            +
            How it works:
         | 
| 493 | 
            +
            - Array of `[pattern, color]` pairs
         | 
| 494 | 
            +
            - First matching pattern wins (uses Ruby's `include?` method)
         | 
| 495 | 
            +
            - If no pattern matches, uses LS_COLORS 'di' value (your configured directory color)
         | 
| 496 | 
            +
            - Pattern matching is simple substring matching: "/G" matches "/home/user/Main/G/..."
         | 
| 497 | 
            +
             | 
| 498 | 
            +
            This lets you visually distinguish different project directories at a glance in your prompt.
         | 
| 499 | 
            +
             | 
| 479 500 | 
             
            You can add any Ruby code to your .rshrc.
         | 
| 480 501 |  | 
| 481 502 | 
             
            # Enter the world of Ruby
         | 
    
        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.6" # Plugin help system, 4 new plugins, plugins disabled by default, Ctrl-W fix
         | 
| 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
         | 
| @@ -1262,6 +1272,18 @@ def get_file_color(filename) # Get color for a file based on LS_COLORS | |
| 1262 1272 | 
             
              file_color = 7 if file_color == 0  # 0 means "reset to default" in LS_COLORS
         | 
| 1263 1273 | 
             
              file_color
         | 
| 1264 1274 | 
             
            end
         | 
| 1275 | 
            +
            def get_dir_color(path) # Get color for directory path in prompt
         | 
| 1276 | 
            +
              # Check pattern matches first (like RTFM's @topmatch)
         | 
| 1277 | 
            +
              # Set @dir_colors in .rshrc as array of [pattern, color] pairs
         | 
| 1278 | 
            +
              # Example: @dir_colors = [["PassionFruit", 171], ["Dualog", 72], ["/G", 172], ["", 33]]
         | 
| 1279 | 
            +
              if @dir_colors && @dir_colors.is_a?(Array)
         | 
| 1280 | 
            +
                match = @dir_colors.find { |pattern, _| pattern.empty? || path.include?(pattern) }
         | 
| 1281 | 
            +
                return match.last if match
         | 
| 1282 | 
            +
              end
         | 
| 1283 | 
            +
             | 
| 1284 | 
            +
              # Fall back to LS_COLORS directory color
         | 
| 1285 | 
            +
              @ls_colors['di'] || 33
         | 
| 1286 | 
            +
            end
         | 
| 1265 1287 | 
             
            def format_tab_item(item, show_metadata: false) # Format tab item with color and optional metadata
         | 
| 1266 1288 | 
             
              color = get_file_color(item)
         | 
| 1267 1289 | 
             
              formatted = item
         | 
| @@ -1335,7 +1357,12 @@ def cmd_check(str) # Check if each element on the readline matches commands, nic | |
| 1335 1357 | 
             
                  # Color bookmarks (after commands and nicks)
         | 
| 1336 1358 | 
             
                  el.c(@c_bookmark)
         | 
| 1337 1359 | 
             
                elsif File.exist?(clean_el)
         | 
| 1338 | 
            -
                   | 
| 1360 | 
            +
                  # Use directory color matching for directories, @c_path for files
         | 
| 1361 | 
            +
                  if File.directory?(clean_el)
         | 
| 1362 | 
            +
                    el.c(get_dir_color(File.expand_path(clean_el)))
         | 
| 1363 | 
            +
                  else
         | 
| 1364 | 
            +
                    el.c(@c_path)
         | 
| 1365 | 
            +
                  end
         | 
| 1339 1366 | 
             
                elsif el[0] == "-"
         | 
| 1340 1367 | 
             
                  el.c(@c_switch)
         | 
| 1341 1368 | 
             
                else
         | 
| @@ -1372,8 +1399,8 @@ def rshrc # Write user configuration to .rshrc (portable between machines) | |
| 1372 1399 | 
             
              conf += "@completion_learning = #{@completion_learning}\n" unless @completion_learning
         | 
| 1373 1400 | 
             
              conf.sub!(/^@completion_show_metadata.*(\n|$)/, "")
         | 
| 1374 1401 | 
             
              conf += "@completion_show_metadata = #{@completion_show_metadata}\n" if @completion_show_metadata
         | 
| 1375 | 
            -
              conf.sub!(/^@ | 
| 1376 | 
            -
              conf += "@ | 
| 1402 | 
            +
              conf.sub!(/^@plugin_enabled.*(\n|$)/, "")
         | 
| 1403 | 
            +
              conf += "@plugin_enabled = #{@plugin_enabled}\n" unless @plugin_enabled.empty?
         | 
| 1377 1404 | 
             
              conf.sub!(/^@validation_rules.*(\n|$)/, "")
         | 
| 1378 1405 | 
             
              conf += "@validation_rules = #{@validation_rules}\n" unless @validation_rules.empty?
         | 
| 1379 1406 |  | 
| @@ -2233,8 +2260,8 @@ def load_plugins # Load all plugins from plugin directory | |
| 2233 2260 | 
             
              plugin_files.each do |plugin_file|
         | 
| 2234 2261 | 
             
                plugin_name = File.basename(plugin_file, '.rb')
         | 
| 2235 2262 |  | 
| 2236 | 
            -
                #  | 
| 2237 | 
            -
                next  | 
| 2263 | 
            +
                # Only load if explicitly enabled (whitelist)
         | 
| 2264 | 
            +
                next unless @plugin_enabled.include?(plugin_name)
         | 
| 2238 2265 |  | 
| 2239 2266 | 
             
                begin
         | 
| 2240 2267 | 
             
                  # Load the plugin file
         | 
| @@ -2304,22 +2331,26 @@ def call_plugin_hook(hook_name, *args) # Call a lifecycle hook for all plugins | |
| 2304 2331 | 
             
            end
         | 
| 2305 2332 | 
             
            def plugins(*args) # Plugin management command
         | 
| 2306 2333 | 
             
              if args.empty?
         | 
| 2307 | 
            -
                # List all plugins
         | 
| 2308 | 
            -
                 | 
| 2309 | 
            -
             | 
| 2334 | 
            +
                # List all available plugins
         | 
| 2335 | 
            +
                available_plugins = Dir.exist?(@plugin_dir) ? Dir.glob(@plugin_dir + '/*.rb').map { |f| File.basename(f, '.rb') }.sort : []
         | 
| 2336 | 
            +
             | 
| 2337 | 
            +
                if available_plugins.empty?
         | 
| 2338 | 
            +
                  puts "\nNo plugins found"
         | 
| 2310 2339 | 
             
                  puts "Place .rb files in #{@plugin_dir}"
         | 
| 2311 2340 | 
             
                  return
         | 
| 2312 2341 | 
             
                end
         | 
| 2313 2342 |  | 
| 2314 | 
            -
                puts "\n   | 
| 2315 | 
            -
                 | 
| 2316 | 
            -
                   | 
| 2317 | 
            -
             | 
| 2318 | 
            -
             | 
| 2319 | 
            -
             | 
| 2320 | 
            -
             | 
| 2321 | 
            -
             | 
| 2322 | 
            -
             | 
| 2343 | 
            +
                puts "\n  Available Plugins:".c(@c_prompt).b
         | 
| 2344 | 
            +
                available_plugins.each do |name|
         | 
| 2345 | 
            +
                  if @plugin_enabled.include?(name)
         | 
| 2346 | 
            +
                    status = '[enabled]'.c(@c_path)
         | 
| 2347 | 
            +
                    plugin = @plugins.find { |p| p[:name] == name }
         | 
| 2348 | 
            +
                    class_info = plugin ? " (#{plugin[:class]})" : ""
         | 
| 2349 | 
            +
                  else
         | 
| 2350 | 
            +
                    status = '[disabled]'.c(196)
         | 
| 2351 | 
            +
                    class_info = ""
         | 
| 2352 | 
            +
                  end
         | 
| 2353 | 
            +
                  puts "    #{name.ljust(20)} #{status}#{class_info}"
         | 
| 2323 2354 | 
             
                end
         | 
| 2324 2355 | 
             
                puts
         | 
| 2325 2356 | 
             
              elsif args[0] == 'reload'
         | 
| @@ -2331,15 +2362,15 @@ def plugins(*args) # Plugin management command | |
| 2331 2362 | 
             
              elsif args[0] == 'enable' && args[1]
         | 
| 2332 2363 | 
             
                # Enable a plugin
         | 
| 2333 2364 | 
             
                plugin_name = args[1]
         | 
| 2334 | 
            -
                @ | 
| 2365 | 
            +
                unless @plugin_enabled.include?(plugin_name)
         | 
| 2366 | 
            +
                  @plugin_enabled << plugin_name
         | 
| 2367 | 
            +
                end
         | 
| 2335 2368 | 
             
                puts "Plugin '#{plugin_name}' enabled. Use :plugins reload to load it"
         | 
| 2336 2369 | 
             
                rshrc
         | 
| 2337 2370 | 
             
              elsif args[0] == 'disable' && args[1]
         | 
| 2338 2371 | 
             
                # Disable a plugin
         | 
| 2339 2372 | 
             
                plugin_name = args[1]
         | 
| 2340 | 
            -
                 | 
| 2341 | 
            -
                  @plugin_disabled << plugin_name
         | 
| 2342 | 
            -
                end
         | 
| 2373 | 
            +
                @plugin_enabled.delete(plugin_name)
         | 
| 2343 2374 | 
             
                @plugins.reject! { |p| p[:name] == plugin_name }
         | 
| 2344 2375 | 
             
                puts "Plugin '#{plugin_name}' disabled"
         | 
| 2345 2376 | 
             
                rshrc
         | 
| @@ -2365,6 +2396,51 @@ def plugins(*args) # Plugin management command | |
| 2365 2396 | 
             
                else
         | 
| 2366 2397 | 
             
                  puts "Plugin '#{plugin_name}' not found"
         | 
| 2367 2398 | 
             
                end
         | 
| 2399 | 
            +
              elsif args[0] == 'help' && args[1]
         | 
| 2400 | 
            +
                # Show plugin help
         | 
| 2401 | 
            +
                plugin_name = args[1]
         | 
| 2402 | 
            +
                plugin_file = "#{@plugin_dir}/#{plugin_name}.rb"
         | 
| 2403 | 
            +
             | 
| 2404 | 
            +
                if File.exist?(plugin_file)
         | 
| 2405 | 
            +
                  content = File.read(plugin_file)
         | 
| 2406 | 
            +
             | 
| 2407 | 
            +
                  # Extract help block (lines starting with # at top of file)
         | 
| 2408 | 
            +
                  help_lines = []
         | 
| 2409 | 
            +
                  in_help = true
         | 
| 2410 | 
            +
                  content.each_line do |line|
         | 
| 2411 | 
            +
                    if in_help && line =~ /^#\s*(.*)$/
         | 
| 2412 | 
            +
                      help_text = $1
         | 
| 2413 | 
            +
                      next if help_text =~ /^-+$/ # Skip separator lines
         | 
| 2414 | 
            +
                      help_lines << help_text
         | 
| 2415 | 
            +
                    elsif line.strip.empty?
         | 
| 2416 | 
            +
                      next
         | 
| 2417 | 
            +
                    else
         | 
| 2418 | 
            +
                      in_help = false
         | 
| 2419 | 
            +
                      break
         | 
| 2420 | 
            +
                    end
         | 
| 2421 | 
            +
                  end
         | 
| 2422 | 
            +
             | 
| 2423 | 
            +
                  if help_lines.any?
         | 
| 2424 | 
            +
                    puts "\n#{help_lines.join("\n")}\n"
         | 
| 2425 | 
            +
                  else
         | 
| 2426 | 
            +
                    puts "No help available for plugin '#{plugin_name}'"
         | 
| 2427 | 
            +
                  end
         | 
| 2428 | 
            +
             | 
| 2429 | 
            +
                  # Extract and show available commands
         | 
| 2430 | 
            +
                  commands = []
         | 
| 2431 | 
            +
                  content.scan(/def (\w+)\(/) { |match| commands << match[0] unless match[0] == 'initialize' }
         | 
| 2432 | 
            +
             | 
| 2433 | 
            +
                  if commands.any?
         | 
| 2434 | 
            +
                    puts "\nCommands: #{commands.join(', ')}".c(@c_nick)
         | 
| 2435 | 
            +
                    puts
         | 
| 2436 | 
            +
                  end
         | 
| 2437 | 
            +
                else
         | 
| 2438 | 
            +
                  puts "Plugin '#{plugin_name}' not found"
         | 
| 2439 | 
            +
                  puts "Available plugins:"
         | 
| 2440 | 
            +
                  Dir.glob(@plugin_dir + '/*.rb').each do |file|
         | 
| 2441 | 
            +
                    puts "  #{File.basename(file, '.rb')}"
         | 
| 2442 | 
            +
                  end
         | 
| 2443 | 
            +
                end
         | 
| 2368 2444 | 
             
              else
         | 
| 2369 2445 | 
             
                puts "Usage:"
         | 
| 2370 2446 | 
             
                puts "  :plugins              List all plugins"
         | 
| @@ -2372,6 +2448,7 @@ def plugins(*args) # Plugin management command | |
| 2372 2448 | 
             
                puts "  :plugins enable NAME  Enable a plugin"
         | 
| 2373 2449 | 
             
                puts "  :plugins disable NAME Disable a plugin"
         | 
| 2374 2450 | 
             
                puts "  :plugins info NAME    Show plugin details"
         | 
| 2451 | 
            +
                puts "  :plugins help NAME    Show plugin help/usage"
         | 
| 2375 2452 | 
             
              end
         | 
| 2376 2453 | 
             
            end
         | 
| 2377 2454 | 
             
            def validate_command(cmd) # Syntax validation before execution
         | 
| @@ -2982,7 +3059,7 @@ def load_rshrc_safe | |
| 2982 3059 | 
             
                @session_autosave = 0 unless @session_autosave.is_a?(Integer)
         | 
| 2983 3060 | 
             
                @auto_correct = false unless [@auto_correct].any? { |v| v == true || v == false }
         | 
| 2984 3061 | 
             
                @slow_command_threshold = 0 unless @slow_command_threshold.is_a?(Integer)
         | 
| 2985 | 
            -
                @ | 
| 3062 | 
            +
                @plugin_enabled = [] unless @plugin_enabled.is_a?(Array)
         | 
| 2986 3063 | 
             
                @plugins = [] unless @plugins.is_a?(Array)
         | 
| 2987 3064 | 
             
                @plugin_commands = {} unless @plugin_commands.is_a?(Hash)
         | 
| 2988 3065 | 
             
                @validation_rules = [] unless @validation_rules.is_a?(Array)
         | 
| @@ -3137,7 +3214,7 @@ def load_defaults | |
| 3137 3214 | 
             
              @session_autosave ||= 0
         | 
| 3138 3215 | 
             
              @auto_correct ||= false
         | 
| 3139 3216 | 
             
              @slow_command_threshold ||= 0
         | 
| 3140 | 
            -
              @ | 
| 3217 | 
            +
              @plugin_enabled ||= []
         | 
| 3141 3218 | 
             
              @plugins ||= []
         | 
| 3142 3219 | 
             
              @plugin_commands ||= {}
         | 
| 3143 3220 | 
             
              @validation_rules ||= []
         | 
| @@ -3211,6 +3288,8 @@ begin # Load .rshrc and populate @history | |
| 3211 3288 | 
             
                exit
         | 
| 3212 3289 | 
             
              end
         | 
| 3213 3290 | 
             
              firstrun unless File.exist?(Dir.home+'/.rshrc') # Initial loading - to get history
         | 
| 3291 | 
            +
              # Initialize @ls_colors with defaults so get_dir_color() works in .rshrc prompt
         | 
| 3292 | 
            +
              @ls_colors = { 'di' => 33, 'ex' => 2, 'ln' => 14, 'fi' => 7 }
         | 
| 3214 3293 | 
             
              load_rshrc_safe
         | 
| 3215 3294 | 
             
              migrate_to_split_config  # Migrate from old format if needed (v3.4.3+)
         | 
| 3216 3295 | 
             
              # Load login shell files if rsh is running as login shell
         | 
| @@ -3235,6 +3314,7 @@ begin # Load .rshrc and populate @history | |
| 3235 3314 | 
             
              ENV["TERM"]  = "rxvt-unicode-256color"
         | 
| 3236 3315 | 
             
              ENV["PATH"]  ? ENV["PATH"] += ":" : ENV["PATH"] = ""
         | 
| 3237 3316 | 
             
              ENV["PATH"] += "/home/#{@user}/bin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
         | 
| 3317 | 
            +
              # Load actual LS_COLORS from file (now that @lscolors is set from .rshrc)
         | 
| 3238 3318 | 
             
              if @lscolors and File.exist?(@lscolors)
         | 
| 3239 3319 | 
             
                ls = File.read(@lscolors)
         | 
| 3240 3320 | 
             
                ls.sub!(/export.*/, '')
         | 
| @@ -3273,9 +3353,10 @@ loop do | |
| 3273 3353 | 
             
                  load_plugins
         | 
| 3274 3354 | 
             
                  @plugins_loaded = true
         | 
| 3275 3355 | 
             
                end
         | 
| 3276 | 
            -
                #  | 
| 3356 | 
            +
                # Build display prompt with plugin additions (don't modify @prompt)
         | 
| 3277 3357 | 
             
                plugin_prompts = call_plugin_hook(:on_prompt)
         | 
| 3278 | 
            -
                @ | 
| 3358 | 
            +
                @display_prompt = @prompt.dup
         | 
| 3359 | 
            +
                @display_prompt += plugin_prompts.join if plugin_prompts.any?
         | 
| 3279 3360 | 
             
                # Auto-save session if enabled and interval elapsed
         | 
| 3280 3361 | 
             
                if @session_autosave && @session_autosave > 0
         | 
| 3281 3362 | 
             
                  current_time = Time.now.to_i
         | 
| @@ -3322,7 +3403,7 @@ loop do | |
| 3322 3403 | 
             
                # Move cursor to end of line and print the full command before clearing
         | 
| 3323 3404 | 
             
                @c.row(@row0)
         | 
| 3324 3405 | 
             
                @c.clear_line
         | 
| 3325 | 
            -
                print @ | 
| 3406 | 
            +
                print @display_prompt + cmd_check(@cmd)
         | 
| 3326 3407 | 
             
                print "\n"; @c.clear_screen_down
         | 
| 3327 3408 | 
             
                if @cmd == "r" # Integration with rtfm (https://github.com/isene/RTFM)
         | 
| 3328 3409 | 
             
                  t  = Time.now
         | 
    
        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.6
         | 
| 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-26 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 | 
            -
              parametrized nicks, Ctrl-G editing, validation rules, shell scripts!'
         | 
| 15 | 
            +
              v3.4.6: PLUGIN SYSTEM ENHANCED - Plugin help system, 4 new plugins (venv/extract/docker/clipboard),
         | 
| 16 | 
            +
              plugins disabled by default. Plus v3.4.5: Full LS_COLORS compliance with pattern-based
         | 
| 17 | 
            +
              directory coloring (@dir_colors like RTFM''s @topmatch)!'
         | 
| 19 18 | 
             
            email: g@isene.com
         | 
| 20 19 | 
             
            executables:
         | 
| 21 20 | 
             
            - rsh
         |