lsend — a LocalSend CLI that actually worked

Low-effort AI post: I asked Codex to investigate two projects, build the tool, write the tests and docs, and draft this post. I did the most important manual test: sent a real file to it from LocalSend. It worked.

The code is here: https://github.com/unmanbearpig/lsend.

I wanted a boring CLI for LocalSend: discover devices, send a file, receive a file, and work from a script without opening a TUI. localsend-rs was supposed to do this, but I could never successfully send anything with it.

It turns out there are two different localsend-rs realities. The published 0.1.2 crate has an --auto-accept flag, but the headless receive command does not actually answer the server’s acceptance request. It waits for a TUI that is not running and eventually rejects the transfer. Its PIN option is mostly cosmetic, and its receiver buffers a whole upload in memory before writing it.

The newer GitHub tree fixes some of that — streamed receiving, hashes, safer filenames — but a clean checkout does not build because it points at three CrossCopy packages outside the repository. More importantly, its HTTPS client still does not present a client certificate. Current official LocalSend uses mutual TLS for normal HTTPS transfers, so self-to-self tests can pass while an official peer rejects the connection.

So instead of repairing another LocalSend protocol implementation, lsend wraps a pinned snapshot of the official Rust core. The command layer adds the parts I wanted:

  • lsend discover
  • lsend send --to phone file.zip
  • lsend text --to phone -- "hello"
  • lsend receive --output ~/Downloads
  • JSON Lines output and stable exit codes for scripts

It keeps one persistent certificate identity, presents it during mutual TLS, pins the receiver certificate, and uses the official IPv4/IPv6 discovery and transfer code. Receiving is streamed into a hidden temporary file inside the output directory. The exact size and any supplied SHA-256 hash are checked before the file is published under its final name. Duplicate names are renamed by default instead of silently overwriting something.

There are compatibility tests in both directions — lsend sender to the official core receiver, and official core sender to lsend receiver — plus a real two-process mutual-TLS test. This matters because the main lesson from localsend-rs is that two copies of the same implementation agreeing with each other does not prove that either agrees with LocalSend.

And the actual test:

Incoming transfer from Strong Blueberry: 1 file (5.5 MB)
Accept? [y/N] y
Received 1 file (5.5 MB) from Strong Blueberry in 0.3s.

There were two warnings afterward about shutting down HTTP connections. They are noisy teardown messages from the official server core, after the file was already validated and published — ugly, but not a failed transfer.

It is still an early CLI. Pairing, recursive directories, browser share links, packaged binaries, and a useful network doctor command are not there yet. But the basic thing I needed, sending files to and from a normal LocalSend client, finally works.