The Problem

Developers and operators need a single, cross‑platform terminal that can host SSH, serial, and Telnet sessions, keep tabs, and support split panes. Existing tools are either platform‑locked (e.g., Windows Terminal) or require multiple binaries (PuTTY + screen). Maintaining separate clients adds configuration drift and hampers automation.

What This Does

tabby bundles a modern terminal emulator with built‑in SSH, serial, and Telnet clients. The core UI lives in tabby-core, while platform‑specific code is in tabby-electron (desktop shell) and tabby-web (self‑hosted web app). Themes and color schemes are provided by tabby-community-color-schemes.

Key entry points:

  • app/lib/app.ts – constructs the main Electron application, registers global hotkeys, and creates windows.
  • tabby-core/src/cli.ts – parses CLI flags and dispatches commands (e.g., new-window, list-profiles).
  • tabby-core/src/services/commands.service.ts – implements the command registry used by the CLI and UI.

Configuration is loaded via app/lib/config.ts, which merges user settings with defaults from tabby-core/src/services/config.service.ts. SSH sessions are created by tabby-ssh/src/session/ssh.ts, which also performs the random‑bytes generation for channel IDs.

How It Is Wired

Execution starts in app/lib/app.ts (init at line 111). The flow is:

  1. init reads the config file (retrieveFilefs.readFile).
  2. It creates the main window (newWindow) which loads the Angular‑style UI bundle built by Webpack (webpack.config.mjs).
  3. UI components subscribe to the subscribe utility (used in 28 places) to react to events such as getAllTabs (22 callers) and showMessageBox (19 callers).
  4. When a user opens an SSH profile, tabby-core/src/services/profiles.service.ts resolves the profile and hands it to tabby-ssh/src/session/ssh.ts. That module calls crypto.randomBytes(16).toString (cryptographic operation) to generate a channel ID, then opens the SSH channel via openShellChannel.
  5. Data from the remote host flows back through tabby-terminal/src/api/baseTerminalTab.component.ts, which exposes output$ and binaryOutput$ streams consumed by the UI renderer.

The most connected modules are tabby-core/src/services/config.service (16 inbound, 6 outbound imports) and tabby-core/src/components/baseTab.component (17 inbound, 3 outbound). Both sit in a circular import cycle, meaning a change to either can trigger wide recompilation and potential runtime surprises. Deep nesting (up to 9 levels) appears in webpack.plugin.config.mjs, config.service.ts, and ssh.ts, making the control flow hard to follow.

How To Use It

# Clone the repo
git clone https://github.com/moses-y/tabby
cd tabby

# Install dependencies (yarn is the lockfile manager)
yarn install          # installs workspace packages
yarn workspace app build   # builds the Electron app
yarn workspace app start   # runs the desktop client

Configuration files live under app/lib/config.ts and are merged with user settings in $HOME/.config/tabby/config.json. No additional environment variables are required beyond the optional .env (which currently contains a secret and must be removed).

Real‑World Use

A CI/CD pipeline can embed Tabby as a headless SSH client to run remote commands on heterogeneous hosts. Example:

import { ProfilesService } from 'tabby-core/src/services/profiles.service';
import { SSHSession } from 'tabby-ssh/src/session/ssh';

async function runRemote(host: string, cmd: string) {
  const profile = await ProfilesService.instance.getProfileByName('default');
  const sess = new SSHSession(profile);
  await sess.connect();
  await sess.exec(cmd);
}

The call chain traverses ProfilesService → SSHSession → openShellChannel → crypto.randomBytes, touching only a handful of modules, which simplifies tracing and debugging.

Code Health & Issues

  • High – Pin GitHub Actions.github/workflows/*.yml uses marvinpinto/action-automatic-releases@latest. Pin to a commit SHA to avoid supply‑chain risk.
  • High – Committed .env – File contains live credentials; remove from history, add to .gitignore, and provide a template (.env.example).
  • High – Eval over runtime valueapp/lib/app.ts executes exec() on a computed string; replace with safe parsing.
  • High – No test suite – 322 source files lack tests; add unit tests for each public entry point and integrate them into CI.
  • Medium – Least‑privilege GITHUB_TOKEN – Workflows lack explicit permissions; declare contents: read at minimum.
  • Medium – Dependency‑vulnerability gate – Add dependency-review-action or osv-scanner to CI.
  • Medium – Large binariestabby-web-demo/data/v86state.bin (23 MB) should be stored in Git LFS or fetched at build time.
  • Medium – Generated output in VCS – Remove the build/ directory from the repo and ignore it.
  • Medium – Persist‑credentials false – Set persist-credentials: false on the checkout step to avoid exposing the token to post‑install scripts.

The Bottom Line

tabby delivers a feature‑rich, cross‑platform terminal with integrated SSH/serial/Telnet support, but the monorepo’s circular imports, deep nesting, and several high‑severity health issues make it risky for production use without remediation. It is best suited for teams willing to invest in refactoring and adding proper tests, or for developers who need a quick‑start UI for remote session management.