A federated, p2p communication platform. Chat with your friends from anywhere in the world!
  • Rust 99.2%
  • PowerShell 0.5%
  • Shell 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
ramhaurg b5c2913fc8
All checks were successful
release / detect (push) Successful in 8m52s
release / release (push) Has been skipped
tui: sync Cargo.lock version to 0.8.5
2026-08-18 12:05:34 -04:00
.forgejo/workflows ci: fix starling dep patching in release workflow 2026-08-14 19:37:13 -04:00
.github/workflows ci: install libopus-dev and pkg-config in GitHub detect job 2026-08-14 22:30:17 -04:00
.grit fix: roost peer discovery and chat routing 2026-07-29 12:14:54 -04:00
src tui: fix multi-party calls — per-peer audio decode, last-peer call end, hang-up teardown 2026-08-18 12:05:08 -04:00
.gitignore bump 0.5.8 2026-07-23 20:32:52 -04:00
build.rs fix: add rustc-link-lib=opus for system libopus linkage 2026-08-06 12:17:40 -04:00
Cargo.lock tui: sync Cargo.lock version to 0.8.5 2026-08-18 12:05:34 -04:00
Cargo.toml tui: bump version to 0.8.5 2026-08-17 23:03:41 -04:00
install.ps1 fix: try/catch checksum 2026-07-30 19:03:01 -04:00
install.sh fix: multi-arch install.ps1, stricter checksum catch 2026-07-29 22:03:38 -04:00
LICENSE fix: roost peer discovery and chat routing 2026-07-29 12:14:54 -04:00
README.md fix: use raw/branch/main URLs for install one-liners 2026-07-29 16:26:42 -04:00

Starling TUI

This is the terminal (TUI) client for Starling — a federated peer-to-peer communications platform. For the main project, see the Starling repository.

⚠️ Early access — under active development. This is the working client, but it's young and changing fast. Expect rough edges and breaking changes between versions. Bug reports and feedback are welcome.

A federated peer-to-peer communications platform where peers — known as birds — can communicate from anywhere in the world thanks to a peer-to-peer network called the murmuration.

Starling TUI runs in the terminal and provides text chat, voice calls, and video calls — all end-to-end encrypted. Birds discover each other through the murmuration using iroh's relay and discovery infrastructure — no central server required. A room code is all a new bird needs to join a flock.

Platform support

Feature Windows macOS Linux WSL2
Text chat
Voice calls (mic + playback) ✓†
Video calls (webcam)
Room codes
Persistent identity

† WSL2 voice requires a one-time setup step (starling profile) that installs the ALSA→PulseAudio bridge. See WSL2 setup below.

WSL2 does not expose webcams by default — use a native Windows build for video calls.


Install

Recommended: install the starling launcher, then:

starling install tui

Or install standalone — no Rust or compiler needed:

Windows (PowerShell):

irm https://forgejo.hearthhome.lol/Saltfault/Starling-TUI/raw/branch/main/install.ps1 | iex

Linux / macOS:

curl -sSfL https://forgejo.hearthhome.lol/Saltfault/Starling-TUI/raw/branch/main/install.sh | bash

The installer places starling-tui in your PATH. Run starling-tui to launch. Upgrade: re-run the same command, or starling update tui. Uninstall: install.sh --uninstall.

Configure your profile (one-time):

starling profile

This opens a setup wizard where you enter your display name, select your microphone and speaker, and get a 32-digit profile code. The code encodes your name and can be used to restore your profile on another machine. The profile is saved to disk automatically.

Run it:

starling open

The header shows a room code like ▀▄ BIRD-00CCFF-00CCFF-... with colored half-block swatches. Share it with another bird — they join with:

starling join BIRD-00CCFF-00CCFF-...

Roosts (headless servers)

A roost is a persistent bird that stays online to keep a community's chat history and channels for late-joiners — like your own Discord server, except it runs on your own machine over the peer-to-peer murmuration.

Roost commands come from the server component (starling-server), which you add with starling install server. You then run them through the same starling command:

starling install server              # one-time, adds the roost commands
starling roost create my-community   # mints identity + database, prints invite code
starling roost open   my-community   # stays online until Ctrl+C

From the TUI, you join a roost exactly like any other flock — with its invite code:

starling join BIRD-...

Once joined, the roost's channels appear in the flock rail. See the Starling-Server README for the full roost command reference.

Developing? You can also clone and run from source:

