There's a lot of open issues
No release in over a year
Stream command-line apps from your server without a web API
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

 Project Readme

Terminalwire for Ruby & Rails

Ship a CLI for your web app. No API required.

Terminalwire streams a command-line app straight from your server. Instead of building a REST/GraphQL API, generating an SDK, and maintaining a separately released client, you write your CLI in your Rails app — and it runs on your users' machines over a single WebSocket.

# app/terminal/main_terminal.rb
class MainTerminal < ApplicationTerminal
  desc "deploy", "Deploy the app"
  def deploy
    puts "Deploying #{current_user.app} to production…"
    # ...your real app code: models, jobs, mailers, the works
    puts "Done. ✅"
  end
end

Your user runs your-app deploy and the command executes on your server, with full access to your database, models, and business logic — while their terminal, files, and browser stay on their machine.

Why this is nice

  • No API to build or version. Your CLI calls your app's code directly. No serializers, no SDK, no client/server version skew to manage.
  • It feels local. Output streams in real time, prompts and passwords work, and it's color/TTY-aware. Your server runs the command; the user's terminal renders it.
  • Secure by construction. The client is the trust boundary: the server requests access to a file, an env var, or the browser, and the client enforces a per-app entitlement policy. Your server never touches the user's machine directly.
  • Auth is just your app's auth. Sessions, current-user, and permissions are whatever your Rails app already does.

The v2 protocol (the terminalwire gem under v2/) adds a lot more — use any CLI library (Thor, Ruby's OptionParser, or bare $stdin/$stdout), output flow control, window resize, Ctrl-C to the server-side command, piping (cat data.csv | your-app import), and raw/ interactive input. The Rails integration for v2 is in progress; the shipping Rails installer today uses the v1 (Thor) runtime.

"I'll just build this myself"

You can! Here's the part that isn't a weekend project:

  • The terminal is a swamp. "Stream stdout" is the easy 10%. The other 90%: window size + live SIGWINCH resize, raw vs. cbreak vs. cooked input, no-echo password reads, color/TTY detection, alt-screen hygiene, piping, and Ctrl-C landing on the server-side command. Terminalwire models the whole terminal — and makes server-side TUI libraries (tty-table, pastel, tty-box, …) render on the user's terminal unmodified. Roll your own and you'll reinvent a worse pty.
  • The trust boundary is subtle. Your server runs with your DB and your secrets; it must NOT get to read ~/.ssh off a user's box. The client has to enforce an origin-scoped sandbox — and getting "origin" right (scheme/port identity, homograph + path-traversal hardening) is exactly the stuff that quietly leaks in a hand-rolled build. Terminalwire's is adversarially tested and fuzzed.
  • Distribution is a product. Users need a client: a signed, cross-platform, self-updating binary you'd build and babysit per app. Terminalwire is one client; your app is a tiny chmod +x file with your URL baked in — users run your-app deploy, no API key, no URL to type.
  • Staying honest across versions. Multiplexing, flow control, capability + version negotiation — a sans-IO protocol with a language-neutral conformance corpus keeps the client and every server (Ruby, Elixir, …) in lockstep. Bespoke JSON-over-WebSocket drifts and rots.

And after all that you've rebuilt the very thing you were avoiding — an API, plus an SDK, plus a client — just to ship one CLI. The point of Terminalwire is to not.

Install (Rails)

Add the gem and run the installer:

# Gemfile
gem "terminalwire-rails"
bundle install
rails generate terminalwire:install my-app   # "my-app" is the launcher name

That generates bin/my-app (the launcher your users run), your CLI in app/terminal/main_terminal.rb, and a /terminal route. Edit the CLI, and your users get the new behavior immediately — there's no client to re-release.

Your users install the client once:

curl -sSL https://terminalwire.sh | bash

How it works

 your users' machine            your Rails server
 ┌────────────────┐   one WS    ┌─────────────────────────────┐
 │ terminalwire    │ ◀────────▶ │ ActionCable / Rack endpoint  │
 │ client (their   │            │   → your Thor (v1) / any-CLI │
 │ terminal, files)│            │     (v2) command, your app   │
 └────────────────┘            └─────────────────────────────┘

The client is a small, fast binary on the user's workstation. Your server runs the CLI and streams terminal I/O — stdout/stderr, prompts, files, the browser — over the wire. (The richer I/O modes — flow control, piping, raw/interactive input — are part of the v2 protocol; see v2/.)

What's in this repository

  • v2/ruby/ — the modern (v2) open-source server runtime, the terminalwire gem. This is where active development happens. See the v2 README for wiring details (Rails/ActionCable, Rack, Thor, OptionParser).
  • gem/ — the v1 gems (terminalwire, terminalwire-rails, …), still published and maintained.

There are servers for other languages too — e.g. Elixir. They all speak the same wire protocol and interoperate with the same client.

Documentation

License

Source-available. Free for personal use and small/early-stage businesses; commercial use is licensed — see LICENSE.txt or https://terminalwire.com/license.

Contributing

Bug reports and pull requests are welcome at https://github.com/terminalwire/ruby. Please follow the code of conduct.