# Ruby on Rails

Use Thor with Rails to deliver a delightful command-line application to users

Terminalwire is "Hotwire for command-line interfaces"—it streams Terminal I/O over WebSockets to a thin-client command-line application running on your users' workstations. All you do is run `bundle add terminalwire-rails && rails g terminalwire:install my-app`, build your CLI on the server, then distribute it via the one-liner curl installer.

Each chapter below in this guide walks you through everything you need to ship awesome command-line apps to your users.


## Getting Started

This guide assumes you have [Ruby](https://www.ruby-lang.org/en/documentation/installation/) and [Rails](https://guides.rubyonrails.org/getting_started.html) installed on your workstation.

## Create a Rails app

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

```sh
$ rails new my-app
$ cd my-app
```

## Install Terminalwire in Rails

Add the Terminalwire Rails gem to your app.

```sh
$ bundle add terminalwire-rails
```

Then install Terminalwire in your Rails app.

```sh
$ rails g terminalwire:install my-app
```

## 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.

```ruby
# ./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.

```ruby
# ./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](http://whatisthor.com), a Ruby command-line parser. You'll learn more about defining commands, arguments, and options in the [Command-Line Parsers](./cli) chapter.

### Routes configuration

The `./config/routes.rb` file mounts the `MainTerminal` in a `Terminalwire::Thor::Server` WebSocket server to the `/terminal` endpoint. This is the URL your client will connect to.

```ruby
# ./config/routes.rb
Rails.application.routes.draw do
  match '/terminal',
    to: Terminalwire::Thor::Server.new(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.

```sh
#!/usr/bin/env terminalwire-exec
url: "ws://localhost:3000"
```

## 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:

```sh
$ ./bin/my-app hello World
Hello World
```

You can also view all available commands by running:

```sh
$ ./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](./distribution), 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](./cli), where you'll discover how to define commands with arguments and options, create subcommands, and customize help output.

## Working with Thor

