terminalwire 0.1.0 → 0.1.1
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/LICENSE.txt +1 -1
 - data/README.md +14 -6
 - data/examples/exec/localrails +2 -0
 - data/exe/terminalwire-exec +9 -0
 - data/lib/generators/terminalwire/install/USAGE +9 -0
 - data/lib/generators/terminalwire/install/install_generator.rb +37 -0
 - data/lib/generators/terminalwire/install/templates/application_terminal.rb.tt +36 -0
 - data/lib/generators/terminalwire/install/templates/bin/terminalwire +2 -0
 - data/lib/terminalwire/client/binary.rb +35 -0
 - data/lib/terminalwire/client.rb +213 -0
 - data/lib/terminalwire/server.rb +239 -0
 - data/lib/terminalwire/thor.rb +43 -0
 - data/lib/terminalwire/transport.rb +61 -0
 - data/lib/terminalwire/version.rb +1 -1
 - data/lib/terminalwire.rb +1 -547
 - metadata +59 -5
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 9fec6bb413813bdaa761403bca8fcaebb92b884a448cfe3b5cba7fc56e3589b0
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 319498815fe666a7266f1dcfd2648af1e15477f7f42f22cd8661f2c42bda45c9
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: bbc8104d13cd4acf420c0c5873b4f1df0749ea94768c91a9ceb6100d34c9fe6da0cd62bf4bb717a5c5e5fdc4f3d7395db0a0cd44b5d88cb1e18006dab6c3f791
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: daeeb21577a26536a4daad6c61b07f1b6a96a23ea52adfbc4eb25881b2948a1010f81ffc6b4958eb92befc0bae348b18cbecf4aeecea813c0b84b18ea0e33ace
         
     | 
    
        data/LICENSE.txt
    CHANGED
    
    | 
         @@ -1 +1 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            Copyright (c) 2024 Brad Gessler. Email brad@terminalwire.com to discuss  
     | 
| 
      
 1 
     | 
    
         
            +
            Copyright (c) 2024 Brad Gessler. Email brad@terminalwire.com to discuss licensing.
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -1,8 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # Terminalwire
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
             
     | 
| 
       4 
     | 
    
         
            -
             
     | 
| 
       5 
     | 
    
         
            -
            Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/terminalwire`. To experiment with that code, run `bin/console` for an interactive prompt.
         
     | 
| 
      
 3 
     | 
    
         
            +
            Unlike most command-line tools for web services that require an API, Terminalwire streams terminal I/O between a web server and client over WebSockets. This means you can use your preferred command-line parser within your favorite web server framework to deliver a delightful CLI experience to your users.
         
     | 
| 
       6 
4 
     | 
    
         | 
| 
       7 
5 
     | 
    
         
             
            ## Installation
         
     | 
| 
       8 
6 
     | 
    
         | 
| 
         @@ -14,9 +12,19 @@ If bundler is not being used to manage dependencies, install the gem by executin 
     | 
|
| 
       14 
12 
     | 
    
         | 
| 
       15 
13 
     | 
    
         
             
                $ gem install terminalwire
         
     | 
| 
       16 
14 
     | 
    
         | 
| 
       17 
     | 
    
         
            -
            ##  
     | 
| 
      
 15 
     | 
    
         
            +
            ## Rails
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            Run the intallation command:
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                $ rails g terminalwire:install my-app
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            This generates the `./bin/my-app` file. Run it to verify that it connects to the server.
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                $ bin/my-app
         
     | 
| 
      
 24 
     | 
    
         
            +
                Commands:
         
     | 
| 
      
 25 
     | 
    
         
            +
                  my-app help [COMMAND]  # Describe available commands or one specific command
         
     | 
| 
       18 
26 
     | 
    
         | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
      
 27 
     | 
    
         
            +
            To edit the command-line, open `./app/cli/main_cli.rb` and make changes to the `MainCLI` class.
         
     | 
| 
       20 
28 
     | 
    
         | 
| 
       21 
29 
     | 
    
         
             
            ## Development
         
     | 
| 
       22 
30 
     | 
    
         | 
| 
         @@ -30,7 +38,7 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/termin 
     | 
|
| 
       30 
38 
     | 
    
         | 
| 
       31 
39 
     | 
    
         
             
            ## License
         
     | 
| 
       32 
40 
     | 
    
         | 
| 
       33 
     | 
    
         
            -
            The gem is available as a propietary license. Email brad@terminalwire.com to discuss  
     | 
| 
      
 41 
     | 
    
         
            +
            The gem is available as a propietary license. Email brad@terminalwire.com to discuss licensing.
         
     | 
| 
       34 
42 
     | 
    
         | 
| 
       35 
43 
     | 
    
         
             
            ## Code of Conduct
         
     | 
| 
       36 
44 
     | 
    
         | 
| 
         @@ -0,0 +1,37 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "bundler"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            class Terminalwire::InstallGenerator < Rails::Generators::Base
         
     | 
| 
      
 4 
     | 
    
         
            +
              source_root File.expand_path("templates", __dir__)
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              argument :binary_name, type: :string, required: true, banner: "binary_name"
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              def create_terminal_files
         
     | 
| 
      
 9 
     | 
    
         
            +
                template "application_terminal.rb.tt", Rails.root.join("app/terminal/application_terminal.rb")
         
     | 
| 
      
 10 
     | 
    
         
            +
              end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              def create_binary_files
         
     | 
| 
      
 13 
     | 
    
         
            +
                copy_file "bin/terminalwire", binary_path
         
     | 
| 
      
 14 
     | 
    
         
            +
                chmod binary_path, 0755, verbose: false
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
              def add_route
         
     | 
| 
      
 18 
     | 
    
         
            +
                route <<~ROUTE
         
     | 
| 
      
 19 
     | 
    
         
            +
                  match "/terminal",
         
     | 
| 
      
 20 
     | 
    
         
            +
                    to: Terminalwire::WebSocket::ThorServer.new(ApplicationTerminal),
         
     | 
| 
      
 21 
     | 
    
         
            +
                    via: [:get, :connect]
         
     | 
| 
      
 22 
     | 
    
         
            +
                ROUTE
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
              def print_post_install_message
         
     | 
| 
      
 26 
     | 
    
         
            +
                say ""
         
     | 
| 
      
 27 
     | 
    
         
            +
                say "Terminalwire has been successfully installed!", :green
         
     | 
| 
      
 28 
     | 
    
         
            +
                say "Run `#{binary_path.relative_path_from(Rails.root)}` to verify everything is in working order. For support visit https://terminalwire.com."
         
     | 
