Working with Thor

Define commands, arguments, & options for your CLI

Thor is a Ruby command-line parser that makes it easy to build powerful command-line applications. Terminalwire uses Thor to define commands, parse arguments, and generate help output for your terminal applications.

This chapter covers the basics of working with Thor to build your command-line interface.

Defining commands

Commands are defined in your terminal classes by using the desc method followed by a regular Ruby method. The method name becomes the command name that users type in their terminal.

class MainTerminal < ApplicationTerminal
  desc "hello", "Say hello"
  def hello
    puts "Hello, World!"
  end
end

Users can now run bin/my-app hello to execute this command.

Commands with arguments

Commands can accept arguments by adding parameters to the method. Required arguments are defined as method parameters, and Thor will automatically parse them from the command line.

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

Users run this with bin/my-app hello Alice, which outputs “Hello, Alice!”.

Multiple arguments

You can define multiple arguments by adding more method parameters:

class MainTerminal < ApplicationTerminal
  desc "greet GREETING NAME", "Greet NAME with a custom GREETING"
  def greet(greeting, name)
    puts "#{greeting}, #{name}!"
  end
end

Users run this with bin/my-app greet "Good morning" Alice.

Optional arguments

Optional arguments can be defined by providing default values:

class MainTerminal < ApplicationTerminal
  desc "hello [NAME]", "Say hello to NAME (or World if not provided)"
  def hello(name = "World")
    puts "Hello, #{name}!"
  end
end

Users can run bin/my-app hello or bin/my-app hello Alice.

Option flags

Options, also called flags, allow users to pass additional configuration to commands. They’re defined using the option method and accessed via the options hash.

class MainTerminal < ApplicationTerminal
  desc "hello NAME", "Say hello to NAME"
  option :enthusiastic, type: :boolean, aliases: "-e", desc: "Add emphasis"
  def hello(name)
    greeting = "Hello, #{name}"
    greeting += "!" if options[:enthusiastic]
    puts greeting
  end
end

Users can run:

bin/my-app hello Alice
Hello, Alice
bin/my-app hello Alice --enthusiastic
Hello, Alice!
bin/my-app hello Alice -e
Hello, Alice!

Option types

Thor supports several option types:

class MainTerminal < ApplicationTerminal
  desc "configure", "Configure the application"
  option :port, type: :numeric, default: 3000, desc: "Port number"
  option :host, type: :string, default: "localhost", desc: "Host name"
  option :verbose, type: :boolean, aliases: "-v", desc: "Verbose output"
  option :environment, type: :string, enum: ["development", "production"], desc: "Environment"
  def configure
    puts "Host: #{options[:host]}"
    puts "Port: #{options[:port]}"
    puts "Environment: #{options[:environment]}" if options[:environment]
    puts "Verbose mode enabled" if options[:verbose]
  end
end

Required options

Options can be marked as required:

class MainTerminal < ApplicationTerminal
  desc "deploy", "Deploy the application"
  option :environment, type: :string, required: true, desc: "Target environment"
  def deploy
    puts "Deploying to #{options[:environment]}..."
  end
end

Thor will show an error if users forget to provide required options.

Subcommands

For larger applications, you can organize commands into subcommands by creating separate Thor classes and registering them:

# ./app/terminals/ticket_terminal.rb
class TicketTerminal < ApplicationTerminal
  desc "list", "List all support tickets"
  def list
    puts "Fetching your support tickets..."
  end

  desc "create SUBJECT", "Create a new support ticket"
  def create(subject)
    puts "Creating ticket: #{subject}"
  end

  desc "show ID", "Show details for a ticket"
  def show(id)
    puts "Showing ticket ##{id}..."
  end
end

# ./app/terminals/main_terminal.rb
class MainTerminal < ApplicationTerminal
  desc "ticket SUBCOMMAND", "Manage support tickets"
  subcommand "ticket", TicketTerminal
end

Users can run:

bin/my-app ticket list
Fetching your support tickets...
bin/my-app ticket create "Need help with API"
Creating ticket: Need help with API
bin/my-app ticket show 123
Showing ticket #123...

Help output

Thor automatically generates help output for your commands. Users can view help by running:

Show all commands
bin/my-app help
Show help for a specific command
bin/my-app help hello

Customizing the basename

The basename is what appears in help output as your command name. You can customize it in your ApplicationTerminal:

class ApplicationTerminal < Thor
  include Terminalwire::Thor

  def self.basename = "my-app"
end

Long descriptions

You can add longer descriptions to commands using the long_desc method:

class MainTerminal < ApplicationTerminal
  desc "deploy", "Deploy the application"
  long_desc <<-LONGDESC
    Deploy the application to the specified environment.

    This command will:
    * Build the application
    * Run tests
    * Deploy to the target environment
    * Run post-deployment checks

    Example:
    $ my-app deploy --environment production
  LONGDESC
  option :environment, type: :string, required: true
  def deploy
    puts "Deploying to #{options[:environment]}..."
  end
end

Class-level configuration

Thor provides several class-level methods for configuring your terminal:

class MainTerminal < ApplicationTerminal
  # Set the namespace for subcommands
  namespace :main

  # Define a default command when none is provided
  def self.default_task = "help"

  # Hide commands from help output
  no_commands do
    def helper_method
      # This won't appear as a command
    end
  end
end

Best practices

When building your Thor commands, follow these best practices:

  1. Keep command names short and memorable - Users will type these frequently
  2. Use clear, descriptive help text - Good help text makes your CLI self-documenting
  3. Validate input early - Check arguments and options at the start of your methods
  4. Provide helpful error messages - Tell users what went wrong and how to fix it
  5. Use options for configuration - Reserve arguments for primary data like names and IDs
  6. Group related commands into subcommands - This keeps your main command list manageable

Next steps

Now that you understand how to work with command-line parsers, you can build powerful command-line interfaces. The next chapter covers Sessions, which allow you to maintain state between commands—essential for implementing authentication and user-specific functionality.