git clone https://forgejo.hearthhome.lol/Saltfault/Starling-TUI.git
cd Starling-TUI
cargo run -- open

Command reference

Everything runs through one command: starling (installed from the main repo, then starling install tui). Roost commands require starling install server.

Command Status Does
starling open Launch the TUI. (Bare starling prints help — use starling open)
starling join <code> Launch and join a flock or roost by invite code
starling profile Run the profile + audio + dependency wizard
starling leave <code> Print how to leave (in the TUI, just close with Esc)
starling list List roosts stored on disk
starling doctor Diagnose config, identity, profile, and dependencies
starling logs Print the log file location
starling install tui Install (or reinstall) the terminal client
starling update tui Update the client to the latest version
starling tui uninstall Uninstall the client and remove its config
starling help Print usage

Inside the running app, joining more flocks, switching between them, calls, and video are all keybindings — see Keybindings below.


Platform setup

Before installing Starling, you need Rust and a C compiler. Follow the section for your platform. Then run starling profile to configure your profile, audio devices, and any platform-specific dependencies.

Windows

1. Install Visual Studio C++ Build Tools (provides the MSVC compiler):

Download from visualstudio.microsoft.com. In the installer, select "Desktop development with C++".

2. Install Rust:

Download and run rustup-init.exe from the Rust website. This installs rustc, cargo, and everything needed to build Rust projects.

3. Install Starling:

cargo install --git https://forgejo.hearthhome.lol/Saltfault/Starling.git
starling install tui

Pre-built binaries (no Rust or compiler required) are planned — see the releases page for available downloads.

4. Run:

starling open

Audio uses WASAPI (Windows Audio Session API) — works out of the box, no extra audio packages needed. Video uses Windows Media Foundation via nokhwa.

macOS

1. Install Xcode Command Line Tools (provides the C compiler):

xcode-select --install

2. Install Homebrew if you don't have it:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

3. Install Rust:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"

4. Install Starling:

cargo install --git https://forgejo.hearthhome.lol/Saltfault/Starling.git
starling install tui

5. Run:

starling open

Audio uses CoreAudio — works out of the box, no extra audio packages needed. Video uses AVFoundation via nokhwa.

Linux (native)

1. Install system dependencies:

# Debian / Ubuntu
sudo apt install build-essential pkg-config libasound2-dev libpulse-dev libclang-dev

# Fedora
sudo dnf install gcc pkgconf-pkg-config alsa-lib-devel pulseaudio-libs-devel clang-devel

# Arch Linux
sudo pacman -S base-devel pkgconf alsa-lib pulseaudio clang

2. Install Rust:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"

3. Install Starling:

cargo install --git https://forgejo.hearthhome.lol/Saltfault/Starling.git
starling install tui

4. Run:

starling open

Audio uses PulseAudio (with ALSA fallback) — works out of the box on most Linux desktops. Video uses V4L2 via nokhwa.

Package Why it's needed
build-essential / base-devel C compiler (gcc) for native code
pkg-config Locating ALSA and PulseAudio libraries at build time
libasound2-dev ALSA headers — cpal compiles the ALSA backend on Linux
libpulse-dev PulseAudio headers — cpal's preferred backend at runtime
libclang-dev Required by nokhwa for V4L2 webcam support

WSL2 (Windows Subsystem for Linux)

WSL2 setup is identical to Linux, with one extra step for audio.

1. Install WSL2 (if not already installed, from PowerShell):

wsl --install

2. Inside WSL, install system dependencies:

sudo apt update
sudo apt install build-essential pkg-config libasound2-dev libpulse-dev libclang-dev

3. Install Rust:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"

4. Install Starling:

cargo install --git https://forgejo.hearthhome.lol/Saltfault/Starling.git
starling install tui

5. Run profile (configures the WSL2 audio bridge):

starling profile

starling profile installs libasound2-plugins and writes /etc/asound.conf to route ALSA through PulseAudio. This is needed because the pure-Rust PulseAudio crate that cpal uses can't authenticate with WSLg's server, but the C library (libpulse) that ALSA's pulse plugin uses can.

If you skip this step, text chat works but voice calls won't.

6. Run:

starling open

If you're on an older Windows 10 build without WSLg, audio won't work in WSL2 — use a native Windows build instead.

Webcam on WSL2: Webcams are not exposed by default. To use video calls from WSL2, set up USB passthrough (run in Windows PowerShell as Admin):