| 
      
 29 
     | 
    
         
            +
                say ""
         
     | 
| 
      
 30 
     | 
    
         
            +
              end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
              private
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
              def binary_path
         
     | 
| 
      
 35 
     | 
    
         
            +
                Rails.root.join("bin", binary_name)
         
     | 
| 
      
 36 
     | 
    
         
            +
              end
         
     | 
| 
      
 37 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,36 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Learn how to use Thor at http://whatisthor.com.
         
     | 
| 
      
 2 
     | 
    
         
            +
            class ApplicationTerminal < Thor
         
     | 
| 
      
 3 
     | 
    
         
            +
              include Terminalwire::Thor
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              def self.basename = "<%= binary_name %>"
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              desc "hello NAME", "say hello to NAME"
         
     | 
| 
      
 8 
     | 
    
         
            +
              def hello(name)
         
     | 
| 
      
 9 
     | 
    
         
            +
                puts "Hello #{name}"
         
     | 
| 
      
 10 
     | 
    
         
            +
              end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              desc "login", "Login to your account"
         
     | 
| 
      
 13 
     | 
    
         
            +
              def login
         
     | 
| 
      
 14 
     | 
    
         
            +
                print "Email: "
         
     | 
| 
      
 15 
     | 
    
         
            +
                email = gets
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                print "Password: "
         
     | 
| 
      
 18 
     | 
    
         
            +
                password = getch
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                if self.current_user = User.authenticate(email, password)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  puts "Successfully logged in as #{user.email}."
         
     | 
| 
      
 22 
     | 
    
         
            +
                else
         
     | 
| 
      
 23 
     | 
    
         
            +
                  puts "Could not find a user with that email and password."
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
              end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
              private
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
              def current_user=(user)
         
     | 
| 
      
 30 
     | 
    
         
            +
                session["user_id"] = user.id
         
     | 
| 
      
 31 
     | 
    
         
            +
              end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
              def current_user
         
     | 
| 
      
 34 
     | 
    
         
            +
                @current_user ||= User.find(session.fetch("user_id"))
         
     | 
| 
      
 35 
     | 
    
         
            +
              end
         
     | 
| 
      
 36 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,35 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "pathname"
         
     | 
| 
      
 2 
     | 
    
         
            +
            require "yaml"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "uri"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Terminalwire::Client
         
     | 
| 
      
 6 
     | 
    
         
            +
              class Binary
         
     | 
| 
      
 7 
     | 
    
         
            +
                attr_reader :arguments, :path, :configuration, :url
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                def initialize(path:, arguments:)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @arguments = arguments
         
     | 
| 
      
 11 
     | 
    
         
            +
                  @path = Pathname.new(path)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @configuration = YAML.load_file(@path)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @url = URI(@configuration.fetch("url"))
         
     | 
| 
      
 14 
     | 
    
         
            +
                rescue Errno::ENOENT => e
         
     | 
| 
      
 15 
     | 
    
         
            +
                  raise Terminalwire::Error, "File not found: #{@path}"
         
     | 
| 
      
 16 
     | 
    
         
            +
                rescue URI::InvalidURIError => e
         
     | 
| 
      
 17 
     | 
    
         
            +
                  raise Terminalwire::Error, "Invalid URI: #{@url}"
         
     | 
| 
      
 18 
     | 
    
         
            +
                rescue KeyError => e
         
     | 
| 
      
 19 
     | 
    
         
            +
                  raise Terminalwire::Error, "Missing key in configuration: #{e}"
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                def start
         
     | 
| 
      
 23 
     | 
    
         
            +
                  Terminalwire::Client.websocket(url:, arguments:)
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                def self.execute
         
     | 
| 
      
 27 
     | 
    
         
            +
                  case ARGV
         
     | 
| 
      
 28 
     | 
    
         
            +
                  in path, *arguments
         
     | 
| 
      
 29 
     | 
    
         
            +
                    new(path:, arguments:).start
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
                rescue NoMatchingPatternError => e
         
     | 
| 
      
 32 
     | 
    
         
            +
                  raise Terminalwire::Error, "Launched with incorrect arguments: #{ARGV}"
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
              end
         
     | 
| 
      
 35 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,213 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Terminalwire
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Client
         
     | 
| 
      
 3 
     | 
    
         
            +
                module Resource
         
     | 
| 
      
 4 
     | 
    
         
            +
                  class IO < Terminalwire::Resource::Base
         
     | 
| 
      
 5 
     | 
    
         
            +
                    def dispatch(action, data)
         
     | 
| 
      
 6 
     | 
    
         
            +
                      if @device.respond_to?(action)
         
     | 
| 
      
 7 
     | 
    
         
            +
                        respond @device.public_send(action, data)
         
     | 
| 
      
 8 
     | 
    
         
            +
                      else
         
     | 
