Getting Started

Install Terminalwire in a Rails app and build your first terminal

This guide assumes you have Ruby and Rails installed on your workstation.

Create a Rails app

If you don’t already have a Rails app, create one.

rails new my-app
cd my-app

Install Terminalwire in Rails

Add the Terminalwire gems to your app’s Gemfile. Terminalwire ships the v1 server gems and the v2 terminalwire gem from one git source, so a single block pulls in everything you need to serve both the v2 Go client and legacy v1 clients:

# Gemfile
git "https://github.com/terminalwire/ruby", branch: "main", glob: "{gem/terminalwire-*,v2/ruby}/*.gemspec" do
  gem "terminalwire-rails"
  gem "terminalwire", require: false # v2
end

Install them:

bundle install

Then scaffold your terminal classes:

rails g terminalwire:install my-app

The generator writes your terminal classes and a development launcher. It also adds a starter terminalwire gem line and a route—you can remove the duplicate gem line (the block above already includes it) and replace the route with the dual-protocol one shown below.

Configure the terminal

The rails g terminalwire:install my-app command creates files that you may configure for your application’s needs.

Terminal command-line classes

The ./app/terminals folder contains two files that define your command-line interface.

ApplicationTerminal

The application_terminal.rb file is the base class for your terminal application. It includes the Terminalwire::Thor module to enable I/O streaming, sets the basename for help output, and provides helper methods like current_user for authentication.

# ./app/terminals/application_terminal.rb

class ApplicationTerminal < Thor
  # Enables IO Streaming.
  include Terminalwire::Thor

  # The name of your binary. Thor uses this for its help output.
  def self.basename = "my-app"

  private

  def current_user=(user)
    # The Session object is a hash-like object that encrypts and signs a hash that's
    # stored on the client's file system. Conceptually, it's similar to Rails signed
    # and encrypted client-side cookies.
    session["user_id"] = user.id
  end

  def current_user
    @current_user ||= User.find(session["user_id"])
  end
end
MainTerminal

The main_terminal.rb file is where you define your application’s commands. You can add, remove, or modify commands as needed. This file inherits from ApplicationTerminal to access I/O streaming and authentication features.

# ./app/terminals/main_terminal.rb

class MainTerminal < ApplicationTerminal
  desc "hello NAME", "say hello to NAME"
  def hello(name)
    puts "Hello #{name}"
  end

  desc "login", "Login to your account"
  def login
    print "Email: "
    email = gets.chomp

    print "Password: "
    password = getpass

    # Replace this with your own authentication logic; this is an example
    # of how you might do this with Devise.
    user = User.find_for_authentication(email: email)
    if user && user.valid_password?(password)
      self.current_user = user
      puts "Successfully logged in as #{current_user.email}."
    else
      puts "Could not find a user with that email and password."
    end
  end

  desc "whoami", "Displays current user information."
  def whoami
    if self.current_user
      puts "Logged in as #{current_user.email}."
    else
      puts "Not logged in. Run `#{self.class.basename} login` to login."
    end
  end

  desc "logout", "Logout of your account"
  def logout
    session.reset
    puts "Successfully logged out."
  end
end

These commands use Thor, a Ruby command-line parser. You’ll learn more about defining commands, arguments, and options in the Command-Line Parsers chapter.

Routes configuration

Mount the MainTerminal at the /terminal endpoint with Terminalwire::V2::Rails.dual_terminal. This is the URL your client connects to. dual_terminal serves both protocols from the same MainTerminal: it routes the v2 Go client to the v2 server and any legacy v1 client to the v1 handler. It’s the same URL and the same CLI for both. Add the require at the top of the file so the v2 server loads (the terminalwire gem is require: false in your Gemfile).

# ./config/routes.rb
require "terminalwire/v2/rails"

Rails.application.routes.draw do
  match "/terminal",
    to: Terminalwire::V2::Rails.dual_terminal(MainTerminal),
    via: [:get, :connect]
end

Terminalwire binary stub configuration

Terminalwire generates a binary stub in the Rails ./bin folder that you may use to interact with your application in a development environment. You may need to change the host if the server is running on a different port.

!/usr/bin/env terminalwire-exec
url: "ws://localhost:3000/terminal"

Test the integration

To test the integration, restart your Rails server, then run ./bin/my-app hello World in your terminal and you should see the following:

./bin/my-app hello World
Hello World

You can also view all available commands by running:

./bin/my-app help
Commands:
  my-app hello NAME      # say hello to NAME
  my-app help [COMMAND]  # Describe available commands or one specific command
  my-app login           # Login to your account
  my-app logout          # Logout of your account
  my-app whoami          # Displays current user information

Congratulations, you’ve successfully built a Terminalwire command-line application! 🎉

Next steps

At this point you could deploy it to production and distribute it to your users, but first you’ll want to learn how to build more powerful command-line interfaces.

Continue to the next chapter to learn about Command-Line Parsers, where you’ll discover how to define commands with arguments and options, create subcommands, and customize help output.