def run_command(*args, &blk)
      debug "run_command"
      
      cmd, args = prep_args(*args)
      
      #p [:run_command, cmd, blk.nil?]
      
      connect if !@rye_ssh || @rye_ssh.closed?
      raise Rye::NotConnected, @rye_host unless @rye_ssh && !@rye_ssh.closed?
      
      cmd_clean = Rye.escape(@rye_safe, cmd, args)
      
      # This following is the command we'll actually execute. cmd_clean
      # can be used for logging, otherwise the output is confusing.
      cmd_internal = prepend_env(cmd_clean)
      
      # Add the current working directory before the command if supplied. 
      # The command will otherwise run in the user's home directory.
      if @rye_current_working_directory
        cwd = Rye.escape(@rye_safe, 'cd', @rye_current_working_directory)
        cmd_internal = '(%s; %s)' % [cwd, cmd_internal]
      end
      
      # ditto (same explanation as cwd)
      if @rye_current_umask
        cwd = Rye.escape(@rye_safe, 'umask', @rye_current_umask)
        cmd_internal = [cwd, cmd_internal].join(' && ')
      end
      
      ## NOTE: Do not raise a CommandNotFound exception in this method.
      # We want it to be possible to define methods to a single instance
      # of Rye::Box. i.e. def rbox.rm()...
      # can? returns the methods in Rye::Cmd so it would incorrectly
      # return false. We could use self.respond_to? but it's possible
      # to get a name collision. I could write a work around but I think
      # this is good enough for now. 
      ## raise Rye::CommandNotFound unless self.can?(cmd)
      
      begin
        debug "COMMAND: #{cmd_internal}"

        if !@rye_quiet && @rye_pre_command_hook.is_a?(Proc)
          @rye_pre_command_hook.call(cmd_clean, user, host, nickname) 
        end
        
        rap = Rye::Rap.new(self)
        rap.cmd = cmd_clean
        
        channel = net_ssh_exec!(cmd_internal, &blk)
        channel[:stderr].position = 0
        channel[:stdout].position = 0
        
        if channel[:exception]
          rap = channel[:exception].rap
        else
          rap.add_stdout(channel[:stdout].read || '')
          rap.add_stderr(channel[:stderr].read || '')
          rap.add_exit_status(channel[:exit_status])
          rap.exit_signal = channel[:exit_signal]
        end
        
        debug "RESULT: %s " % [rap.inspect]
        
        # It seems a convention for various commands to return -1
        # when something only mildly concerning happens. (ls even 
        # returns -1 for apparently no reason sometimes). Anyway,
        # the real errors are the ones that are greater than zero.
        raise Rye::Err.new(rap) if rap.exit_status != 0
        
      rescue Exception => ex
        return rap if @rye_quiet
        choice = nil
        @rye_exception_hook.each_pair do |klass,act|
          next unless ex.kind_of? klass
          choice = act.call(ex, cmd_clean, user, host, nickname)
          break
        end
        if choice == :retry
          retry
        elsif choice == :skip
          # do nothing
        elsif choice == :interactive && !@rye_shell
          @rye_shell = true
          previous_state = @rye_sudo
          disable_sudo
          bash
          @rye_sudo = previous_state
          @rye_shell = false
        elsif !ex.is_a?(Interrupt)
          raise ex, ex.message
        end
      end
      
      if !@rye_quiet && @rye_post_command_hook.is_a?(Proc)
        @rye_post_command_hook.call(rap)
      end
      
      rap
    end
def run_command(*args, &blk)
      debug "run_command"
      
      cmd, args = prep_args(*args)
      
      #p [:run_command, cmd, blk.nil?]
      
      connect if !@rye_ssh || @rye_ssh.closed?
      raise Rye::NotConnected, @rye_host unless @rye_ssh && !@rye_ssh.closed?
      
      cmd_clean = Rye.escape(@rye_safe, cmd, args)
      
      # This following is the command we'll actually execute. cmd_clean
      # can be used for logging, otherwise the output is confusing.
      cmd_internal = prepend_env(cmd_clean)
      
      # Add the current working directory before the command if supplied. 
      # The command will otherwise run in the user's home directory.
      if @rye_current_working_directory
        cwd = Rye.escape(@rye_safe, 'cd', @rye_current_working_directory)
        cmd_internal = '(%s; %s)' % [cwd, cmd_internal]
      end
      
      # ditto (same explanation as cwd)
      if @rye_current_umask
        cwd = Rye.escape(@rye_safe, 'umask', @rye_current_umask)
        cmd_internal = [cwd, cmd_internal].join(' && ')
      end
      
      ## NOTE: Do not raise a CommandNotFound exception in this method.
      # We want it to be possible to define methods to a single instance
      # of Rye::Box. i.e. def rbox.rm()...
      # can? returns the methods in Rye::Cmd so it would incorrectly
      # return false. We could use self.respond_to? but it's possible
      # to get a name collision. I could write a work around but I think
      # this is good enough for now. 
      ## raise Rye::CommandNotFound unless self.can?(cmd)
      
      begin
        debug "COMMAND: #{cmd_internal}"

        if !@rye_quiet && @rye_pre_command_hook.is_a?(Proc)
          @rye_pre_command_hook.call(cmd_clean, user, host, nickname) 
        end
        
        rap = Rye::Rap.new(self)
        rap.cmd = cmd_clean
        
        channel = net_ssh_exec!(cmd_internal, &blk)
        channel[:stderr].position = 0
        channel[:stdout].position = 0
        
        if channel[:exception]
          rap = channel[:exception].rap
        else
          rap.add_stdout(channel[:stdout].read || '')
          rap.add_stderr(channel[:stderr].read || '')
          rap.add_exit_status(channel[:exit_status])
          rap.exit_signal = channel[:exit_signal]
        end
        
        debug "RESULT: %s " % [rap.inspect]
        
        # It seems a convention for various commands to return -1
        # when something only mildly concerning happens. (ls even 
        # returns -1 for apparently no reason sometimes). Anyway,
        # the real errors are the ones that are greater than zero.
        raise Rye::Err.new(rap) if rap.exit_status != 0
        
      rescue Exception => ex
        return rap if @rye_quiet
        choice = nil
        @rye_exception_hook.each_pair do |klass,act|
          next unless ex.kind_of? klass
          choice = act.call(ex, cmd_clean, user, host, nickname)
          break
        end
        if choice == :retry
          retry
        elsif choice == :skip
          # do nothing
        elsif choice == :interactive && !@rye_shell
          @rye_shell = true
          previous_state = @rye_sudo
          disable_sudo
          bash
          @rye_sudo = previous_state
          @rye_shell = false
        elsif !ex.is_a?(Interrupt)
          raise ex, ex.message
        end
      end
      
      if !@rye_quiet && @rye_post_command_hook.is_a?(Proc)
        @rye_post_command_hook.call(rap)
      end
      
      rap
    end
Execute a command over SSH

 * +args+ is a command name and list of arguments.
 The command name is the literal name of the command
 that will be executed in the remote shell. The arguments
 will be thoroughly escaped and passed to the command.

     rbox = Rye::Box.new
     rbox.ls :l, 'arg1', 'arg2'

 is equivalent to

     $ ls -l 'arg1' 'arg2'

 This method will try to connect to the host automatically
 but if it fails it will raise a Rye::NotConnected exception.