[Thor](http://whatisthor.com) 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.

```ruby
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.

```ruby
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:

```ruby
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:

```ruby
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.

```ruby
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:

```sh
$ 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:

```ruby
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:

```ruby
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:

```ruby
# ./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:

```sh
$ 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:

```sh
# 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`:

```ruby
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:

```ruby
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:

```ruby
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](./sessions), which allow you to maintain state between commands—essential for implementing authentication and user-specific functionality.


## Sessions

Sessions are essential for allowing users to login to your application. Like Rails, Terminalwire uses sessions to manage state between commands, but there are some differences that are covered after the big picture.

# Working with sessions

Sessions are accessible in Thor via the `session` method. In this example, a `current_user` getter and setter wrap the `uaer_id` key in sesion.

```ruby
# Learn how to use Thor at http://whatisthor.com.
class ApplicationTerminal < Thor
  # .. Code ...
  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 sytem. 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
```

When a session is access from the server, it streams the JWT file from the users workstation to your server. The server then decrypts and validates the contents of the token with the `Rails.application.secret_key_base` and then reads the contents of the token. If the token is invalid, the server will raise an error that you'll need to handle.

Writing session data encryptes the hash as a JWT, then streams it back to the users workstation and writes it to the `~/.terminalwire/authorities/example.com/storage/session.jwt` file.

# Bulk session changes

Since each change to a session requires a round-trip between the server and the client, it's best to make bulk changes to the session. This is done by passing a block to the `Session#edit` method.

```ruby
session.edit |data|
  data["uuid"] = SecureRandom.uuid
  data["id"] = user.id
  data["expires_at"] = 1.month.from_now
end
```

This code will make all the changes to the session in a single round-trip to the client.

# Destroying sessions

To reset a session, you can call the `Session#destroy` method. This deletes the session file from the users workstation.

# Implementation details

Different frameworks deal with sessions differently, which is why Terminalwire tries to model its session management after Rails.

## Sessions are JavaScript Web Tokens

Sessions are stored in a JavaScript Web Token, or JWT,

 on your users workstation at `~/.terminalwire/authorities/$DOMAIN/storage/session.jwt`. The file is encrypted and signed using the `Rails.application.secret_key_base` as the secret. Because it's encrypted, users can't read or tamper with the contents of the session token. If users try to tamper with a token, the server will raise an error.

## Stored in the client's file system

Sessions are similar to cookies in web browsers in that they're stored on the client's file system. The server stores session files on the client's workstation at `~/.terminalwire/authorities/$AUTHORITY/storage/session.jwt` where `$AUTHORITY` is the domain and non-default port of the server.

## Encrypted and signed

Session files are encrypted on the server using the `Rails.application.secret_key_base` as the default secret. This means that users can't read or tamper with the contents of the session file. If users try to tamper with a session file, the server will raise an error.

## No expiration or IDs

Unlike Rails, Sessions do not have an ID or an expiration. You can do this manually by storing the expiration and ID in the session.

```ruby
session.edit do |data|
  data["expires_at"] = 1.month.from_now
  data["uuid"] = SecureRandom.uuid
end
```

Then, in your application code you can check for the existence of the `expires_at` key and compare it to the current time to see if the session has expired.


## Authentication

At some point you'll want people to login to your terminal application so they can securely access their data from the command-line. Terminalwire can be configured an infinite number of ways to authenticate users, but for the sake of brevity we'll focus on the most common methods:

1. Email and password authentication
2. Web browser authentication

But first, let's briefly look at sessions to understand how the client maintains it's login state.

# Email and password authentication

The simplest way to authenticate users is to use email and password authentication. This method is secure and easy to implement.

```ruby
# ./app/terminals/main_terminal.rb
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
```

The important method is `getpass`, which reads the users input without showing it on screen preventing users from exposing their passwords to people who may be looking at their screen.

The `gets` method is used to get the persons username, which is shown on screen.

Finally, these two inputs are passed into the same authentication logic you'd use in your Rails controller to authenticate users.

# Web browser authentication

Authentication isn't always as simple as email and password. For more complex authorization workflows, you may want to use a web browser to authenticate users. This is useful if you want to use OAuth, SAML, or other authentication methods.

Web-based authentication requires two main components:

1. A command in Terminalwire that launches a web browser and sends the user to a URL where they can authenticate.

2. A controller in your Rails app that handles the authentication and sends the user back to the terminal application.

## Terminalwire authentication command

The Terminalwire command will launch a web browser and send the user to a URL where they can authenticate. The URL will contain a token that the Rails app can use to authenticate the user. This token should be a secure nonce that expires after a short period of time.

```ruby
# ./app/terminals/main_terminal.rb
class MainTerminal < ApplicationTerminal
  desc "login", "Login to application"
  def login
    # Create a nonce
    nonce = SecureRandom.hex
    # Encrypt it with Rails secret key
    verifier = ActiveSupport::MessageVerifier.new(Rails.application.secret_key_base)
    # Generate a token that expires in 10 minutes
    token = verifier.generate(nonce, expires_in: 10.minutes)
    # Generate a URL that the user can visit to authenticate
    url = new_terminal_session_url(token:)

    puts "Opening #{url} in your browser..."
    # How sign it with Rails and gem it into the URL...
    client.browser.launch(new_terminal_session_url(token:))

    # Wait for the user to authenticate. This is a blocking call that waits
    # fot the user to successfully authenticate in the browser.
    authorization = ActiveExchange::Channel.new(name: "auth:#{nonce}")

    if user_id = JSON.parse(authorization.read).fetch("user_id")
      self.current_user = User.find(user_id)
      puts "Welcome #{current_user.email}"
    else
      puts "Well that didn't work"
    end
  end
end
```

The `new_terminal_session_url(token:)` URL launches the users browser to a page in your Rails application that asks the user to "Approve" or "Deny" the authorization request.

## Terminal authentication controller

The webpage launched by the Terminalwire command will be handled by a controller in your Rails app. The example controller provided below shows how to handle the authentication request and send the user back to the terminal application.

```ruby
# ./app/controllers/terminal/sessions_controller.rb
class Terminal::SessionsController < ApplicationController
  # If the user is not logged in, this will force them to login before they
  # can authorize the terminal app.
  before_action :authorize_user

  # Enabled inline rendering of the views below.
  layout false
  include Superview::Actions
  View = DeveloperView

  # Display a form asking the user to Approve or Deny the terminal app.
  class New < View
    def title = "Authorize command line app"
    def subtitle = "The terminal app is requesting access to your account. Do you approve?"

    def view_template
      form action: url_for(action: :create), method: :post, class: "flex flex-row gap-4" do
        input type: "hidden", value: helpers.params.fetch("token"), name: "token"
        input type: "submit", class: "btn btn-primary", value: "Approve"
        input type: "cancel", class: "btn btn-error", value: "Deny"
      end
    end
  end

  # Display a success message after the user has approved the terminal app.
  class Show < View
    def title = "Successfully authorized command line app"
    def subtitle = "You may now close this window and continue using the command-line interface."

    def view_template
    end
  end

  # The New view submits a form to this action. If the user approves the terminal authorization
  # it will send a message to the terminal app with the user's ID to the nonce they're listening on.
  def create
    verifier = ActiveSupport::MessageVerifier.new(Rails.application.secret_key_base)
    nonce = verifier.verified(helpers.params[:token])

    message = JSON.generate(user_id: current_user.id)
    ActiveExchange::Channel.new(name: "auth:#{nonce}").broadcast(message)
    redirect_to terminal_session_url
  end
end
```

The `authorize_user` method is a before_action that checks if the user is logged in before they can authorize the terminal app. If the user is not logged in, they will be redirected to the login page.

This implementation may work differently in your application depending on the authentication method or libraries you're using. For example, if you're using OAuth, you may need to redirect the user to the OAuth provider's login page instead of your own login page.


## Standard I/O

Terminal applications use Standard I/O, frequently notated as `stdio`, to read and write text to the terminal. For those who are new to working with consoles, `stdio` typically consists of three streams: `stdin`, `stdout`, and `stderr`.

All of these methods are available in your [Thor commands](./cli) and work seamlessly with Terminalwire's streaming architecture.

## Output text with `stdout`

"Standard out" is where a program writes its output. In ruby, when you run `puts`, it implicitly writes to `$stdout.puts`, which displays text in the users terminal.

Terminalwire replaces `$stdout.puts` in Thor with `Terminalwire::Server::Resource::STDOUT#puts` to stream output over WebSockets to the client. This allows you to write to the terminal as you would in a normal terminal application.

```ruby
# Print "Hello, World!" to the terminal and append a newline
puts "Hello, World!"
# Print "Hi!" to the terminal without a newline
print "Hi!"
```

## Read text `stdin`

"Standard in" is where a program reads its input. In ruby, when you run `gets`, it implicitly reads from `$stdin.gets`, which reads text from the users terminal.

```ruby
print "What is your name?: "
# Read a line of text from the terminal
name = gets.chomp # chomp removes the newline character
# Print a greeting to the terminal
puts "Hello, #{name}!"
```

### Reading passwords via `getpass`

When you need to read a password from the terminal, you can use the `getpass` method in Thor. This method will prompt the user for a password and return the value as a string *without* showing the users input on their terminal. This prevents people from seeing the password as it is typed.

```ruby
puts "Login to your account"
# Read a line of text from the terminal
password = getpass # chomp removes the newline character
# Check if the password is correct.
if password == "password"
  puts "You are logged in"
else
  puts "Incorrect password"
end
```

## Display error message with `stderr`

"Standard error" is where a program writes its error messages. In ruby, when you run `warn`, it implicitly writes to `$stderr.puts`, which displays text in the users terminal.

```ruby
# Write an error message to the users terminal
warn "Oops, An error occurred"
```


## Files & Directories

Terminalwire makes it possible to read and write files on the client's file system, but only if they grant access.

# Security

First let's talk about security. Be default, Terminalwire servers can only read/write files to the `~/.terminalwire/authorities/$AUTHORITY/storage` path. This is primarly used to store session tokens, similar to how web browsers store cookies on workstations.

All other paths are denied access. Only the client can grant access to other paths on the client's file system.

# Files

Working with files on a client's file system is similar to working with files on a server. You can read, write, and delete files. Here's some code:

```ruby
# Write a file to the client's file system at
# ~/.terminalwire/authorities/$AUTHORITY/storage/hello.txt
context.file.write storage_path.join("hello.txt"), "Hello, World!"

```

# Directories

Working with directories on a client's file system is similar to working with directories on a server. You can create, delete, and list directories. Here's some code:

```ruby
# List all of the files in the client's file system in their Terminalwire storage path.
context.dir.glob storage_path
```


## Environment Variables

# Security

By default, ENV vars are denied access to Terminalwire servers. Only the client can grant access to ENV vars on the client's workstation.

The only ENV var that's allowed by default is `TERMINALWIRE_HOME`, which is required for [session to work properly](sessions).

# Reading variables

You can read an ENV var from the client's workstation by using the `env` device. Here's an example:

```ruby
# Access the ENV hash
context.ENV["TERMINALWIRE_HOME"]
# Or use the env device
context.environmnent_variables.read("TERMINALWIRE_HOME")
```


## Web Browser

Terminalwire can launch web pages from the command-line interface. This is useful for opening URLs in the client's default web browser from a command-line interface. For example:

1. Opening authentication pages so users can login to your app via OAuth, Single Sign-On (SSO), Okta, "Login with Google", "Login with Apple", or any other web-base login flow.
2. Launch web pages when users run commands, like `bin/blog post open 23` could open `https://example.com/blog/23`.
3. Display an "authorize command-line" page to users when they start using your terminal app.

# Launching web browsers

The `browser.launch` accepts a URL as an argument and opens it in the client's default web browser. For example, to launch the root of your Rails app when a user runs `bin/my-app open` you'd implement the following code:

```ruby
class MainTerminal < ApplicationTerminal
  desc "open", "Open in browser."
  def open
    # Launch's the root URL of a Rails app from the console
    browser.launch root_url
  end
end
```

## Rails URL helpers

The `terminalwire-rails` integration includes all the Rails URL helpers that you'd expect in Rails views, like `root_url` and everything listed under `bin/rails routes`.

The `default_url_options[:host]` is set to the host of the current request the Terminalwire client uses to connect to the server. To change these settings, you may override the `default_url_options` method in your `ApplicationTerminal`:

```ruby
class ApplicationTerminal < Thor
  include Terminalwire::Thor

  def default_url_options
    { host: 'your-custom-host.com' }
  end
end
```

# Security

By default, the `browser` resource can only launch `http` and `https` URLs. This is to prevent malicious code from opening other types of URLs, like `file`, that could harm the client's workstation.


## Distribution & Licensing

Licensing and distributing your Terminalwire app is designed to be as easy and as low friction as possible. There's basically two things you need to do to distribute your app:

1. Acquire a license
2. Distribute the app

# Get a license

Terminalwire Server is proprietary software that requires a license to run in production. You can get a license by signing up for a [Terminalwire account](/developer/licenses) and [requesting a license](/developer/licenses) with the URL of your web application.

Licenses are free for projects with under $100,000/year in revenue or for organizations with under $1,000,000 in gross assets. This makes them perfect for personal projects, hobbies, or startups that are just getting off the ground. If you're an established larger organization, you'll need to purchase a commercial license, which starts at $99/month.

Licenses can be obtained from the [Terminalwire account page](/developer/licenses) or the command-line.

```sh
# Request a Pro license for a commercial web application
$ terminalwire license request https://mega-corp.example.com/terminal \
  --product pro
# Request a Core license for a personal application
$ terminalwire license request https://hobbyist.example.com/terminal \
  --product core
```

If a commercial license is requisted from the command line, the browser will open to collect payment.

# Distribute the app

Once you have a acquired server license, you can distribute your app to users. There are a few ways to distribute your app:

1. One-liner curl installer
2. Terminalwire directory

You'll want to make sure you have a license before distributing your app to users; otherwise they'll see a warning message that your server is not licensed when they run the app.

The application directory makes it possible for users to install your app via `terminalwire install my-app`. You can list your Terminalwire application from the [Distribution Dashboard](/developer/distributions) or by running the following command:

```sh
# Creates a distribution for the app at https://example.com/terminal
$ terminalwire distribution create my-app \
  --url https://example.com/terminal
```

## One-liner curl installer

The one-liner curl installer is the best way to distribute your app to your users. It installs the Terminlwire client, configures the users' shell initialization files, and installs your app with one line of bash script.

```sh
curl -sSL https://my-app.terminalwire.sh | bash
```

Replace the `my-app` with the `binary name` of your application.

## Terminalwire directory

Your users may then install this app by running:

```sh
# How customers install your app
$ terminalwire install my-app
```

Distributions listed in the directory appear on [Terminalwire.com/applications](/applications) and include an installation page you can share with your customers, like [this installation page for TinyZap](https://terminalwire.com/applications/tinyzap).

# One-off installations by URL

Terminalwire applications may be installed by running:

```sh
$ terminalwire install \
  --url https://example.com/terminal \
  --name my-app
```

This creates the same binary file as the application being installed from the Terminalwire directory and places it in  `~/.terminalwire/bin/my-app`. The user can then run `my-app` to start the application.
