Metadata-Version: 2.5
Name: localsendd
Version: 0.1.0
Summary: A LocalSend protocol v2.2 daemon with a JSON-over-unix-socket control API
Project-URL: Homepage, https://github.com/hireri/localsendd
Project-URL: Issues, https://github.com/hireri/localsendd/issues
Author: hireri
License-Expression: MIT
License-File: LICENSE
Keywords: daemon,file-transfer,lan,localsend
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: No Input/Output (Daemon)
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Communications :: File Sharing
Requires-Python: >=3.11
Description-Content-Type: text/markdown

# localsendd

![License](https://img.shields.io/badge/license-MIT-blue.svg)
![Python](https://img.shields.io/badge/python-3.11%2B-3776ab.svg?logo=python&logoColor=white)
![Protocol](https://img.shields.io/badge/localsend-v2.2-00c2a8.svg)

> [!NOTE]
> This is **NOT** a gui. it don't got buttons. If ya want to see it in action, check out [israshell](https://aveline.fish/chisra). <br>
> If you js wanna use localsend, get the official [LocalSend](https://localsend.org) app or some other tui. <br>
> This module aims for scripts, widgets, and whatever else program you'd wanna control localsend from

A [LocalSend](https://localsend.org) peer that runs in the background and talks JSON over a unix socket. One process holds the protocol state, and anything local just connects and asks for things instead of reimplementing multicast discovery and mTLS itself.
Socket's `0600` btw, so that's the whole auth story - nothing to configure, nothing to leak.

**Full v2.2**: multicast discovery with http subnet scan fallback, mutual-TLS
  HTTPS mode, SHA-256 verification, PIN gating, sanitized file names so a peer
  can't write outside `download_dir` or clobber something that's already there.

## Install

```bash
uv tool install localsendd
```

or `pipx install localsendd`

Needs Python 3.11+ and `openssl` on `PATH` for HTTPS mode.

## Run

Add to your autostart / execute:
```bash
localsendd &
```

then from anywhere:

```bash
localsendctl devices --scan
localsendctl send ~/Pictures/brian.png --to "Gassy Mario" --wait
localsendctl watch
```

Something incoming shows up in `localsendctl status`; `localsendctl accept` takes it and `localsendctl reject` turns it down.
Only one transfer moves at a time either direction though - start a second and
you just get `busy` back, no queue.

## Running it supervised

`localsendd` is a foreground process that logs to stdout, so whatever starts it just captures the log. Point your init system at `localsendd` and das it

**Pass `--no-log-time` if your logger already stamps lines** (svlogd,
journald) or you'll get double timestamps.........

## Configuration

defaults are optional, set them in `$XDG_CONFIG_HOME/localsendd/config.toml`:

```toml
alias = "workstation"
device_type = "desktop"      # mobile | desktop | web | headless | server
port = 53317
download_dir = "~/Downloads/LocalSend"
encryption = true            # HTTPS
pin = "1234"
unattended = false           # see "Headless behavior"
```

CLI flags win over everything, then `LOCALSEND_ALIAS`, `LOCALSEND_PIN` and `LOCALSENDD_SOCKET`, then this file. Use the file for `pin` if you can bc `--pin` is visible to every local user in `ps`. `--config PATH` points the daemon at a different file, and [`config.example.toml`](config.example.toml) has every key with comments.
`alias`, `device_type`, `pin`, `encryption` and visibility can be changed at runtime over the socket without a restart, the rest need one. Either way the file itself never hot reloads, and runtime changes aren't written back to it.

## Disabling

`set_discoverable false` (or `localsendctl visible off`) keeps the daemon up and the port bound, but kills multicast announces, stops replying to other peers' announces, and 404s/403s `/info`, `/register`, `prepare-upload`, `prepare-download`, and `download`. Sending out still works.

## Headless behavior

Accepting a transfer needs a human to click accept. With no control client attached, the daemon stays discoverable and answers `/info`, but **declines incoming transfers immediately** instead of parking the sender on a confirmation timeout nobody's there to answer. Sending out still works.

Setting `unattended = true` auto accepts into `download_dir` without confirmation. Now, anything on your network can send files to ya disk, so use a pin unless you enjoy surprises 👀.

## Documentation

[DOCS.md](DOCS.md) has the full control socket protocol reference, the complete configuration table, and a runnable Python client example.

## Development

```bash
PYTHONPATH=src:tests python -m unittest discover -s tests -t tests
```

No dependencies beyond the standard library. Each test spins up a real daemon behind a real HTTP server on an ephemeral port, not mocks.
Multicast and the subnet scan are stubbed out so none of it touches the actual network.

## License

MIT