| 
      
 9 
     | 
    
         
            +
                        raise "Unknown action #{action} for device ID #{@id}"
         
     | 
| 
      
 10 
     | 
    
         
            +
                      end
         
     | 
| 
      
 11 
     | 
    
         
            +
                    end
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  class STDOUT < IO
         
     | 
| 
      
 15 
     | 
    
         
            +
                    def connect
         
     | 
| 
      
 16 
     | 
    
         
            +
                      @device = $stdout
         
     | 
| 
      
 17 
     | 
    
         
            +
                    end
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  class STDIN < IO
         
     | 
| 
      
 21 
     | 
    
         
            +
                    def connect
         
     | 
| 
      
 22 
     | 
    
         
            +
                      @device = $stdin
         
     | 
| 
      
 23 
     | 
    
         
            +
                    end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                    def dispatch(action, data)
         
     | 
| 
      
 26 
     | 
    
         
            +
                      respond case action
         
     | 
| 
      
 27 
     | 
    
         
            +
                      when "puts"
         
     | 
| 
      
 28 
     | 
    
         
            +
                        @device.puts(data)
         
     | 
| 
      
 29 
     | 
    
         
            +
                      when "gets"
         
     | 
| 
      
 30 
     | 
    
         
            +
                        @device.gets
         
     | 
| 
      
 31 
     | 
    
         
            +
                      when "getpass"
         
     | 
| 
      
 32 
     | 
    
         
            +
                        @device.getpass
         
     | 
| 
      
 33 
     | 
    
         
            +
                      end
         
     | 
| 
      
 34 
     | 
    
         
            +
                    end
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  class STDERR < IO
         
     | 
| 
      
 38 
     | 
    
         
            +
                    def connect
         
     | 
| 
      
 39 
     | 
    
         
            +
                      @device = $stderr
         
     | 
| 
      
 40 
     | 
    
         
            +
                    end
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                  class File < Terminalwire::Resource::Base
         
     | 
| 
      
 44 
     | 
    
         
            +
                    def connect
         
     | 
| 
      
 45 
     | 
    
         
            +
                      @files = {}
         
     | 
| 
      
 46 
     | 
    
         
            +
                    end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                    def dispatch(action, data)
         
     | 
| 
      
 49 
     | 
    
         
            +
                      respond case action
         
     | 
| 
      
 50 
     | 
    
         
            +
                      when "read"
         
     | 
| 
      
 51 
     | 
    
         
            +
                        read_file(data)
         
     | 
| 
      
 52 
     | 
    
         
            +
                      when "write"
         
     | 
| 
      
 53 
     | 
    
         
            +
                        write_file(data.fetch(:path), data.fetch(:content))
         
     | 
| 
      
 54 
     | 
    
         
            +
                      when "append"
         
     | 
| 
      
 55 
     | 
    
         
            +
                        append_to_file(data.fetch(:path), data.fetch(:content))
         
     | 
| 
      
 56 
     | 
    
         
            +
                      when "mkdir"
         
     | 
| 
      
 57 
     | 
    
         
            +
                        mkdir(data.fetch(:path))
         
     | 
| 
      
 58 
     | 
    
         
            +
                      when "exist"
         
     | 
| 
      
 59 
     | 
    
         
            +
                        exist?(data.fetch(:path))
         
     | 
| 
      
 60 
     | 
    
         
            +
                      else
         
     | 
| 
      
 61 
     | 
    
         
            +
                        raise "Unknown action #{action} for file device"
         
     | 
| 
      
 62 
     | 
    
         
            +
                      end
         
     | 
| 
      
 63 
     | 
    
         
            +
                    end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                    def mkdir(path)
         
     | 
| 
      
 66 
     | 
    
         
            +
                      FileUtils.mkdir_p(::File.expand_path(path))
         
     | 
| 
      
 67 
     | 
    
         
            +
                    end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                    def exist?(path)
         
     | 
| 
      
 70 
     | 
    
         
            +
                      ::File.exist? ::File.expand_path(path)
         
     | 
| 
      
 71 
     | 
    
         
            +
                    end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                    def read_file(path)
         
     | 
| 
      
 74 
     | 
    
         
            +
                      ::File.read ::File.expand_path(path)
         
     | 
| 
      
 75 
     | 
    
         
            +
                    end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                    def write_file(path, content)
         
     | 
| 
      
 78 
     | 
    
         
            +
                      ::File.open(::File.expand_path(path), "w") { |f| f.write(content) }
         
     | 
| 
      
 79 
     | 
    
         
            +
                    end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                    def append_to_file(path, content)
         
     | 
| 
      
 82 
     | 
    
         
            +
                      ::File.open(::File.expand_path(path), "a") { |f| f.write(content) }
         
     | 
| 
      
 83 
     | 
    
         
            +
                    end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                    def disconnect
         
     | 
| 
      
 86 
     | 
    
         
            +
                      @files.clear
         
     | 
| 
      
 87 
     | 
    
         
            +
                    end
         
     | 
| 
      
 88 
     | 
    
         
            +
                  end
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                  class Browser < Terminalwire::Resource::Base
         
     | 
| 
      
 91 
     | 
    
         
            +
                    def dispatch(action, data)
         
     | 
| 
      
 92 
     | 
    
         
            +
                      respond case action
         
     | 
| 
      
 93 
     | 
    
         
            +
                      when "launch"
         
     | 
| 
      
 94 
     | 
    
         
            +
                        Launchy.open(data)
         
     | 
| 
      
 95 
     | 
    
         
            +
                        "Launched browser with URL: #{data}"
         
     | 
| 
      
 96 
     | 
    
         
            +
                      else
         
     | 