winget install usbipd
usbipd list                    # find your camera's BUSID
usbipd bind --busid <BUSID>
usbipd attach --wsl --busid <BUSID>

Then in WSL2:

sudo apt install linux-tools-generic usbip hwdata
sudo update-usbids
ls /dev/video*                  # should show your camera

Alternatively, use a native Windows build for video calls.


Running Starling

Start a new flock

starling open

The app starts and the header shows a room code with color swatches:

▀▄ ▀▄ ▀▄ ▀▄ ▀▄ ▀▄ ▀▄ ▀▄ ▀▄ ▀▄ ▀▄  BIRD-00CCFF-00CCFF-00CCFF-00CCFF-00CCFF-00CCFF-00CCFF-00CCFF-00CCFF-00CCFF-00CCFF

Share this code with another bird so they can join your flock.

Join an existing flock

starling join BIRD-00CCFF-00CCFF-...

Join multiple flocks

Once inside the app, you can join additional flocks at any time by clicking the [Join] button or typing /join <code> in the message input and pressing Enter. Join a roost via [Menu] → Join Roost, or with /join-roost <code>. A rail appears on the left side of the screen showing all joined flocks and roosts. Use Alt+↑ and Alt+↓ to switch between them. Each flock has its own message list and end-to-end encryption key.

 ── flocks ──        ╔════════════════════════════════════╗
╔════════════╗       ║ BIRD-00CCFF-... . 3 birds          ║
║> BIRD-…    ║       ║ Alice: hello!    ╔════════════════╗║
║  BIRD-…    ║       ║ Bob: hi there   ║ birds           ║║
╚════════════╝       ╚═════════════════╩══════════════════╝
 ── roosts ──
╔════════════╗
║  my-chat   ║
║  dev-room  ║
╚════════════╝

You start in your home flock automatically. Joining a new flock does not leave the current one — you remain subscribed to all of them simultaneously.

Set your name

When you start Starling for the first time, a popup asks for your display name — the name other birds see next to your messages in the flock. Type it and press Enter to join the murmuration. You can change it later via the Menu button ([Menu] → Profile) or by running starling profile.

Logs

Errors are written to logs/latest.log. On each launch, the previous log is gzipped to logs/<timestamp>.log.gz. Check this file if something isn't working.


Interface

Starling TUI is operated through on-screen buttons and the Menu popup. Buttons are clickable with the mouse. The bottom toolbar shows the main actions:

Button Action
[Create] Create a new room (flock)
[Join] Join a flock by invite code
[Menu] Open the action menu
[Quit] Exit Starling

Keyboard shortcuts

Key Action
Enter Send typed message (or /join <code> / /join-roost <code>)
Alt+↑ Switch to previous flock/roost
Alt+↓ Switch to next flock/roost
Esc Open the action menu
Backspace Delete last character

Menu actions

Clicking [Menu] or pressing Esc opens the action menu with these options:

Menu Item Action
Create Room Open a new flock
Join Flock Join an existing flock by code
Join Roost Join a roost by invite code
Create Roost Create a new roost (runs starling roost create)
Invite Show the current flock's invite code
Next Peer Cycle selection to the next bird
Toggle Mute Mute or unmute the microphone
Toggle Video Show or hide the webcam feed
Call / Hang Up Start a voice call or hang up
Profile Exit and run the profile wizard (starling profile)
Quit Exit Starling

Use arrow keys (/) to navigate the menu and Enter to activate. Click any item with the mouse to activate it directly. Click outside the menu to close it.


Architecture

┌──────────────────────────────────────────────────────────────────┐
│ main.rs (UI loop)                                                │
│   keyboard → Command ──┐                                         │
│   AppEvent ←───────────┤──── mpsc channels ────┐                │
│   playback ← VoiceFrame│                       │                │
│   video_frame ← VideoFrame                     │                │
└────────────────────────┊────────────────────────┊───────────────┘
                         ▼                        ▼
┌──────────────────────────────────────────────────────────────────┐
│ net.rs (network task)                                            │
│   gossip for chat · QUIC datagrams for voice                     │
│   QUIC uni streams for video · flock map (many flocks)           │
│   mic capture (voice.rs) → call.rs                               │
│   webcam capture (video.rs) → call.rs                            │
│   sync.rs — history backfill for late joiners                    │
└──────────────────────────────────────────────────────────────────┘

The TUI is a pure client — it never runs a roost. The roost/ module here holds only the protocol types (e.g. RoostState) the client reads off a roost's control channel to render the channel rail. The actual roost server lives in starling-server.

