terminalwire 0.2.5 → 0.3.0.alpha2

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +67 -5
  3. data/exe/terminalwire +1 -1
  4. data/exe/terminalwire-exec +1 -1
  5. metadata +9 -171
  6. data/.rspec +0 -3
  7. data/CHANGELOG.md +0 -5
  8. data/CODE_OF_CONDUCT.md +0 -132
  9. data/LICENSE.txt +0 -9
  10. data/README.md +0 -45
  11. data/examples/exec/localrails +0 -2
  12. data/lib/generators/terminalwire/install/USAGE +0 -9
  13. data/lib/generators/terminalwire/install/install_generator.rb +0 -38
  14. data/lib/generators/terminalwire/install/templates/application_terminal.rb.tt +0 -21
  15. data/lib/generators/terminalwire/install/templates/bin/terminalwire +0 -2
  16. data/lib/generators/terminalwire/install/templates/main_terminal.rb +0 -40
  17. data/lib/terminalwire/adapter.rb +0 -53
  18. data/lib/terminalwire/cache.rb +0 -114
  19. data/lib/terminalwire/client/entitlement/environment_variables.rb +0 -26
  20. data/lib/terminalwire/client/entitlement/paths.rb +0 -97
  21. data/lib/terminalwire/client/entitlement/policy.rb +0 -117
  22. data/lib/terminalwire/client/entitlement/schemes.rb +0 -26
  23. data/lib/terminalwire/client/entitlement.rb +0 -9
  24. data/lib/terminalwire/client/exec.rb +0 -44
  25. data/lib/terminalwire/client/handler.rb +0 -67
  26. data/lib/terminalwire/client/resource.rb +0 -204
  27. data/lib/terminalwire/client/server_license_verification.rb +0 -74
  28. data/lib/terminalwire/client.rb +0 -30
  29. data/lib/terminalwire/logging.rb +0 -8
  30. data/lib/terminalwire/rails.rb +0 -69
  31. data/lib/terminalwire/server/context.rb +0 -80
  32. data/lib/terminalwire/server/resource.rb +0 -120
  33. data/lib/terminalwire/server.rb +0 -63
  34. data/lib/terminalwire/thor.rb +0 -67
  35. data/lib/terminalwire/transport.rb +0 -58
  36. data/lib/terminalwire/version.rb +0 -5
  37. data/lib/terminalwire.rb +0 -56
  38. data/sig/terminalwire.rbs +0 -4
