Terminalwire.CLI (Terminalwire v0.1.0)

Copy Markdown View Source

A small command router so your CLI reads like the commands themselves — public functions become commands, their parameters become arguments, and @desc becomes help. It's the Elixir analog of Ruby's Thor desc/def.

defmodule MyApp.CLI do
  use Terminalwire.CLI, name: "my-app"

  @desc "Greet someone by name"
  def hello(name) do
    puts("Hello, #{name}!")
  end

  @desc "Deploy to an environment"
  def deploy(env) do
    confirm = gets("Deploy to #{env}? [y/N] ")
    if String.trim(confirm) == "y", do: puts("Deploying…"), else: puts("Aborted")
  end
end

Mount it like any handler — use generates run/1:

WebSockAdapter.upgrade(conn, Terminalwire.WebSock, [handler: &MyApp.CLI.run/1], [])

Then my-app hello Ada calls hello("Ada"), my-app deploy staging calls deploy("staging"), and my-app (or my-app help) prints a generated command list. An unknown command or wrong argument count exits non-zero with a usage hint.

How it dispatches

  • The first argument is the command; it's matched to a @desc-annotated public function with the same name and the same number of remaining arguments.
  • Functions without a @desc are ordinary helpers, not commands.
  • A command's return value sets the exit code when it's an integer; otherwise 0.

Talking to the terminal

Inside a command, use Terminalwire.CLI imports terminal helpers bound to the current session — puts/1, print/1, warn/1, gets/1, read_secret/1, env/1 — so you write puts("hi") instead of threading a context around. Bare IO.puts and any standard-IO library (like Owl) also stream to the user, because the handler's group leader is a Terminalwire IO device. For everything else on the context (files, directories, the browser, the raw terminal), context/0 returns the Terminalwire.Server.Context:

@desc "Import a CSV from the user's machine"
def import(path) do
  data = Terminalwire.Server.Context.file_read(context(), path)
  puts("imported #{byte_size(data)} bytes")
end

Scope

This is a router, not a full option parser — it handles commands and positional arguments. For flags, options, and richer parsing, write a plain run/1 handler and reach for a library like Optimus; the two approaches use the exact same Context.

Summary

Functions

The current command's Terminalwire.Server.Context (for files, env, browser, the raw terminal).

Read an environment variable from the user's machine (entitlement-gated).

Prompt (optional) and read a line from the user's stdin.

Write to the user's stdout without a trailing newline.

Write a line to the user's stdout.

Prompt (optional) and read a line without echo (passwords).

Write a line to the user's stderr.

Functions

context()

The current command's Terminalwire.Server.Context (for files, env, browser, the raw terminal).

env(name)

Read an environment variable from the user's machine (entitlement-gated).

gets(prompt \\ nil)

Prompt (optional) and read a line from the user's stdin.

print(data)

Write to the user's stdout without a trailing newline.

puts(data \\ "")

Write a line to the user's stdout.

read_secret(prompt \\ nil)

Prompt (optional) and read a line without echo (passwords).

warn(data \\ "")

Write a line to the user's stderr.