The login screen ignored every key while a connect+login attempt ran, so against an unreachable server it sat on a static "Connecting…" for 20s or more, with a hint promising that Esc would quit. While connecting it now shows a spinner: Enter or r restarts the attempt, Esc stops it and returns to the form, q or Ctrl-C quits. Ctrl-C quits from every state of the screen instead of typing a "c" into a field. A failure that got no verdict (unreachable, timed out) keeps the password, so Enter or r retries it; only a rejection, Ok(false) or AuthenticationFailed, clears it. Because a retry can now reuse a password that came from the CLI, env or keychain, entered_via_form compares the credentials with the ones the flow started with instead of trusting any submit, so a retried stored password is never written to the keychain. A restart of an attempt younger than a second is ignored: each attempt rescans the shares and opens a listener, and a held Enter would otherwise start one per key repeat. The attempt result is read before drawing, so a key is never handled against a phase the screen did not show. A stopped or restarted attempt is abandoned, not cancelled: the server actor blocks its own mailbox in connect_timeout, so a stop could not reach it sooner. Its thread runs out its timeouts and drops the client, which disconnects. Until then it holds the listener port, so a restarted session can fall back to an ephemeral port, and should a slow server accept the abandoned login after its replacement got in, the late login displaces the new session. Fixing either needs a cancellable connect, or a bind after the login verdict, in the library.
soulseek-rs
A Soulseek client for the terminal: a full-screen TUI, and a one-shot command surface built for scripts and agents.
Website: https://re-invention.nl/soulseek-rs/
Installation
brew install michel/tap/soulseek-rs # macOS and Linux, prebuilt
cargo install soulseek-rs # anywhere with a Rust toolchain
Usage
Run it with no subcommand for the interactive TUI:
soulseek-rs
Anything else is a one-shot command that runs headless. stdout carries records only, stderr carries progress, and the exit code carries the verdict:
soulseek-rs search <QUERY> # print matching files
soulseek-rs get <QUERY> # search, pick the best, download
soulseek-rs download <USER> <PATH> # fetch one known file
soulseek-rs download --stdin # fetch files listed on stdin
soulseek-rs browse <USER> # list a user's shared files
soulseek-rs serve [--follow] # stay online sharing, stream uploads
soulseek-rs whoami # confirm credentials and connection
soulseek-rs user <NAME> # a peer's status and share counts
soulseek-rs room list|say|users|listen
soulseek-rs message send|read
soulseek-rs shares list|add|remove|status|reindex
soulseek-rs config path|list|get|set
soulseek-rs portmap # test automatic port mapping
soulseek-rs completions install # tab completion for bash/zsh/fish
Add --json for newline-delimited JSON instead of tab-separated text, so
commands compose:
soulseek-rs search "rjd2" --json \
| jq -c 'select(.size > 5000000 and .free_slot)' \
| soulseek-rs download --stdin
Exit codes: 0 success, 2 usage or config, 3 connect or login, 4 nothing
found, 5 timed out, 6 transfer failed, 7 session lost mid-command, 1
unexpected error.
The full README documents every
command, record shape, flag, environment variable and config.toml key.
Building from Source
git clone https://github.com/michel/soulseek-rs.git
cd soulseek-rs
cargo build --release
Library
To build your own Soulseek client, use the soulseek-rs-lib crate: the protocol implementation this client is built on.