@@ -1,204 +0,0 @@
1
- require "fileutils"
2
- require "io/console"
3
-
4
- module Terminalwire::Client::Resource
5
- # Dispatches messages from the Client::Handler to the appropriate resource.
6
- class Handler
7
- include Enumerable
8
-
9
- def initialize
10
- @resources = {}
11
- yield self if block_given?
12
- end
13
-
14
- def each(&block)
15
- @resources.values.each(&block)
16
- end
17
-
18
- def add(resource)
19
- # Detect if the resource is already registered and throw an error
20
- if @resources.key?(resource.name)
21
- raise "Resource #{resource.name} already registered"
22
- else
23
- @resources[resource.name] = resource
24
- end
25
- end
26
- alias :<< :add
27
-
28
- def dispatch(**message)
29
- case message
30
- in { event:, action:, name:, command:, parameters: }
31
- resource = @resources.fetch(name)
32
- resource.command(command, **parameters)
33
- end
34
- end
35
- end
36
-
37
- # Dispatcher, security, and response macros for resources.
38
- class Base < Terminalwire::Resource::Base
39
- def initialize(*, entitlement:, **)
40
- super(*, **)
41
- @entitlement = entitlement
42
- connect
43
- end
44
-
45
- def command(command, **parameters)
46
- begin
47
- if permit(command, **parameters)
48
- succeed self.public_send(command, **parameters)
49
- else
50
- fail "Client denied #{command}", command:, parameters:
51
- end
52
- rescue => e
53
- fail e.message, command:, parameters:
54
- raise
55
- end
56
- end
57
-
58
- protected
59
-
60
- def permit(...)
61
- false
62
- end
63
- end
64
-
65
- class EnvironmentVariable < Base
66
- # Accepts a list of environment variables to permit.
67
- def read(name:)
68
- ENV[name]
69
- end
70
-
71
- # def write(name:, value:)
72
- # ENV[name] = value
73
- # end
74
-
75
- protected
76
-
77
- def permit(command, name:, **)
78
- @entitlement.environment_variables.permitted? name
79
- end
80
- end
81
-
82
- class STDOUT < Base
83
- def connect
84
- @io = $stdout
85
- end
86
-
87
- def print(data:)
88
- @io.print(data)
89
- end
90
-
91
- def print_line(data:)
92
- @io.puts(data)
93
- end
94
-
95
- protected
96
-
97
- def permit(...)
98
- true
99
- end
100
- end
101
-
102
- class STDERR < STDOUT
103
- def connect
104
- @io = $stderr
105
- end
106
- end
107
-
108
- class STDIN < Base
109
- def connect
110
- @io = $stdin
111
- end
112
-
113
- def read_line
114
- @io.gets
115
- end
116
-
117
- def read_password
118
- @io.getpass
119
- end
120
-
121
- protected
122
-
123
- def permit(...)
124
- true
125
- end
126
- end
127
-
128
- class File < Base
129
- File = ::File
130
-
131
- def read(path:)
132
- File.read File.expand_path(path)
133
- end
134
-
135
- def write(path:, content:, mode: nil)
136
- File.open(File.expand_path(path), "w", mode) { |f| f.write(content) }
137
- end
138
-
139
- def append(path:, content:, mode: nil)
140
- File.open(File.expand_path(path), "a", mode) { |f| f.write(content) }
141
- end
142
-
143
- def delete(path:)
144
- File.delete File.expand_path(path)
145
- end
146
-
147
- def exist(path:)
148
- File.exist? File.expand_path(path)
149
- end
150
-
151
- def change_mode(path:, mode:)
152
- File.chmod mode, File.expand_path(path)
153
- end
154
-
155
- protected
156
-
157
- def permit(command, path:, mode: nil, **)
158
- @entitlement.paths.permitted? path, mode:
159
- end
160
- end
161
-
162
- class Directory < Base
163
- File = ::File
164
-
165
- def list(path:)
166
- Dir.glob path
167
- end
168
-
169
- def create(path:)
170
- FileUtils.mkdir_p File.expand_path(path)
171
- rescue Errno::EEXIST
172
- # Do nothing
173
- end
174
-
175
- def exist(path:)
176
- Dir.exist? path
177
- end
178
-
179
- def delete(path:)
180
- Dir.delete path
181
- end
182
-
183
- protected
184
-
185
- def permit(command, path:, **)
186
- @entitlement.paths.permitted? path
187
- end
188
- end
189
-
190
- class Browser < Base
191
- def launch(url:)
192
- Launchy.open(URI(url))
193
- # TODO: This is a hack to get the `respond` method to work.
194
- # Maybe explicitly call a `suceed` and `fail` method?
195
- nil
196
- end
197
-
198
- protected
199
-
200
- def permit(command, url:, **)
201
- @entitlement.schemes.permitted? url
202
- end
203
- end
204
- end
@@ -1,74 +0,0 @@
1
- require "async/http/internet"
2
- require "base64"
3
- require "uri"
4
- require "fileutils"
5
-
6
- module Terminalwire::Client
7
- # Checkes the server for a license verification at `https://terminalwire.com/licenses/verifications/`
8
- # and displays the message to the user, if necessary.
9
- class ServerLicenseVerification
10
- include Terminalwire::Logging
11
-
12
- def initialize(url:)
13
- @url = URI(url)
14
- @internet = Async::HTTP::Internet.new
15
- @cache_store = Terminalwire::Cache::File::Store.new(path: Terminalwire::Client.root_path.join("cache/licenses/verifications"))
16
- end
17
-
18
- def key
19
- Base64.urlsafe_encode64 @url
20
- end
21
-
22
- def cache = @cache_store.entry key
23
-
24
- def payload
25
- if cache.miss?
26
- logger.debug "Stale verification. Requesting new verification."
27
- request do |response|
28
- # Set the expiry on the file cache for the header.
29
- if max_age = response.headers["cache-control"].max_age
30
- logger.debug "Caching for #{max_age}"
31
- cache.expires = Time.now + max_age
32
- end
33
-
34
- # Process based on the response code.
35
- case response.status
36
- in 200
37
- logger.debug "License for #{@url} found."
38
- data = self.class.unpack response.read
39
- cache.value = data
40
- return data
41
- in 404
42
- logger.debug "License for #{@url} not found."
43
- return self.class.unpack response.read
44
- end
45
- end
46
- else
47
- return cache.value
48
- end
49
- end
50
-
51
- def message
52
- payload.dig(:shell, :output)
53
- end
54
-
55
- protected
56
-
57
- def verification_url
58
- Terminalwire.url
59
- .path("/licenses/verifications", key)
60
- end
61
-
62
- def request(&)
63
- logger.debug "Requesting license verification from #{verification_url}."
64
- response = @internet.get verification_url, {
65
- "Accept" => "application/x-msgpack",
66
- "User-Agent" => "Terminalwire/#{Terminalwire::VERSION} Ruby/#{RUBY_VERSION} (#{RUBY_PLATFORM})",
67
- }, &
68
- end
69
-
70
- def self.unpack(pack)
71
- MessagePack.unpack(pack, symbolize_keys: true)
72
- end
73
- end
74
- end
@@ -1,30 +0,0 @@
1
- require 'fileutils'
2
- require 'launchy'
3
- require 'io/console'
4
- require 'pathname'
5
-
6
- module Terminalwire
7
- module Client
8
- ROOT_PATH = "~/.terminalwire".freeze
9
- def self.root_path = Pathname.new(ENV.fetch("TERMINALWIRE_HOME", ROOT_PATH))
10
-
11
- def self.websocket(url:, arguments: ARGV, &configuration)
12
- ENV["TERMINALWIRE_HOME"] ||= root_path.to_s
13
-
14
- url = URI(url)
15
-
16
- Async do |task|
17
- endpoint = Async::HTTP::Endpoint.parse(
18
- url,
19
- alpn_protocols: Async::HTTP::Protocol::HTTP11.names
20
- )
21
-
22
- Async::WebSocket::Client.connect(endpoint) do |adapter|
23
- transport = Terminalwire::Transport::WebSocket.new(adapter)
24
- adapter = Terminalwire::Adapter::Socket.new(transport)
25
- Terminalwire::Client::Handler.new(adapter, arguments:, endpoint:, &configuration).connect
26
- end
27
- end
28
- end
29
- end
30
- end
@@ -1,8 +0,0 @@
1
- require 'logger'
2
-
3
- module Terminalwire
4
- module Logging
5
- DEVICE = Logger.new($stdout, level: ENV.fetch("TERMINALWIRE_LOG_LEVEL", "info"))
6
- def logger = DEVICE
7
- end
8
- end
@@ -1,69 +0,0 @@
1
- require 'jwt'
2
- require 'pathname'
3
- require 'forwardable'
4
-
5
- module Terminalwire::Rails
6
- class Session
7
- # JWT file name for the session file.
8
- FILENAME = "session.jwt"
9
-
10
- # Empty dictionary the user can stash all their session data into.
11
- EMPTY_SESSION = {}.freeze
12
-
13
- extend Forwardable
14
-
15
- # Delegate `dig` and `fetch` to the `read` method
16
- def_delegators :read,
17
- :dig, :fetch, :[]
18
-
19
- def initialize(context:, path: nil, secret_key: self.class.secret_key)
20
- @context = context
21
- @path = Pathname.new(path || context.storage_path)
22
- @config_file_path = @path.join(FILENAME)
23
- @secret_key = secret_key
24
-
25
- ensure_file
26
- end
27
-
28
- def read
29
- jwt_token = @context.file.read(@config_file_path)
30
- decoded_data = JWT.decode(jwt_token, @secret_key, true, algorithm: 'HS256')
31
- decoded_data[0] # JWT payload is the first element in the array
32
- rescue JWT::DecodeError => e
33
- raise "Invalid or tampered file: #{e.message}"
34
- end
35
-
36
- def reset
37
- @context.file.delete @config_file_path
38
- end
39
-
40
- def edit
41
- config = read
42
- yield config
43
- write(config)
44
- end
45
-
46
- def []=(key, value)
47
- edit { |config| config[key] = value }
48
- end
49
-
50
- def write(config)
51
- token = JWT.encode(config, @secret_key, 'HS256')
52
- @context.file.write(@config_file_path, token)
53
- end
54
-
55
- private
56
-
57
- def ensure_file
58
- return true if @context.file.exist? @config_file_path
59
- # Create the path if it doesn't exist on the client.
60
- @context.directory.create @path
61
- # Write an empty configuration on initialization
62
- write(EMPTY_SESSION)
63
- end
64
-
65
- def self.secret_key
66
- Rails.application.secret_key_base
67
- end
68
- end
69
- end
@@ -1,80 +0,0 @@
1
- require "fileutils"
2
-
3
- module Terminalwire::Server
4
- # Contains all of the resources that are accessible to the server on the client-side.
5
- # It's the primary interface for the server to interact with the client and is integrated
6
- # into other libraries like Thor, etc.
7
- class Context
8
- extend Forwardable
9
-
10
- attr_reader \
11
- :stdout, :stdin, :stderr,
12
- :browser,
13
- :file, :directory,
14
- :environment_variable,
15
- :authority,
16
- :root_path,
17
- :authority_path,
18
- :storage_path
19
-
20
- def_delegators :@stdout, :puts, :print
21
- def_delegators :@stdin, :gets, :getpass
22
-
23
- def initialize(adapter:, entitlement:)
24
- @adapter = adapter
25
- @entitlement = entitlement
26
-
27
- # Initialize resources
28
- @stdout = Resource::STDOUT.new("stdout", @adapter)
29
- @stdin = Resource::STDIN.new("stdin", @adapter)
30
- @stderr = Resource::STDERR.new("stderr", @adapter)
31
- @browser = Resource::Browser.new("browser", @adapter)
32
- @file = Resource::File.new("file", @adapter)
33
- @directory = Resource::Directory.new("directory", @adapter)
34
- @environment_variable = Resource::EnvironmentVariable.new("environment_variable", @adapter)
35
-
36
- # Authority is provided by the client.
37
- @authority = @entitlement.fetch(:authority)
38
- # The Terminalwire home path is provided by the client and set
39
- # as an environment variable.
40
- @root_path = Pathname.new(
41
- @environment_variable.read("TERMINALWIRE_HOME")
42
- )
43
- # Now derive the rest of the paths from the Terminalwire home path.
44
- @authority_path = @root_path.join("authorities", @authority)
45
- @storage_path = @authority_path.join("storage")
46
-
47
- if block_given?
48
- begin
49
- yield self
50
- ensure
51
- exit
52
- end
53
- end
54
- end
55
-
56
- # Wraps the environment variables in a hash-like object that can be accessed
57
- # from client#ENV. This makes it look and feel just like the ENV object in Ruby.
58
- class Env
59
- def initialize(context:)
60
- @context = context
61
- end
62
-
63
- def [](name)
64
- @context.environment_variable.read(name)
65
- end
66
- end
67
-
68
- def ENV
69
- @ENV ||= Env.new(context: self)
70
- end
71
-
72
- def exit(status = 0)
73
- @adapter.write(event: "exit", status: status)
74
- end
75
-
76
- def close
77
- @adapter.close
78
- end
79
- end
80
- end
@@ -1,120 +0,0 @@
1
- module Terminalwire::Server
2
- # Representation of the resources avilable to the server on the client-side. These
3
- # classes encapsulate the API alls to the client and provide a more Ruby-like interface.
4
- module Resource
5
- class Base < Terminalwire::Resource::Base
6
- private
7
-
8
- def command(command, **parameters)
9
- @adapter.write(
10
- event: "resource",
11
- name: @name,
12
- action: "command",
13
- command: command,
14
- parameters: parameters
15
- )
16
-
17
- response = @adapter.read
18
- case response.fetch(:status)
19
- when "success"
20
- response.fetch(:response)
21
- when "failure"
22
- raise Terminalwire::Error, response.inspect
23
- end
24
- end
25
- end
26
-
27
- class EnvironmentVariable < Base
28
- # Accepts a list of environment variables to permit.
29
- def read(name)
30
- command("read", name:)
31
- end
32
-
33
- # def write(name:, value:)
34
- # command("write", name:, value:)
35
- # end
36
- end
37
-
38
- class STDOUT < Base
39
- def puts(data)
40
- command("print_line", data: data)
41
- end
42
-
43
- def print(data)
44
- command("print", data: data)
45
- end
46
-
47
- def flush
48
- # Do nothing
49
- end
50
- end
51
-
52
- class STDERR < STDOUT
53
- end
54
-
55
- class STDIN < Base
56
- def getpass
57
- command("read_password")
58
- end
59
-
60
- def gets
61
- command("read_line")
62
- end
63
- end
64
-
65
- class File < Base
66
- def read(path)
67
- command("read", path: path.to_s)
68
- end
69
-
70
- def write(path, content)
71
- command("write", path: path.to_s, content:)
72
- end
73
-
74
- def append(path, content)
75
- command("append", path: path.to_s, content:)
76
- end
77
-
78
- def delete(path)
79
- command("delete", path: path.to_s)
80
- end
81
- alias :rm :delete
82
-
83
- def exist?(path)
84
- command("exist", path: path.to_s)
85
- end
86
-
87
- def change_mode(path, mode)
88
- command("change_mode", path: path.to_s, mode:)
89
- end
90
- alias :chmod :change_mode
91
- end
92
-
93
- class Directory < Base
94
- def list(path)
95
- command("list", path: path.to_s)
96
- end
97
- alias :ls :list
98
-
99
- def create(path)
100
- command("create", path: path.to_s)
101
- end
102
- alias :mkdir :create
103
-
104
- def exist?(path)
105
- command("exist", path: path.to_s)
106
- end
107
-
108
- def delete(path)
109
- command("delete", path: path.to_s)
110
- end
111
- alias :rm :delete
112
- end
113
-
114
- class Browser < Base
115
- def launch(url)
116
- command("launch", url: url)
117
- end
118
- end
119
- end
120
- end
@@ -1,63 +0,0 @@
1
- require "thor"
2
-
3
- module Terminalwire
4
- module Server
5
- class WebSocket
6
- include Logging
7
-
8
- def call(env)
9
- Async::WebSocket::Adapters::Rack.open(env, protocols: ['ws']) do |connection|
10
- handle(Adapter::Socket.new(Terminalwire::Transport::WebSocket.new(connection)))
11
- end or [200, { "Content-Type" => "text/plain" }, ["Connect via WebSockets"]]
12
- end
13
-
14
- def handle(adapter)
15
- while message = adapter.read
16
- puts message
17
- end
18
- end
19
- end
20
-
21
- class Thor < WebSocket
22
- Rails = ::Rails
23
-
24
- include Logging
25
-
26
- def initialize(cli_class)
27
- @cli_class = cli_class
28
-
29
- unless @cli_class.included_modules.include?(Terminalwire::Thor)
30
- raise 'Add `include Terminalwire::Thor` to the #{@cli_class.inspect} class.'
31
- end
32
- end
33
-
34
- def error_message
35
- "An error occurred. Please try again."
36
- end
37
-
38
- def handle(adapter)
39
- logger.info "ThorServer: Running #{@cli_class.inspect}"
40
- while message = adapter.read
41
- case message
42
- in { event: "initialization", protocol:, program: { arguments: }, entitlement: }
43
- context = Terminalwire::Server::Context.new(adapter:, entitlement:)
44
-
45
- begin
46
- @cli_class.start(arguments, context:)
47
- context.exit
48
- rescue StandardError => e
49
- if Rails.application.config.consider_all_requests_local
50
- # Show the full error message with stack trace in development
51
- context.stderr.puts "#{e.inspect}\n#{e.backtrace.join("\n")}"
52
- else
53
- # Show a generic message in production
54
- context.stderr.puts error_message
55
- end
56
- context.exit 1
57
- end
58
- end
59
- end
60
- end
61
- end
62
- end
63
- end