Skip to content
slastraPublic

About

Command-line tool to list, switch, close, and open tabs across Firefox, Chrome, and other browsers

Topics

Resources

Stars

20 stars

Watchers

0 watching

Forks

Repository files navigation

tabctl

Command-line tool to list, switch, close, and open tabs across Firefox, Chrome, and other browsers.

tabctl-promo.mp4

Control browser tabs from the command line using D-Bus IPC.

Features

  • D-Bus Architecture - Fast, reliable inter-process communication
  • Multi-Browser Support - Firefox, Zen, Chrome, Chromium, Brave, Brave Origin, and Helium work simultaneously
  • Core Commands - List, close, activate, navigate, and open tabs across browsers
  • Desktop Switching - Automatic window focus across virtual desktops
  • Multiple Output Formats - TSV, JSON, simple
  • Clean Architecture - Minimal dependencies, production ready

Install

Requirements

  • Go 1.24+ (to build from source)
  • D-Bus session bus
  • Browser with native messaging support

Arch Linux (AUR)

yay -S tabctl
# or
paru -S tabctl

After installation, set up native messaging:

tabctl install

From source

  1. Build the binaries:
git clone https://github.com/slastra/tabctl.git
cd tabctl
make build

Both binaries land in build/. Use make build rather than a bare go build: it stamps the version, which tabctl status reports, and it keeps every build in one place. The native messaging manifest records an absolute path to the mediator, so binaries in two locations means the browser can keep launching an old one after you rebuild.

  1. Install native messaging host:
./build/tabctl install
  1. Install the browser extension:
  1. Restart browser to activate native messaging

Uninstalling

tabctl uninstall removes the native-messaging manifest from every supported browser, or one with --browser. Run it before removing the package; the manifests live in your home directory, so the package manager cannot remove them. Remove the extension from the browser itself.

Usage

# List all tabs from all browsers
tabctl list

# List tabs from specific browser
tabctl list --browser Firefox
tabctl list --browser Brave

# Activate a tab (switches desktop if needed!)
tabctl activate firefox.1.2          # Firefox tab
tabctl activate brave.1234.5678      # Brave tab
tabctl activate helium.1874583011.1874583012  # Helium tab

# Load a URL in an existing tab (keeps its position, pin and history)
tabctl navigate firefox.1.2 https://example.com
tabctl navigate firefox.1.2 https://example.com && tabctl activate --focused firefox.1.2

# Close tabs
tabctl close firefox.1.2 firefox.1.3
echo "brave.1234.5678" | tabctl close

# Open URLs in new tabs (prints the new tab IDs)
tabctl open https://example.com https://github.com
echo "https://example.com" | tabctl open
tabctl open --browser Firefox https://example.com  # when multiple browsers are connected

# Show mediator/extension versions and protocol compatibility
tabctl status

Tab ID format

Tab IDs are prefixed with the lowercased browser name so multiple browsers (e.g. Brave + Helium) stay distinguishable:

  • <browser>.<window_id>.<tab_id>
  • Examples: firefox.1.2, helium.1874583011.1874583012, brave.999.42, chrome.123.45

Release channels and editions count as separate browsers, since they install side by side with their own profiles: bravebeta.999.42, braveorigin.1207522592.1207522593.

Tab IDs are ephemeral. list prints them and activate, navigate or close consume them. Do not store them.

Output formats

# JSON output
tabctl list --format json

# Simple format (titles only; cannot be mapped back to tab IDs)
tabctl list --format simple

# Custom delimiter
tabctl list --delimiter ","

docs/usage.md covers several profiles of one browser and the favIconUrl field in JSON output. docs/troubleshooting.md covers "No browsers found on D-Bus" and other failures.

Related projects

Building on tabctl? COMPATIBILITY.md lists what stays stable across releases (output formats, exit statuses, tab IDs, the D-Bus interface, release asset names) and what may change.

Tools built on tabctl, including tab switchers and launchers:

  • tabstrip. A tab strip daemon for Hyprland. It shows the tabs of every browser window on the current workspace as clickable chips with favicons. It reads tabctl over D-Bus and updates live on the TabsUpdated signal. It feeds waybar and Quickshell from the same snapshot.
  • lastshell. A Quickshell desktop shell whose bottom bar renders the tabstrip snapshot as a live tab strip, clicks and scroll included.
  • vicinae-tabctl. A Vicinae launcher extension for switching tabs through tabctl.
  • dms-tabs-launcher. A DankMaterialShell plugin that lists and activates tabs, grouped by browser.

How it works

Browser Extension ← Native Messaging → tabctl-mediator ← D-Bus → tabctl CLI
  • tabctl - Command-line interface
  • tabctl-mediator - Native messaging host with D-Bus server
  • Browser Extensions - Firefox (Manifest V2) and Chrome (Manifest V3) extensions
  • D-Bus Services - dev.slastra.TabCtl.Firefox, dev.slastra.TabCtl.Brave

The native-messaging protocol is a JSON-RPC 2.0 subset with a version handshake. Because the extension updates through the browser stores and the tabctl binaries update through the AUR, the two can briefly be out of sync after a release. Update both to matching versions together.

Both sides surface a mismatch so it never fails silently:

  • If the mediator is newer, tabctl status reports it and any tab command fails with a clear "update" error.
  • If the extension is newer (it handshakes but the mediator is too old to answer), the extension shows a red ! badge on its toolbar icon. Hover it for the "update the tabctl package" hint.

ARCHITECTURE.md has the full design.

Development

make build

A bare go build works too, but it writes wherever you point -o and skips the version stamping, so tabctl status reports dev. Keep every build in build/ if you have a mediator installed: the native messaging manifest holds an absolute path, so a second copy elsewhere means the browser can keep launching the old one.

go test ./...
make test               # Go tests, race tests and the extension tests
make test-integration   # end-to-end, needs dbus and python3
make test-contract      # CLI, D-Bus and wire output against testdata/contract
make lint

Credits

Inspired by BroTab, rewritten in Go with a D-Bus architecture and a Manifest v3 Chrome extension.

License

MIT © 2025 Shaun Lastra. See LICENSE.

About

Command-line tool to list, switch, close, and open tabs across Firefox, Chrome, and other browsers

Topics

Resources

Stars

20 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages