Open source

Zetta Com

A LAN intercom. Hold a key, and the people you choose hear you.

v1.4.0Released 10 August 2026MITView on GitHub

No internet. No accounts. No server. No configuration. Start it on two machines on the same network and they find each other; hold F8 and talk. Your voice goes straight from your machine to theirs and touches nothing else on the way.

It runs on an air-gapped network. It runs in a workshop, a warehouse, a studio or a site office — anywhere there is a LAN and people who need to reach each other faster than walking.

The main window: a talk bar, a row of machines to aim at, and the message log.
Idle.
The same window with F8 held — the talk bar filled, reading “Talking to everyone”.
With F8 held.
The shortcut list, with the nine talk and nine message keys collapsed into one row each.
Every shortcut, including any that failed to bind.
Settings: the port, and every machine with its name, address and shortcut number.
Settings holds the port and every machine.
Adding a machine by address, with an optional name.
Anything discovery cannot reach is added by address.
Diagnostics: counters for packets sent, received, lost and rejected.
Counters moving is the whole answer to “is this working”.

Why it exists

There was an earlier version built from batch files, PowerShell and ffmpeg. It worked. Nearly every failure in it came from the substrate rather than from the intercom: PowerShell 5.1-only APIs, BOM parsing, CRLF, a script base64-embedded inside a .bat, an installer that swallowed exit codes.

The one that settled it was the firewall. Inbound block rules killed a machine because the network-facing program was powershell.exe — a shared system binary that anything can get muzzled, silently. A dedicated executable has its own firewall identity, its own prompt and its own rule. That is the whole reason this is an application rather than a script.

What it does

Push to talk

Hold F8 and whoever is selected hears you. Ctrl+1…9 talks to a specific machine, Ctrl+0 to the room. Nothing is transmitted unless the key is held.

It reports absence

A heartbeat every two seconds means the roster greys somebody out within seven of their machine going quiet. Knowing that nobody is listening is worth more than knowing that somebody might be.

It works while you work

The shortcuts are global. You do not alt-tab, you do not click into a window, you do not find a tab — you hold a key in whatever you were already doing.

Finds everyone by itself

mDNS discovery, so there are no addresses to type on a normal network. A machine discovery cannot reach is added by address and named at the same time.

Names you choose

DEVS002 is not Rafi. Name a machine when you add it, then rename, correct or remove it later.

Says which build everyone is on

Each machine puts its version on the wire, so the roster names anyone running something different from you — for keeping a rollout straight, using only what the machines on your own network say.

Decisions, and what they cost

Every one of these is recorded in the repository itself. They are here because a choice explained is worth more than a feature listed.

  1. Unicast, not broadcast

    Opus at 20 ms frames, one frame per UDP datagram, sent to each recipient individually. Broadcast would be one packet instead of many, but some WiFi access points rate-limit or drop broadcast frames — and the failure that produces is one person silently hearing nothing, which is the worst thing an intercom can do. Each remote talker gets its own decoder and its own reorder window, so a missing frame is concealed rather than skipped.

  2. Half duplex, deliberately

    Local playback is muted while you transmit, so the echo path never exists and there is nothing to cancel. Full duplex would have meant acoustic echo cancellation, and that becomes the project rather than a step in it.

  3. No MSI build, on purpose

    The Windows firewall rule is added by an NSIS installer hook, and the MSI has no equivalent here. It produced an install that looked identical to the working one and then walked the user into exactly the failure this application was written to remove. A build that ships and then fails silently is worse than a build that does not exist.

  4. Shortcuts that failed to register are still listed

    A global shortcut can lose a race to another application, and when it does the key silently does nothing — which is otherwise indistinguishable from the app being broken. Every key is listed in the app, including the ones that did not bind.

  5. A room code derived from the key

    Every packet is sealed with ChaCha20-Poly1305, and one that fails to authenticate is dropped without a reply. That makes a mistyped passphrase indistinguishable from a network fault: everything appears to run, the roster is empty, and nothing reports an error. So every machine shows a short room code derived from the key — same code, same room.

  6. No AppImage

    It has to carry its own copy of WebKitGTK and GStreamer to run on a machine that has neither, which made it 79 MB against the 4 MB of a .deb that simply depends on what the distribution already ships.

  7. The log lives on the Rust side

    A machine whose only job is to listen has to come up receiving before any window has loaded, so the log is kept outside the webview. It records which microphone and speakers were chosen, what was bound, and who was heard from — then rotates once it passes a megabyte.

  8. Intel macOS was dropped, and the workflow says why

    GitHub is retiring the Intel macOS runner and the job stopped being scheduled at all — it sat queued indefinitely and held every release open behind it. Apple silicon ships; Intel builds from source.

How it is put together

audio     capture, encode, decode, playback. Knows nothing about sockets.
net       socket, header, peers. Knows nothing about codecs.
session   owns both and wires them together.

Three modules, and the dependency direction is the design: neither half knows the other exists.

Language mix

  • Rust64.9%
  • TypeScript32.7%
  • CSS1.6%
Transport
UDP 9001 — one socket
Discovery
mDNS, UDP 5353
Codec
Opus, 20 ms frames
Encryption
ChaCha20-Poly1305
Duplex
Half, by design
Stored data
One JSON file, local
Telemetry
None
Licence
MIT
  • Rust
  • Tauri 2
  • TypeScript
  • React
  • Opus
  • ChaCha20-Poly1305
  • mDNS

Everything you can type or press

Shortcuts

Every one of these is global — while the app runs it owns Ctrl+0…9 for every program on the machine. That is the price of talking to somebody without leaving what you were doing, and it is why the app’s own actions sit on function keys instead of taking more of that space.

F8
Hold to talk to whoever is selected
Ctrl+1…9
Hold to talk to that machine
Ctrl+0
Hold to talk to everyone
Ctrl+Shift+1…9
Message that machine
Ctrl+Shift+0
Message everyone
F9
Start or stop
Ctrl+Alt+T
Open the window
F1
Show all shortcuts

Where your data lives

One file, on your machine.

Windows
%APPDATA%\com.joy.zetta-com\transport.json
macOS
~/Library/Application Support/com.joy.zetta-com/transport.json
Linux
~/.config/com.joy.zetta-com/transport.json

It holds the port, the machines you added and the names you gave them, the order they sit in, your rebound keys, your presets and the room passphrase. No account, no sync, no analytics, and no call to anything outside your own network. Delete it and the app is back to a first start.

Build it yourself

Windows also needs CMake, which libopus is compiled with. Linux needs libasound2-dev for ALSA.

bun install
bun run tauri build

Each platform builds only its own artifacts; the release workflow in the repository does all of them at once.

Download

v1.4.010 August 2026

The macOS builds are unsigned, so Gatekeeper will call the app damaged on first run. It is not — the repository has the one command that clears it, and the reason the Intel build is missing.