Eugeny/tabby
A terminal for a more modern age
Single-maintainer risk — review before adopting
- ✓Last commit 1d ago
- ✓5 active contributors
- ✓MIT licensed
- ✓CI configured
- ⚠Small team — 5 top contributors
- ⚠Single-maintainer risk — top contributor 81% of commits
- ⚠No test directory detected
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Embed this verdict
[](https://repopilot.app/r/eugeny/tabby)Paste into your README — the badge live-updates from the latest cached analysis.
Onboarding doc
Onboarding: Eugeny/tabby
Generated by RepoPilot · 2026-05-05 · Source
Verdict
WAIT — Single-maintainer risk — review before adopting
- Last commit 1d ago
- 5 active contributors
- MIT licensed
- CI configured
- ⚠ Small team — 5 top contributors
- ⚠ Single-maintainer risk — top contributor 81% of commits
- ⚠ No test directory detected
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
TL;DR
Tabby is a cross-platform terminal emulator (Windows 10, macOS, Linux) built on Electron with Angular, featuring an integrated SSH/Telnet/Serial client, connection manager, split panes, and Zmodem file transfer. It solves the problem of fragmented tooling by combining a fully configurable terminal emulator with encrypted SSH secret storage, SFTP, and a plugin ecosystem in one app. The main process lives in app/lib/ and the UI is composed of Angular-based plugin packages (tabby-core, tabby-ssh, tabby-terminal, tabby-local, etc.). Monorepo where app/lib/ contains the Electron main process (entry: app/lib/index.ts, PTY management in app/lib/pty.ts, plugin loading in app/lib/pluginManager.ts), and feature functionality is split into peer-dependency packages (tabby-core, tabby-ssh, tabby-terminal, tabby-local, tabby-serial, tabby-settings, tabby-electron, tabby-plugin-manager) each built independently via Webpack. The UI layer uses Angular with Pug templates and SCSS.
Who it's for
Developers, sysadmins, and DevOps engineers who manage remote servers via SSH and want a modern, extensible terminal with built-in connection management, theming, and split panes — especially those on Windows who need WSL/PowerShell/Cygwin/Clink support without switching between multiple tools.
Maturity & risk
Tabby (formerly Terminus) has hundreds of thousands of GitHub downloads and a nightly CI pipeline defined in .github/workflows/build.yml and release.yml, indicating active maintenance. The presence of CodeQL analysis (.github/workflows/codeql-analysis.yml), Mergify, Dependabot, and Stale bot automation signals a well-managed open-source project. It is production-ready for daily terminal use, though the package version is pinned at 1.0.0-alpha.1 which is a legacy artifact rather than a true alpha indicator.
The dependency tree is heavy and native-module-laden: node-pty (^1.2.0-beta.8 — still beta), russh (0.1.37 — Rust-based SSH, relatively new), fontmanager-redux, keytar, and platform-specific optionalDependencies like @tabby-gang/windows-blurbehind all require native compilation and can break on Node/Electron version bumps. The project uses patch-package (postinstall script) suggesting upstream packages needed local patches. Single primary maintainer (Eugeny) is a bus-factor risk for core architectural decisions.
Active areas of work
Active development is visible through the nightly build workflow and Dependabot configuration tracking dependency updates. The russh SSH backend (0.1.37) is a relatively recent Rust-based SSH implementation replacing older JS SSH libraries. The .github/workflows/build.yml and release.yml suggest ongoing release automation, and the funding manifest at .well-known/funding-manifest-urls reflects recent open-source sustainability tooling adoption.
Get running
git clone https://github.com/Eugeny/tabby.git cd tabby npm install npm run build
Then launch Electron against the built output — see HACKING.md for full dev setup including running the Angular watch builds per-package
Daily commands: npm run build # one-time full build npm run watch # incremental Webpack watch mode
Then from a separate terminal: npx electron app (or use .vscode/launch.json for VS Code debugger)
Map of the codebase
app/lib/app.ts— Core Electron main-process entry point that bootstraps the application, manages windows, and wires together all major subsystems.app/lib/window.ts— Defines the BrowserWindow lifecycle, IPC bridge setup, and window state management that every terminal tab depends on.app/lib/pluginManager.ts— Implements the plugin discovery, loading, and dependency-resolution system that drives Tabby's entire extensibility model.app/src/app.module.ts— Angular root module that registers all core providers, services, and plugin injection tokens consumed by every UI component.app/src/entry.ts— Renderer-process bootstrap that initialises Angular and hands off to the root module, making it the true front-end entry point.app/src/entry.preload.ts— Electron preload script that establishes the secure context-bridge surface between the sandboxed renderer and the Node main process.app/lib/config.ts— Handles persistent configuration read/write (YAML via js-yaml) and exposes the typed config schema relied on by every plugin.
How to make changes
Add a new built-in plugin
- Create a new Angular module directory (e.g. tabby-myplugin/) with an NgModule, and register any InjectionToken providers your feature exposes. (
app/src/app.module.ts) - Add the new package name to the static built-in plugin list so the plugin manager discovers it at startup. (
app/src/plugins.ts) - Ensure the package is NOT listed in the blacklist file; if migrating from an old name, add the old name here. (
app/src/pluginBlacklist.ts)
Add a new IPC channel between renderer and main process
- Define the new IPC channel handler in the main process using electron-promise-ipc or ipcMain, and register it during window setup. (
app/lib/window.ts) - Expose the channel invocation through the contextBridge API so the sandboxed renderer can call it safely. (
app/src/entry.preload.ts) - Consume the exposed API in Angular services via the injected preload interface, keeping raw IPC calls out of components. (
app/src/app.module.ts)
Add a new user-configurable setting
- Extend the typed config schema and default values in the config service so the new key is always present with a safe default. (
app/lib/config.ts) - Add migration logic (if changing an existing key shape) inside the config migration section of the same file. (
app/lib/config.ts) - Bind the new config property to a settings UI component in the relevant Angular plugin module (create or edit the settings component inside the appropriate plugin folder). (
app/src/app.module.ts)
Add a new terminal profile type
- Implement a ProfileProvider class in a plugin module, contributing a profile schema and a factory that creates a session. (
app/src/app.module.ts) - Register the PTY session with the main-process PTY wrapper so the OS-level process is spawned correctly. (
app/lib/pty.ts) - Register the new provider in the plugin's NgModule so Angular's DI injects it into the profile manager at runtime. (
app/src/plugins.ts)
Why these technologies
- Electron — Provides cross-platform native window management, system tray, OS notifications, and sandboxed Chromium renderer without requiring separate web server infrastructure.
- Angular — Chosen for its strong dependency-injection system, which maps cleanly onto Tabby's plugin model where providers are contributed and resolved at runtime.
- node-pty — Native Node.js binding to OS pseudo-terminal APIs — the only reliable way to run interactive shell sessions with correct TTY semantics on Windows, macOS, and Linux.
- Webpack (ESM config) — Bundles both main and renderer processes separately with fine-grained control over externals (native Node modules must remain unbundled in the main process).
- js-yaml + atomically — Human-readable config format with atomic write semantics to prevent config file corruption on crash or power loss.
- electron-updater — Handles differential auto-updates with code-signing verification across all three platforms without requiring the user to reinstall the full package.
Trade-offs already made
-
Angular instead of React/Vue for the UI layer
- Why: Angular's hierarchical injector system makes it trivial for plugins to override or extend core services without forking core code.
- Consequence: Steeper learning curve for contributors; larger initial bundle size; slower hot-reload compared to lighter frameworks.
-
Separate Webpack bundles for main and renderer
- Why: Native Node modules (node-pty, serialport) cannot be bundled for the renderer; splitting enforces the correct module boundary.
- Consequence: Two build pipelines to maintain; shared code must be carefully kept in packages imported by both, not in either entry bundle.
-
Plugin system via Angular DI tokens rather than a custom registry
- Why: Reuses Angular's already-present lifecycle and lazy-loading rather than inventing a parallel system.
- Consequence: Plugins are tightly coupled to Angular version; upgrading Angular requires coordinated updates across all plugins.
-
Electron contextIsolation with a preload bridge
- Why: Required by modern Electron security guidelines to prevent renderer-side XSS from gaining full Node.js access.
- Consequence: All main↔renderer communication
Traps & gotchas
Native modules (node-pty, keytar, russh, serialport, fontmanager-redux) must be compiled against the exact Electron version's Node ABI — mismatches cause silent load failures. The patch-package postinstall step applies patches in a patches/ directory; if patches fail to apply (e.g. after upstream updates), the install breaks. On Windows, building requires Python and MSVC Build Tools for node-gyp. The resolutions field in package.json pins nan, node-abi, and node-addon-api specifically to avoid native build regressions — do not bump these without testing all native modules.
Architecture
Concepts to learn
- PTY (Pseudoterminal) — Tabby uses
node-ptyinapp/lib/pty.tsto spawn shells with a real PTY, which is required for interactive programs (vim, htop, readline) to work correctly — without it you'd get a dumb pipe - Electron IPC (Inter-Process Communication) — Tabby splits work between Electron's main process (
app/lib/) and the Angular renderer process;electron-promise-ipcbridges them, and misunderstanding this boundary causes bugs when accessing Node APIs from renderer code - Angular Dependency Injection for Plugin Systems — Tabby's plugin architecture relies on Angular's DI container to allow
tabby-*packages to register and discover services/components at runtime without hard dependencies on each other - Zmodem file transfer protocol — Tabby supports direct file transfer over SSH sessions via Zmodem — a serial file transfer protocol that rides inside a terminal stream, enabling uploads/downloads without SFTP
- Node ABI (Application Binary Interface) versioning — Native Node modules like
node-ptyandrusshare compiled to a specific Node ABI version; Electron ships its own Node version, so modules must be recompiled (or pre-built) for that exact ABI — theresolutionsin package.json exist specifically to manage this - VT/ANSI terminal escape sequences — The
tabby-terminalpackage must correctly parse and render ANSI/VT100/VT220 escape sequences for color, cursor movement, and control codes — bugs here cause garbled output in programs like vim or tmux
Related repos
microsoft/terminal— Direct alternative — Windows Terminal is the Microsoft-built modern terminal for Windows, solving the same problem on one platformvercel/hyper— Close alternative — another Electron + web-tech terminal emulator with a plugin system, same ecosystemEugeny/tabby-web— Companion repo — the self-hosted web app version of Tabby's SSH/SFTP/Telnet clientwarp-tech/warpgate— Ecosystem companion — smart SSH/HTTP/MySQL bastion server explicitly recommended in Tabby's own README for managing remote environmentsxtermjs/xterm.js— Core dependency ecosystem — the terminal rendering library used by tabby-terminal for VT/ANSI emulation in the browser/Electron renderer
PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add unit tests for app/lib/utfSplitter.ts
The utfSplitter.ts file handles UTF byte-stream splitting, which is a critical low-level utility for correct terminal rendering of multibyte characters (CJK, emoji, etc.). There are no test files visible anywhere in the file structure for this module. Bugs here cause silent data corruption in terminal output. A focused test suite would catch regressions when node-pty or rxjs versions are bumped (both of which are in dependencies).
- [ ] Create app/lib/utfSplitter.spec.ts (or a tests directory alongside app/lib/)
- [ ] Write tests covering: ASCII-only input passes through unchanged, a multibyte UTF-8 sequence split across two chunks is correctly reassembled, a 4-byte emoji split across three chunks is handled, and an invalid/truncated byte sequence does not hang or throw
- [ ] Add a 'test' script to app/package.json using Jest or Vitest, including ts-jest for TypeScript support
- [ ] Verify tests run cleanly against the existing utfSplitter.ts implementation without any modifications to production code
- [ ] Document the test command in HACKING.md under a new 'Running Tests' section
Add unit tests for app/lib/lru.ts
app/lib/lru.ts is a custom LRU cache implementation used internally in the Electron main process. Custom data structures are high-risk for subtle off-by-one or eviction-order bugs that only surface under load (e.g., many open terminal tabs). There is no test coverage visible for this file, and it has no external library backing its correctness. Adding tests here directly reduces the risk of memory leaks or stale cache entries corrupting terminal session state.
- [ ] Create app/lib/lru.spec.ts alongside the existing lru.ts
- [ ] Write tests covering: basic get/set, eviction of the least-recently-used entry when capacity is exceeded, access order update on get (item moves to MRU position), behavior on cache size of 1, and correct handling of key overwrite (update value without eviction)
- [ ] Ensure the test file uses the same TypeScript config as app/tsconfig.json by referencing it in the Jest/Vitest config
- [ ] Add or extend the 'test' npm script in app/package.json to include this file
- [ ] Run tests in the existing .github/workflows/build.yml CI pipeline by adding a 'test' step after the install step for the app workspace
Refactor app/lib/window.ts by splitting window lifecycle, IPC handling, and window state persistence into separate modules
app/lib/window.ts is almost certainly a large God-object file: it is the Electron BrowserWindow manager and typically accumulates window creation, IPC channel registration, window state persistence (size/position), tray integration references, and update hooks all in one place. In mature Electron apps this file routinely exceeds 400–600 lines and becomes a major barrier to new contributors. Splitting it into focused modules (window-factory.ts, ipc-handlers.ts, window-state.ts) makes each concern independently testable and easier to review.
- [ ] Audit app/lib/window.ts and catalogue every logical responsibility (window creation, IPC listeners, state save/restore, vibrancy/blur setup via glasstron, remote module setup via @electron/remote)
- [ ] Extract window state persistence (save/restore of bounds) into app/lib/window-state.ts with a clean interface; this integrates with app/lib/config.ts for storage
- [ ] Extract all electron-promise-ipc and ipcMain handler registrations into app/lib/ipc-handlers.ts, accepting the BrowserWindow instance as a parameter
- [ ] Keep app/lib/window.ts as a thin orchestrator that imports and wires the above modules, ensuring the public API (exported functions/classes) remains identical so no call-sites in app/lib/index.
Good first issues
- Add unit tests for
app/lib/lru.ts(the LRU cache implementation has no visible test file and is a self-contained pure logic module). 2. Add JSDoc/TSDoc comments toapp/lib/utfSplitter.ts— the UTF splitter is a non-obvious streaming utility with no inline documentation. 3. Create aCONTRIBUTING.mdor expandHACKING.mdwith a per-platform native module troubleshooting guide, sincenode-pty/keytarbuild failures are the most common new contributor blockers.
Top contributors
- @Eugeny — 60 commits
- @allcontributors[bot] — 5 commits
- @gh-log — 4 commits
- @ruanimal — 3 commits
- @andya1lan — 2 commits
Recent commits
fb9bec8— warn before opening external app-specific URIs (Eugeny)3ab66ee— restore unsigned build (Eugeny)5aa2ea9— fail on webpack exceptions (Eugeny)1f53f00— fail on typing generation errors (Eugeny)56908f4— fix incorrect imports in tabby-ssh (Eugeny)5f047de— docs: add serial-timestamp plugin to community plugins list (#11198) (SnakeEye-sudo)d8fdecd— Windows signing fix (#11186) (Eugeny)811cda4— Update build.yml (Eugeny)9425029— add superm1 as a contributor for code (#11174) (allcontributors[bot])c3ede45— Disable global shortcuts on older plasma versions (#11157) (superm1)
Security observations
- High · Outdated and Potentially Vulnerable npm Dependency (npm@6) —
app/package.json - dependencies.npm. The package.json includes 'npm': '6' as a direct dependency. npm version 6 is end-of-life and has multiple known vulnerabilities including prototype pollution and path traversal issues. Bundling npm itself as a runtime dependency is unusual and significantly increases the attack surface. Fix: Remove npm as a runtime dependency if not strictly required. If needed, upgrade to the latest stable npm version (10+). Audit why npm is bundled as a runtime dependency. - High · Sensitive .env File Present in Repository —
.env. A .env file is present at the root of the repository. This file commonly contains sensitive configuration values such as API keys, secrets, tokens, or database credentials. If committed to version control, these secrets are exposed to anyone with repository access and potentially publicly via GitHub. Fix: Immediately audit the .env file contents. Remove any secrets from version control history using tools like git-filter-repo or BFG Repo Cleaner. Add .env to .gitignore. Use environment variable injection at runtime or a secrets management solution. - High · keytar Credential Storage Attack Surface —
app/package.json - dependencies.keytar. The keytar package (^7.9.0) is used for OS-level credential storage. While keytar itself is legitimate, it interfaces with the system keychain. If the Electron application is compromised (e.g., via XSS leading to RCE in Electron context), an attacker could use keytar to extract stored credentials including SSH keys, passwords, and tokens from the system keychain. Fix: Ensure Electron contextIsolation and nodeIntegration=false are enforced in all renderer processes. Validate that keytar access is sandboxed appropriately and only accessible from the main process via IPC. Keep keytar updated to the latest version. - High · node-pty Remote Code Execution Risk —
app/package.json - dependencies.node-pty. node-pty (^1.2.0-beta.8) is used for pseudoterminal creation. A beta version is in use, which may contain unpatched vulnerabilities. node-pty has historically had vulnerabilities including buffer overflows. More critically, if user-controlled input reaches the PTY without proper sanitization, it can lead to arbitrary command execution in the terminal context. Fix: Upgrade to the latest stable release of node-pty rather than a beta version. Ensure all data passed to the PTY from external sources (SSH, serial, URL handlers) is properly validated. Implement strict input controls in urlHandler.ts and similar entry points. - High · electron-updater Without Code Signing Verification Risk —
app/package.json - dependencies.electron-updater, app/dev-app-update.yml. electron-updater (^5.2.1) handles automatic updates. If update feeds are not properly authenticated or if the update server is compromised, malicious updates could be delivered to users. The presence of app/dev-app-update.yml suggests dev update configurations that could potentially be misconfigured and used in production builds. Fix: Ensure all update packages are code-signed and signatures are verified before installation. Never distribute dev-app-update.yml in production builds. Configure strict update server authentication and use HTTPS endpoints only. - Medium · electron-promise-ipc Potential IPC Injection —
app/package.json - dependencies.electron-promise-ipc. electron-promise-ipc (^2.2.4) facilitates IPC communication between main and renderer processes. If renderer processes are not properly sandboxed (contextIsolation disabled or nodeIntegration enabled), malicious web content could invoke IPC handlers and potentially execute privileged operations in the main process. Fix: Enable contextIsolation and disable nodeIntegration in all BrowserWindow configurations. Validate and sanitize all data received through IPC channels. Use a whitelist approach for allowed IPC messages. Ensure preload scripts minimize exposed APIs. - Medium · URL Handler Potential Injection Risk — ``. The presence of app/lib/urlHandler.ts suggests the application handles custom URL schemes. In Electron applications, URL handlers are a common attack vector where malicious URLs can be crafted to trigger command injection, path traversal, or other attacks if the URL parameters are not strictly validated before use. Fix: undefined
LLM-derived; treat as a starting point, not a security audit.
Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.