| 
      
 97 
     | 
    
         
            +
                        raise "Unknown action #{action} for browser device"
         
     | 
| 
      
 98 
     | 
    
         
            +
                      end
         
     | 
| 
      
 99 
     | 
    
         
            +
                    end
         
     | 
| 
      
 100 
     | 
    
         
            +
                  end
         
     | 
| 
      
 101 
     | 
    
         
            +
                end
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                class ResourceMapper
         
     | 
| 
      
 104 
     | 
    
         
            +
                  def initialize(connection, resources)
         
     | 
| 
      
 105 
     | 
    
         
            +
                    @connection = connection
         
     | 
| 
      
 106 
     | 
    
         
            +
                    @resources = resources
         
     | 
| 
      
 107 
     | 
    
         
            +
                    @devices = Hash.new { |h,k| h[Integer(k)] }
         
     | 
| 
      
 108 
     | 
    
         
            +
                  end
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
                  def connect_device(id, type)
         
     | 
| 
      
 111 
     | 
    
         
            +
                    klass = @resources.find(type)
         
     | 
| 
      
 112 
     | 
    
         
            +
                    if klass
         
     | 
| 
      
 113 
     | 
    
         
            +
                      device = klass.new(id, @connection)
         
     | 
| 
      
 114 
     | 
    
         
            +
                      device.connect
         
     | 
| 
      
 115 
     | 
    
         
            +
                      @devices[id] = device
         
     | 
| 
      
 116 
     | 
    
         
            +
                      @connection.write(event: "device", action: "connect", status: "success", id: id, type: type)
         
     | 
| 
      
 117 
     | 
    
         
            +
                    else
         
     | 
| 
      
 118 
     | 
    
         
            +
                      @connection.write(event: "device", action: "connect", status: "failure", id: id, type: type, message: "Unknown device type")
         
     | 
| 
      
 119 
     | 
    
         
            +
                    end
         
     | 
| 
      
 120 
     | 
    
         
            +
                  end
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
                  def dispatch(id, action, data)
         
     | 
| 
      
 123 
     | 
    
         
            +
                    device = @devices[id]
         
     | 
| 
      
 124 
     | 
    
         
            +
                    if device
         
     | 
| 
      
 125 
     | 
    
         
            +
                      device.dispatch(action, data)
         
     | 
| 
      
 126 
     | 
    
         
            +
                    else
         
     | 
| 
      
 127 
     | 
    
         
            +
                      raise "Unknown device ID: #{id}"
         
     | 
| 
      
 128 
     | 
    
         
            +
                    end
         
     | 
| 
      
 129 
     | 
    
         
            +
                  end
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
                  def disconnect_device(id)
         
     | 
| 
      
 132 
     | 
    
         
            +
                    device = @devices.delete(id)
         
     | 
| 
      
 133 
     | 
    
         
            +
                    device&.disconnect
         
     | 
| 
      
 134 
     | 
    
         
            +
                    @connection.write(event: "device", action: "disconnect", id: id)
         
     | 
| 
      
 135 
     | 
    
         
            +
                  end
         
     | 
| 
      
 136 
     | 
    
         
            +
                end
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
                class Handler
         
     | 
| 
      
 139 
     | 
    
         
            +
                  VERSION = "0.1.0".freeze
         
     | 
| 
      
 140 
     | 
    
         
            +
             
     | 
| 
      
 141 
     | 
    
         
            +
                  include Logging
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
                  attr_reader :arguments, :program_name
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
                  def initialize(connection, resources = self.class.resources, arguments: ARGV, program_name: $0)
         
     | 
| 
      
 146 
     | 
    
         
            +
                    @connection = connection
         
     | 
| 
      
 147 
     | 
    
         
            +
                    @resources = resources
         
     | 
| 
      
 148 
     | 
    
         
            +
                    @arguments = arguments
         
     | 
| 
      
 149 
     | 
    
         
            +
                  end
         
     | 
| 
      
 150 
     | 
    
         
            +
             
     | 
| 
      
 151 
     | 
    
         
            +
                  def connect
         
     | 
| 
      
 152 
     | 
    
         
            +
                    @devices = ResourceMapper.new(@connection, @resources)
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
                    @connection.write(event: "initialize", protocol: { version: VERSION }, arguments:, program_name:)
         
     | 
| 
      
 155 
     | 
    
         
            +
             
     | 
| 
      
 156 
     | 
    
         
            +
                    loop do
         
     | 
| 
      
 157 
     | 
    
         
            +
                      handle @connection.recv
         
     | 
| 
      
 158 
     | 
    
         
            +
                    end
         
     | 
| 
      
 159 
     | 
    
         
            +
                  end
         
     | 
| 
      
 160 
     | 
    
         
            +
             
     | 
| 
      
 161 
     | 
    
         
            +
                  def handle(message)
         
     | 
| 
      
 162 
     | 
    
         
            +
                    case message
         
     | 
| 
      
 163 
     | 
    
         
            +
                    in { event: "device", action: "connect", id:, type: }
         
     | 
| 
      
 164 
     | 
    
         
            +
                      @devices.connect_device(id, type)
         
     | 
| 
      
 165 
     | 
    
         
            +
                    in { event: "device", action: "command", id:, command:, data: }
         
     | 
| 
      
 166 
     | 
    
         
            +
                      @devices.dispatch(id, command, data)
         
     | 
| 
      
 167 
     | 
    
         
            +
                    in { event: "device", action: "disconnect", id: }
         
     | 
| 
      
 168 
     | 
    
         
            +
                      @devices.disconnect_device(id)
         
     | 
| 
      
 169 
     | 
    
         
            +
                    in { event: "exit", status: }
         
     | 
