Class: Terminalwire::V2::Server::Context
- Inherits:
-
Object
- Object
- Terminalwire::V2::Server::Context
- Defined in:
- lib/terminalwire/v2/server/context.rb
Overview
The server's handle on the client's machine. CLI code calls these methods (puts, gets, file.read, ...) and the Context turns them into protocol frames via the Runtime. Output is one-way; input and filesystem ops are synchronous request/response.
Defined Under Namespace
Classes: Browser, Directory, File, RawInput, Stdin
Instance Attribute Summary collapse
-
#request ⇒ Object
The incoming HTTP connection profile, captured at the WebSocket upgrade (the Rack adapter sets it): { host:, ip:, user_agent:, headers: }.
Instance Method Summary collapse
- #browser ⇒ Object
-
#capabilities ⇒ Object
The capability set negotiated for this session — the intersection the client and server agreed on in the handshake.
-
#client_version ⇒ Object
The client build version, parsed from the User-Agent ("terminalwire-exec/
(…)"). - #directory ⇒ Object
-
#entitlement ⇒ Object
The entitlement the client granted this session: its authority (origin), the path globs it will allow writes within, and the permitted schemes/env vars.
- #env(name) ⇒ Object
- #exit(status = 0) ⇒ Object
- #file ⇒ Object
- #getpass ⇒ Object
-
#gets ⇒ Object
Convenience delegators (Thor's shell uses these).
- #http_headers ⇒ Object
-
#initialize(runtime) ⇒ Context
constructor
A new instance of Context.
-
#on_resize(&block) ⇒ Object
Register a callback fired when the client's window resizes.
-
#print(data, stream: :stdout) ⇒ Object
Output is flow-controlled and chunked by the runtime: write_data sizes each data frame to the client's available credit and blocks when the window is exhausted, so a fast server can't outrun a slow client.
- #program_arguments ⇒ Object
-
#program_name ⇒ Object
The program name + arguments the client launched with (from the hello).
- #puts(data = "", stream: :stdout) ⇒ Object
-
#query_terminal(sequence, timeout: 1.0) ⇒ Object
Query the client's terminal with a control sequence and return its reply (e.g. cursor-position report "\e[6n" -> "\e[row;colR"). The client writes the query to its tty and reads the terminal's response. Used by advanced TUI libraries that probe the terminal. Support is advertised via the terminal-query capability (see #capabilities); a client without a tty answers with an io error. (Enforcement isn't wired yet — this issues the request regardless — so check #capabilities yourself if that matters.).
-
#raw_input(mode: Protocol::Mode::RAW) ⇒ Object
Read keystrokes for the duration of the block: the client puts its terminal in
modeand streams keypresses, restoring it when the block exits (even on error). -
#read_key ⇒ Object
Single-keypress read with echo + signal keys left on (cbreak): the y/n prompt case.
-
#remote_ip ⇒ Object
The client's real IP (through Fly/proxies), its User-Agent, and the raw incoming HTTP headers.
-
#root_path ⇒ Object
The server-manageable root on the client — the USER subtree (~/.terminalwire/usr), so binary_path = root_path/bin = ~/.terminalwire/usr/bin (installed app launchers).
- #stdin ⇒ Object
-
#storage_path ⇒ Object
The client directory this origin may persist files into — its sandbox — derived from the first granted path glob (".../**" -> "...").
-
#terminal ⇒ Object
The client's live terminal (rows/cols/tty?/color?), kept current by the runtime's read pump as resize frames arrive.
- #user_agent ⇒ Object
- #warn(data = "") ⇒ Object
Constructor Details
#initialize(runtime) ⇒ Context
Returns a new instance of Context.
12 13 14 15 16 17 |
# File 'lib/terminalwire/v2/server/context.rb', line 12 def initialize(runtime) @runtime = runtime @stdout_sid = nil @stderr_sid = nil @request = {} end |
Instance Attribute Details
#request ⇒ Object
The incoming HTTP connection profile, captured at the WebSocket upgrade
(the Rack adapter sets it): { host:, ip:, user_agent:, headers: }. The
client identifies itself in headers — chiefly a real User-Agent — so server
code can see who connected (and a terminalwire about can light it up).
23 24 25 |
# File 'lib/terminalwire/v2/server/context.rb', line 23 def request @request end |
Instance Method Details
#browser ⇒ Object
148 149 150 |
# File 'lib/terminalwire/v2/server/context.rb', line 148 def browser @browser ||= Browser.new(@runtime) end |
#capabilities ⇒ Object
The capability set negotiated for this session — the intersection the client and server agreed on in the handshake. Branch on this to offer optional features only when the connected client supports them.
56 |
# File 'lib/terminalwire/v2/server/context.rb', line 56 def capabilities = @runtime.connection.capabilities |
#client_version ⇒ Object
The client build version, parsed from the User-Agent
("terminalwire-exec/
34 35 36 |
# File 'lib/terminalwire/v2/server/context.rb', line 34 def client_version user_agent && user_agent[%r{terminalwire-exec/(\S+)}, 1] end |
#directory ⇒ Object
144 145 146 |
# File 'lib/terminalwire/v2/server/context.rb', line 144 def directory @directory ||= Directory.new(@runtime) end |
#entitlement ⇒ Object
The entitlement the client granted this session: its authority (origin), the path globs it will allow writes within, and the permitted schemes/env vars. The CLIENT enforces this; here it's a read-only view so server code can stay inside the sandbox (e.g. learn where it may persist a file).
51 |
# File 'lib/terminalwire/v2/server/context.rb', line 51 def entitlement = @runtime.entitlement |
#env(name) ⇒ Object
104 105 106 |
# File 'lib/terminalwire/v2/server/context.rb', line 104 def env(name) @runtime.request(:env, :read, { "name" => name.to_s }) end |
#exit(status = 0) ⇒ Object
152 153 154 |
# File 'lib/terminalwire/v2/server/context.rb', line 152 def exit(status = 0) @runtime.emit(Frames.exit(status: status)) end |
#file ⇒ Object
140 141 142 |
# File 'lib/terminalwire/v2/server/context.rb', line 140 def file @file ||= File.new(@runtime) end |
#getpass ⇒ Object
102 |
# File 'lib/terminalwire/v2/server/context.rb', line 102 def getpass = stdin.getpass |
#gets ⇒ Object
Convenience delegators (Thor's shell uses these).
101 |
# File 'lib/terminalwire/v2/server/context.rb', line 101 def gets = stdin.gets |
#http_headers ⇒ Object
29 |
# File 'lib/terminalwire/v2/server/context.rb', line 29 def http_headers = @request[:headers] || {} |
#on_resize(&block) ⇒ Object
Register a callback fired when the client's window resizes.
78 |
# File 'lib/terminalwire/v2/server/context.rb', line 78 def on_resize(&block) = @runtime.on_resize(&block) |
#print(data, stream: :stdout) ⇒ Object
Output is flow-controlled and chunked by the runtime: write_data sizes each data frame to the client's available credit and blocks when the window is exhausted, so a fast server can't outrun a slow client.
83 84 85 86 |
# File 'lib/terminalwire/v2/server/context.rb', line 83 def print(data, stream: :stdout) sid = stream == :stderr ? (@stderr_sid ||= open(:stderr)) : (@stdout_sid ||= open(:stdout)) @runtime.write_data(sid, data.to_s) end |
#program_arguments ⇒ Object
45 |
# File 'lib/terminalwire/v2/server/context.rb', line 45 def program_arguments = Array(@runtime.program && @runtime.program["args"]) |
#program_name ⇒ Object
The program name + arguments the client launched with (from the hello).
CLI parsers (OptionParser, Thor, GLI, …) consume program_arguments.
44 |
# File 'lib/terminalwire/v2/server/context.rb', line 44 def program_name = @runtime.program && @runtime.program["name"] |
#puts(data = "", stream: :stdout) ⇒ Object
88 89 90 |
# File 'lib/terminalwire/v2/server/context.rb', line 88 def puts(data = "", stream: :stdout) print("#{data}\n", stream: stream) end |
#query_terminal(sequence, timeout: 1.0) ⇒ Object
Query the client's terminal with a control sequence and return its reply (e.g. cursor-position report "\e[6n" -> "\e[row;colR"). The client writes the query to its tty and reads the terminal's response. Used by advanced TUI libraries that probe the terminal. Support is advertised via the terminal-query capability (see #capabilities); a client without a tty answers with an io error. (Enforcement isn't wired yet — this issues the request regardless — so check #capabilities yourself if that matters.)
136 137 138 |
# File 'lib/terminalwire/v2/server/context.rb', line 136 def query_terminal(sequence, timeout: 1.0) @runtime.request(:terminal, :query, { "sequence" => sequence.b, "timeout" => timeout }) end |
#raw_input(mode: Protocol::Mode::RAW) ⇒ Object
Read keystrokes for the duration of the block: the client puts its terminal
in mode and streams keypresses, restoring it when the block exits (even
on error). Foundation for REPLs and interactive TUIs.
mode: :raw — char-at-a-time, no echo, signals as bytes (TUIs)
mode: :cbreak — char-at-a-time, echo + signal keys on (single-key y/n)
context.raw_input { |keys| keys.each { |bytes| handle(bytes) } }
116 117 118 119 120 121 |
# File 'lib/terminalwire/v2/server/context.rb', line 116 def raw_input(mode: Protocol::Mode::RAW) sid = @runtime.open_raw_input(mode: mode.to_s) yield RawInput.new(@runtime, sid) ensure @runtime.close_raw_input(sid) if sid end |
#read_key ⇒ Object
Single-keypress read with echo + signal keys left on (cbreak): the y/n prompt case. Returns the first byte(s) the user types.
125 126 127 |
# File 'lib/terminalwire/v2/server/context.rb', line 125 def read_key raw_input(mode: Protocol::Mode::CBREAK) { |keys| return keys.read } end |
#remote_ip ⇒ Object
The client's real IP (through Fly/proxies), its User-Agent, and the raw incoming HTTP headers. nil/empty when unknown (e.g. non-HTTP transports).
27 |
# File 'lib/terminalwire/v2/server/context.rb', line 27 def remote_ip = @request[:ip] |
#root_path ⇒ Object
The server-manageable root on the client — the USER subtree (~/.terminalwire/usr), so binary_path = root_path/bin = ~/.terminalwire/usr/bin (installed app launchers). It deliberately does NOT point at ~/.terminalwire: the system bin (the terminalwire-exec engine) and the control dir (authorities) live above this and are off-limits — a server can't reach them even with a grant. A SYMBOLIC tilde path the client expands + enforces; only the privileged terminalwire.com origin is granted ~/.terminalwire/usr/bin/** (installer-seeded).
75 |
# File 'lib/terminalwire/v2/server/context.rb', line 75 def root_path = Pathname.new("~/.terminalwire/usr") |
#stdin ⇒ Object
96 97 98 |
# File 'lib/terminalwire/v2/server/context.rb', line 96 def stdin @stdin ||= Stdin.new(@runtime) end |
#storage_path ⇒ Object
The client directory this origin may persist files into — its sandbox — derived from the first granted path glob (".../**" -> "..."). Returns nil if no writable path was granted. Used like:
context.file.write("#{context.storage_path}/session.json", data)
63 64 65 66 |
# File 'lib/terminalwire/v2/server/context.rb', line 63 def storage_path glob = entitlement && entitlement.dig("paths", 0, "glob") glob && glob.sub(%r{/\*\*\z}, "") end |
#terminal ⇒ Object
The client's live terminal (rows/cols/tty?/color?), kept current by the runtime's read pump as resize frames arrive.
40 |
# File 'lib/terminalwire/v2/server/context.rb', line 40 def terminal = @runtime.terminal |
#user_agent ⇒ Object
28 |
# File 'lib/terminalwire/v2/server/context.rb', line 28 def user_agent = @request[:user_agent] |
#warn(data = "") ⇒ Object
92 93 94 |
# File 'lib/terminalwire/v2/server/context.rb', line 92 def warn(data = "") puts(data, stream: :stderr) end |