Standard I/O

Read and write text to the client's terminal

A command’s standard I/O (stdout, stderr, stdin) is wired to the user’s terminal across the WebSocket. There are two ways to reach it: the helpers imported into a Terminalwire.CLI module, and plain IO.

Output

Inside a command, puts, print, and warn write to the user’s terminal.

puts("Hello, World!")   # writes a line to stdout
print("Working… ")      # no trailing newline
warn("disk almost full") # writes to stderr

Your command process runs with a Terminalwire IO device as its group leader, so the standard IO functions reach the same terminal, including ANSI color and anything built on top of it.

IO.puts(IO.ANSI.format([:green, "✓ done"]))
Owl.IO.puts(["deploying ", Owl.Data.tag("staging", :cyan)])

That’s why a library like Owl works unchanged: it writes to standard IO, and standard IO is the user’s terminal.

Write errors with warn (or Context.warn/2). Don’t write to the :stderr device directly; that goes to your server’s console, not the client’s.

Input

gets reads a line. The prompt is optional.

name = gets("What is your name? ") |> String.trim()
puts("Hello, #{name}!")

read_secret reads a line without echoing it. Use it for passwords and tokens so they don’t appear on screen.

password = read_secret("Password: ")

Piped input

When a user pipes data in (cat data.csv | my-app import), read it through the context rather than gets. Context.read/1 drains stdin to EOF; Context.read_chunk/2 pulls one chunk at a time for streaming.

@desc "Import rows from piped CSV"
def import do
  context()
  |> Terminalwire.Server.Context.read()
  |> String.split("\n", trim: true)
  |> Enum.each(&MyApp.Imports.process/1)
end

Single-keypress and raw terminal input (for prompts, pagers, and TUIs) are covered by Context.read_key/2 and Context.raw_input/3.