| 
      
 170 
     | 
    
         
            +
                      exit Integer(status)
         
     | 
| 
      
 171 
     | 
    
         
            +
                    end
         
     | 
| 
      
 172 
     | 
    
         
            +
                  end
         
     | 
| 
      
 173 
     | 
    
         
            +
             
     | 
| 
      
 174 
     | 
    
         
            +
                  def self.resources
         
     | 
| 
      
 175 
     | 
    
         
            +
                    ResourceRegistry.new.tap do |resources|
         
     | 
| 
      
 176 
     | 
    
         
            +
                      resources << Client::Resource::STDOUT
         
     | 
| 
      
 177 
     | 
    
         
            +
                      resources << Client::Resource::STDIN
         
     | 
| 
      
 178 
     | 
    
         
            +
                      resources << Client::Resource::STDERR
         
     | 
| 
      
 179 
     | 
    
         
            +
                      resources << Client::Resource::Browser
         
     | 
| 
      
 180 
     | 
    
         
            +
                      resources << Client::Resource::File
         
     | 
| 
      
 181 
     | 
    
         
            +
                    end
         
     | 
| 
      
 182 
     | 
    
         
            +
                  end
         
     | 
| 
      
 183 
     | 
    
         
            +
                end
         
     | 
| 
      
 184 
     | 
    
         
            +
             
     | 
| 
      
 185 
     | 
    
         
            +
                def self.tcp(...)
         
     | 
| 
      
 186 
     | 
    
         
            +
                  socket = TCPSocket.new(...)
         
     | 
| 
      
 187 
     | 
    
         
            +
                  transport = Terminalwire::Transport::Socket.new(socket)
         
     | 
| 
      
 188 
     | 
    
         
            +
                  connection = Terminalwire::Connection.new(transport)
         
     | 
| 
      
 189 
     | 
    
         
            +
                  Terminalwire::Client::Handler.new(connection)
         
     | 
| 
      
 190 
     | 
    
         
            +
                end
         
     | 
| 
      
 191 
     | 
    
         
            +
             
     | 
| 
      
 192 
     | 
    
         
            +
                def self.socket(...)
         
     | 
| 
      
 193 
     | 
    
         
            +
                  socket = UNIXSocket.new(...)
         
     | 
| 
      
 194 
     | 
    
         
            +
                  transport = Terminalwire::Transport::Socket.new(socket)
         
     | 
| 
      
 195 
     | 
    
         
            +
                  connection = Terminalwire::Connection.new(transport)
         
     | 
| 
      
 196 
     | 
    
         
            +
                  Terminalwire::Client::Handler.new(connection)
         
     | 
| 
      
 197 
     | 
    
         
            +
                end
         
     | 
| 
      
 198 
     | 
    
         
            +
             
     | 
| 
      
 199 
     | 
    
         
            +
                def self.websocket(url:, arguments: ARGV)
         
     | 
| 
      
 200 
     | 
    
         
            +
                  url = URI(url)
         
     | 
| 
      
 201 
     | 
    
         
            +
             
     | 
| 
      
 202 
     | 
    
         
            +
                  Async do |task|
         
     | 
| 
      
 203 
     | 
    
         
            +
                    endpoint = Async::HTTP::Endpoint.parse(url)
         
     | 
| 
      
 204 
     | 
    
         
            +
             
     | 
| 
      
 205 
     | 
    
         
            +
                    Async::WebSocket::Client.connect(endpoint) do |connection|
         
     | 
| 
      
 206 
     | 
    
         
            +
                      transport = Terminalwire::Transport::WebSocket.new(connection)
         
     | 
| 
      
 207 
     | 
    
         
            +
                      connection = Terminalwire::Connection.new(transport)
         
     | 
| 
      
 208 
     | 
    
         
            +
                      Terminalwire::Client::Handler.new(connection, arguments:).connect
         
     | 
| 
      
 209 
     | 
    
         
            +
                    end
         
     | 
| 
      
 210 
     | 
    
         
            +
                  end
         
     | 
| 
      
 211 
     | 
    
         
            +
                end
         
     | 
| 
      
 212 
     | 
    
         
            +
              end
         
     | 
| 
      
 213 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,239 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Terminalwire
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Server
         
     | 
| 
      
 3 
     | 
    
         
            +
                module Resource
         
     | 
| 
      
 4 
     | 
    
         
            +
                  class IO < Terminalwire::Resource::Base
         
     | 
| 
      
 5 
     | 
    
         
            +
                    def puts(data)
         
     | 
| 
      
 6 
     | 
    
         
            +
                      command("puts", data: data)
         
     | 
| 
      
 7 
     | 
    
         
            +
                    end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                    def print(data)
         
     | 
| 
      
 10 
     | 
    
         
            +
                      command("print", data: data)
         
     | 
| 
      
 11 
     | 
    
         
            +
                    end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                    def gets
         
     | 
| 
      
 14 
     | 
    
         
            +
                      command("gets")
         
     | 
| 
      
 15 
     | 
    
         
            +
                    end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                    def flush
         
     | 
| 
      
 18 
     | 
    
         
            +
                      # @connection.flush
         
     | 
| 
      
 19 
     | 
    
         
            +
                    end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                    private
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                    def command(command, data: nil)
         
     | 
| 
      
 24 
     | 
    
         
            +
                      @connection.write(event: "device", id: @id, action: "command", command: command, data: data)
         
     | 
| 
      
 25 
     | 
    
         
            +
                      @connection.recv&.fetch(:response)
         
     | 
| 
      
 26 
     | 
    
         
            +
                    end
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  class STDOUT < IO
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  class STDIN < IO
         
     | 