Source layout

File Responsibility
main.rs Event loop, keyboard handling, subcommand dispatch
event.rs Command (UI→net) and AppEvent (net→UI) types
net.rs Owns the iroh endpoint, flock map, voice/video handlers
call.rs Opens/accepts QUIC streams for voice datagrams and video
voice.rs Mic capture: cpal input → Opus encoder → channel
playback.rs Audio output: channel → Opus decoder → ring buffer → cpal output
video.rs Webcam capture: nokhwa → JPEG frames → channel, terminal rendering
opus_ffi.rs Safe Rust wrappers around the pre-built Opus C library
ui.rs Terminal rendering and UI state (App struct)
setup.rs Setup wizard TUI for profile and device configuration
config.rs Profile struct, disk persistence, 32-digit code, persistent identity key
crypto.rs E2E encryption (ChaCha20-Poly1305) for gossip messages
logger.rs File logger with gzipped log rotation
util.rs Platform utilities (stderr suppression on Unix)
sync.rs History backfill: asks a peer for recent messages on join
roost/ Roost protocol types the client reads to render the channel rail (the server itself lives in starling-server)
build.rs Downloads pre-built Opus static libraries from shiguredo/opus-rs

How the murmuration works

Birds connect to the murmuration through iroh's global relay network and node discovery. No central server coordinates them:

  1. A bird opens a flock — their persistent node ID becomes the room code (e.g. BIRD-00CCFF-00CCFF-...). The gossip topic and E2E encryption key are both derived from this code via SHA-256.
  2. Other birds join by entering the same room code — they subscribe to the same gossip topic and derive the same encryption key.
  3. iroh's relay connects both peers on the topic automatically. No node IDs or addresses need to be exchanged beyond the room code.
  4. Text messages broadcast over gossip reach all birds in the mesh.
  5. Voice calls are direct peer-to-peer QUIC datagram streams — no relay needed if direct connectivity is available, with relay fallback.
  6. Video calls use QUIC unidirectional streams carrying JPEG frames.

Audio is encoded as 48 kHz stereo Opus, 20 ms frames (960 samples per channel), sent as QUIC datagrams. Playback uses a 2-second ring buffer to absorb network jitter.

All text messages are end-to-end encrypted with ChaCha20-Poly1305 using a key derived from the room code. Each flock gets its own encryption key, so messages from different flocks are isolated cryptographically. Voice and video calls are E2E encrypted via iroh's QUIC TLS 1.3. Relays and intermediaries cannot read message content.

Persistent identity

Starling saves your node's secret key to ~/.config/starling/identity.key on first launch. This means your room code stays the same every time you open a flock — other birds can bookmark your code and rejoin later without you needing to share a new one.


Troubleshooting

No microphone / no audio output (WSL2)

Run the one-time audio setup:

starling profile

This installs libasound2-plugins and writes /etc/asound.conf to route ALSA through PulseAudio. See WSL2 setup for details.

If it still doesn't work, verify PulseAudio is running:

ls /mnt/wslg/PulseServer   # should exist
echo $PULSE_SERVER          # should show unix:/mnt/wslg/PulseServer

If you don't have WSLg (older Windows 10), audio won't work in WSL2 — use a native Windows build instead.

You need the Visual Studio C++ Build Tools. Reinstall them and make sure "Desktop development with C++" is selected.

libclang not found (Linux / WSL2)

sudo apt install libclang-dev   # Debian/Ubuntu
sudo dnf install clang-devel    # Fedora
sudo pacman -S clang            # Arch

This is required by nokhwa for webcam support.

No webcam detected (WSL2)

WSL2 doesn't expose USB webcams by default. Set up USB passthrough:

In Windows PowerShell (as Admin):

winget install usbipd
usbipd list                     # find your camera's BUSID
usbipd bind --busid <BUSID>
usbipd attach --wsl --busid <BUSID>

In WSL2:

sudo apt install linux-tools-generic usbip hwdata
sudo update-usbids
ls /dev/video*                   # should show your camera

Then re-run starling profile to detect the camera.

Build is slow on first compile

The Opus codec is downloaded as a pre-built static library on the first build. Subsequent builds are cached. Expect 25 minutes for the initial build.

Check the logs

Errors are written to logs/latest.log. On each launch, the previous log is gzipped to logs/<timestamp>.log.gz.


License

Apache 2.0