| 
      
 33 
     | 
    
         
            +
                    def getpass
         
     | 
| 
      
 34 
     | 
    
         
            +
                      command("getpass")
         
     | 
| 
      
 35 
     | 
    
         
            +
                    end
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  class STDERR < IO
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  class File < Terminalwire::Resource::Base
         
     | 
| 
      
 42 
     | 
    
         
            +
                    def read(path)
         
     | 
| 
      
 43 
     | 
    
         
            +
                      command("read", path.to_s)
         
     | 
| 
      
 44 
     | 
    
         
            +
                    end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                    def write(path, content)
         
     | 
| 
      
 47 
     | 
    
         
            +
                      command("write", { 'path' => path.to_s, 'content' => content })
         
     | 
| 
      
 48 
     | 
    
         
            +
                    end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                    def append(path, content)
         
     | 
| 
      
 51 
     | 
    
         
            +
                      command("append", { 'path' => path.to_s, 'content' => content })
         
     | 
| 
      
 52 
     | 
    
         
            +
                    end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                    def mkdir(path)
         
     | 
| 
      
 55 
     | 
    
         
            +
                      command("mkdir", { 'path' => path.to_s })
         
     | 
| 
      
 56 
     | 
    
         
            +
                    end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                    def exist?(path)
         
     | 
| 
      
 59 
     | 
    
         
            +
                      command("exist", { 'path' => path.to_s })
         
     | 
| 
      
 60 
     | 
    
         
            +
                    end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                    private
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                    def command(action, data)
         
     | 
| 
      
 65 
     | 
    
         
            +
                      @connection.write(event: "device", id: @id, action: "command", command: action, data: data)
         
     | 
| 
      
 66 
     | 
    
         
            +
                      response = @connection.recv
         
     | 
| 
      
 67 
     | 
    
         
            +
                      response.fetch(:response)
         
     | 
| 
      
 68 
     | 
    
         
            +
                    end
         
     | 
| 
      
 69 
     | 
    
         
            +
                  end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                  class Browser < Terminalwire::Resource::Base
         
     | 
| 
      
 72 
     | 
    
         
            +
                    def launch(url)
         
     | 
| 
      
 73 
     | 
    
         
            +
                      command("launch", data: url)
         
     | 
| 
      
 74 
     | 
    
         
            +
                    end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                    private
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                    def command(command, data: nil)
         
     | 
| 
      
 79 
     | 
    
         
            +
                      @connection.write(event: "device", id: @id, action: "command", command: command, data: data)
         
     | 
| 
      
 80 
     | 
    
         
            +
                      @connection.recv.fetch(:response)
         
     | 
| 
      
 81 
     | 
    
         
            +
                    end
         
     | 
| 
      
 82 
     | 
    
         
            +
                  end
         
     | 
| 
      
 83 
     | 
    
         
            +
                end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                class ResourceMapper
         
     | 
| 
      
 86 
     | 
    
         
            +
                  include Logging
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                  def initialize(connection, resources = self.class.resources)
         
     | 
| 
      
 89 
     | 
    
         
            +
                    @id = -1
         
     | 
| 
      
 90 
     | 
    
         
            +
                    @resources = resources
         
     | 
| 
      
 91 
     | 
    
         
            +
                    @devices = Hash.new { |h,k| h[Integer(k)] }
         
     | 
| 
      
 92 
     | 
    
         
            +
                    @connection = connection
         
     | 
| 
      
 93 
     | 
    
         
            +
                  end
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                  def connect_device(type)
         
     | 
| 
      
 96 
     | 
    
         
            +
                    id = next_id
         
     | 
| 
      
 97 
     | 
    
         
            +
                    logger.debug "Server: Requesting client to connect device #{type} with ID #{id}"
         
     | 
| 
      
 98 
     | 
    
         
            +
                    @connection.write(event: "device", action: "connect", id: id, type: type)
         
     | 
| 
      
 99 
     | 
    
         
            +
                    response = @connection.recv
         
     | 
| 
      
 100 
     | 
    
         
            +
                    case response
         
     | 
| 
      
 101 
     | 
    
         
            +
                    in { status: "success" }
         
     | 
| 
      
 102 
     | 
    
         
            +
                      logger.debug "Server: Resource #{type} connected with ID #{id}."
         
     | 
| 
      
 103 
     | 
    
         
            +
                      @devices[id] = @resources.find(type).new(id, @connection)
         
     | 
| 
      
 104 
     | 
    
         
            +
                    else
         
     | 
| 
      
 105 
     | 
    
         
            +
                      logger.debug "Server: Failed to connect device #{type} with ID #{id}."
         
     | 
| 
      
 106 
     | 
    
         
            +
                    end
         
     | 
| 
      
 107 
     | 
    
         
            +
                  end
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
                  private
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
                  def next_id
         
     | 
| 
      
 112 
     | 
    
         
            +
                    @id += 1
         
     | 
| 
      
 113 
     | 
    
         
            +
                  end
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                  def self.resources
         
     | 
| 
      
 116 
     | 
    
         
            +
                    ResourceRegistry.new.tap do |resources|
         
     | 
| 
      
 117 
     | 
    
         
            +
                      resources << Server::Resource::STDOUT
         
     | 
| 
      
 118 
     | 
    
         
            +
                      resources << Server::Resource::STDIN
         
     | 
| 
      
 119 
     | 
    
         
            +
                      resources << Server::Resource::STDERR
         
     | 
| 
      
 120 
     | 
    
         
            +
                      resources << Server::Resource::Browser
         
     | 
| 
      
 121 
     | 
    
         
            +
                      resources << Server::Resource::File
         
     | 
| 
      
 122 
     | 
    
         
            +
                    end
         
     | 
| 
      
 123 
     | 
    
         
            +
                  end
         
     | 
| 
      
 124 
     | 
    
         
            +
                end
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
                class Session
         
     | 
| 
      
 127 
     | 
    
         
            +
                  extend Forwardable
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                  attr_reader :stdout, :stdin, :stderr, :browser, :file
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
                  def_delegators :@stdout, :puts, :print
         
     | 
| 
      
 132 
     | 
    
         
            +
                  def_delegators :@stdin, :gets, :getpass
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
                  def initialize(connection:)
         
     | 
| 
      
 135 
     | 
    
         
            +
                    @connection = connection
         
     | 
| 
      
 136 
     | 
    
         
            +
                    @devices = ResourceMapper.new(@connection)
         
     | 
| 
      
 137 
     | 
    
         
            +
                    @stdout = @devices.connect_device("stdout")
         
     | 
| 
      
 138 
     | 
    
         
            +
                    @stdin = @devices.connect_device("stdin")
         
     | 
| 
      
 139 
     | 
    
         
            +
                    @stderr = @devices.connect_device("stderr")
         
     | 
| 
      
 140 
     | 
    
         
            +
                    @browser = @devices.connect_device("browser")
         
     | 
| 
      
 141 
     | 
    
         
            +
                    @file = @devices.connect_device("file")
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
                    if block_given?
         
     | 
| 
      
 144 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 145 
     | 
    
         
            +
                        yield self
         
     | 
| 
      
 146 
     | 
    
         
            +
                      ensure
         
     | 
| 
      
 147 
     | 
    
         
            +
                        exit
         
     | 
| 
      
 148 
     | 
    
         
            +
                      end
         
     | 
| 
      
 149 
     | 
    
         
            +
                    end
         
     | 
| 
      
 150 
     | 
    
         
            +
                  end
         
     | 
| 
      
 151 
     | 
    
         
            +
             
     | 
| 
      
 152 
     | 
    
         
            +
                  def exec(&shell)
         
     | 
| 
      
 153 
     | 
    
         
            +
                    instance_eval(&shell)
         
     | 
| 
      
 154 
     | 
    
         
            +
                  ensure
         
     | 
| 
      
 155 
     | 
    
         
            +
                    exit
         
     | 
| 
      
 156 
     | 
    
         
            +
                  end
         
     | 
| 
      
 157 
     | 
    
         
            +
             
     | 
| 
      
 158 
     | 
    
         
            +
                  def exit(status = 0)
         
     | 
| 
      
 159 
     | 
    
         
            +
                    @connection.write(event: "exit", status: status)
         
     | 
| 
      
 160 
     | 
    
         
            +
                  end
         
     | 
| 
      
 161 
     | 
    
         
            +
             
     | 
| 
      
 162 
     | 
    
         
            +
                  def close
         
     | 
| 
      
 163 
     | 
    
         
            +
                    @connection.close
         
     | 
| 
      
 164 
     | 
    
         
            +
                  end
         
     | 
| 
      
 165 
     | 
    
         
            +
                end
         
     | 
| 
      
 166 
     | 
    
         
            +
             
     | 
| 
      
 167 
     | 
    
         
            +
                class MyCLI < ::Thor
         
     | 
| 
      
 168 
     | 
    
         
            +
                  include Terminalwire::Thor
         
     | 
| 
      
 169 
     | 
    
         
            +
             
     | 
| 
      
 170 
     | 
    
         
            +
                  desc "greet NAME", "Greet a person"
         
     | 
| 
      
 171 
     | 
    
         
            +
                  def greet(name)
         
     | 
| 
      
 172 
     | 
    
         
            +
                    name = ask "What's your name?"
         
     | 
| 
      
 173 
     | 
    
         
            +
                    say "Hello, #{name}!"
         
     | 
| 
      
 174 
     | 
    
         
            +
                  end
         
     | 
| 
      
 175 
     | 
    
         
            +
                end
         
     | 
| 
      
 176 
     | 
    
         
            +
             
     | 
| 
      
 177 
     | 
    
         
            +
                class Socket
         
     | 
| 
      
 178 
     | 
    
         
            +
                  include Logging
         
     | 
| 
      
 179 
     | 
    
         
            +
             
     | 
| 
      
 180 
     | 
    
         
            +
                  def initialize(server_socket)
         
     | 
| 
      
 181 
     | 
    
         
            +
                    @server_socket = server_socket
         
     | 
| 
      
 182 
     | 
    
         
            +
                  end
         
     | 
| 
      
 183 
     | 
    
         
            +
             
     | 
| 
      
 184 
     | 
    
         
            +
                  def listen
         
     | 
| 
      
 185 
     | 
    
         
            +
                    logger.info "Socket: Sistening..."
         
     | 
| 
      
 186 
     | 
    
         
            +
                    loop do
         
     | 
| 
      
 187 
     | 
    
         
            +
                      client_socket = @server_socket.accept
         
     | 
| 
      
 188 
     | 
    
         
            +
                      logger.debug "Socket: Client #{client_socket.inspect} connected"
         
     | 
| 
      
 189 
     | 
    
         
            +
                      handle_client(client_socket)
         
     | 
| 
      
 190 
     | 
    
         
            +
                    end
         
     | 
| 
      
 191 
     | 
    
         
            +
                  end
         
     | 
| 
      
 192 
     | 
    
         
            +
             
     | 
| 
      
 193 
     | 
    
         
            +
                  private
         
     | 
| 
      
 194 
     | 
    
         
            +
             
     | 
| 
      
 195 
     | 
    
         
            +
                  def handle_client(socket)
         
     | 
| 
      
 196 
     | 
    
         
            +
                    transport = Transport::Socket.new(socket)
         
     | 
| 
      
 197 
     | 
    
         
            +
                    connection = Connection.new(transport)
         
     | 
| 
      
 198 
     | 
    
         
            +
             
     | 
| 
      
 199 
     | 
    
         
            +
                    Thread.new do
         
     | 
| 
      
 200 
     | 
    
         
            +
                      handler = Handler.new(connection)
         
     | 
| 
      
 201 
     | 
    
         
            +
                      handler.run
         
     | 
| 
      
 202 
     | 
    
         
            +
                    end
         
     | 
| 
      
 203 
     | 
    
         
            +
                  end
         
     | 
| 
      
 204 
     | 
    
         
            +
                end
         
     | 
| 
      
 205 
     | 
    
         
            +
             
     | 
| 
      
 206 
     | 
    
         
            +
                class Handler
         
     | 
| 
      
 207 
     | 
    
         
            +
                  include Logging
         
     | 
| 
      
 208 
     | 
    
         
            +
             
     | 
| 
      
 209 
     | 
    
         
            +
                  def initialize(connection)
         
     | 
| 
      
 210 
     | 
    
         
            +
                    @connection = connection
         
     | 
| 
      
 211 
     | 
    
         
            +
                  end
         
     | 
| 
      
 212 
     | 
    
         
            +
             
     | 
| 
      
 213 
     | 
    
         
            +
                  def run
         
     | 
| 
      
 214 
     | 
    
         
            +
                    logger.info "Server Handler: Running"
         
     | 
| 
      
 215 
     | 
    
         
            +
                    loop do
         
     | 
| 
      
 216 
     | 
    
         
            +
                      message = @connection.recv
         
     | 
| 
      
 217 
     | 
    
         
            +
                      case message
         
     | 
| 
      
 218 
     | 
    
         
            +
                      in { event: "initialize", arguments:, program_name: }
         
     | 
| 
      
 219 
     | 
    
         
            +
                        Session.new(connection: @connection) do |session|
         
     | 
| 
      
 220 
     | 
    
         
            +
                          MyCLI.start(arguments, session: session)
         
     | 
| 
      
 221 
     | 
    
         
            +
                        end
         
     | 
| 
      
 222 
     | 
    
         
            +
                      end
         
     | 
| 
      
 223 
     | 
    
         
            +
                    end
         
     | 
| 
      
 224 
     | 
    
         
            +
                  rescue EOFError, Errno::ECONNRESET
         
     | 
| 
      
 225 
     | 
    
         
            +
                    logger.info "Server Handler: Client disconnected"
         
     | 
| 
      
 226 
     | 
    
         
            +
                  ensure
         
     | 
| 
      
 227 
     | 
    
         
            +
                    @connection.close
         
     | 
| 
      
 228 
     | 
    
         
            +
                  end
         
     | 
| 
      
 229 
     | 
    
         
            +
                end
         
     | 
| 
      
 230 
     | 
    
         
            +
             
     | 
| 
      
 231 
     | 
    
         
            +
                def self.tcp(...)
         
     | 
| 
      
 232 
     | 
    
         
            +
                  Server::Socket.new(TCPServer.new(...))
         
     | 
| 
      
 233 
     | 
    
         
            +
                end
         
     | 
| 
      
 234 
     | 
    
         
            +
             
     | 
| 
      
 235 
     | 
    
         
            +
                def self.socket(...)
         
     | 
| 
      
 236 
     | 
    
         
            +
                  Server::Socket.new(UNIXServer.new(...))
         
     | 
| 
      
 237 
     | 
    
         
            +
                end
         
     | 
| 
      
 238 
     | 
    
         
            +
              end
         
     | 
| 
      
 239 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,43 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Terminalwire
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Thor
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Shell < ::Thor::Shell::Basic
         
     | 
| 
      
 4 
     | 
    
         
            +
                  extend Forwardable
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                  # Encapsulates all of the IO devices for a Terminalwire connection.
         
     | 
| 
      
 7 
     | 
    
         
            +
                  attr_reader :session
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  def_delegators :@session, :stdin, :stdout, :stderr
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  def initialize(session)
         
     | 
| 
      
 12 
     | 
    
         
            +
                    @session = session
         
     | 
| 
      
 13 
     | 
    
         
            +
                    super()
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def self.included(base)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  base.extend ClassMethods
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  # I have to do this in a block to deal with some of Thor's DSL
         
     | 
| 
      
 21 
     | 
    
         
            +
                  base.class_eval do
         
     | 
| 
      
 22 
     | 
    
         
            +
                    extend Forwardable
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                    protected
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                    no_commands do
         
     | 
| 
      
 27 
     | 
    
         
            +
                      def_delegators :shell, :session
         
     | 
| 
      
 28 
     | 
    
         
            +
                      def_delegators :session, :stdout, :stdin, :stderr, :browser
         
     | 
| 
      
 29 
     | 
    
         
            +
                      def_delegators :stdout, :puts, :print
         
     | 
| 
      
 30 
     | 
    
         
            +
                      def_delegators :stdin, :gets
         
     | 
| 
      
 31 
     | 
    
         
            +
                    end
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                module ClassMethods
         
     | 
| 
      
 36 
     | 
    
         
            +
                  def start(given_args = ARGV, config = {})
         
     | 
| 
      
 37 
     | 
    
         
            +
                    session = config.delete(:session)
         
     | 
| 
      
 38 
     | 
    
         
            +
                    config[:shell] = Shell.new(session) if session
         
     | 
| 
      
 39 
     | 
    
         
            +
                    super(given_args, config)
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
              end
         
     | 
| 
      
 43 
     | 
    
         
            +
            end
         
     |