docs: drop the em-dashes from the README

Thirty-eight of them, in three roles that each wanted a different fix: label
separators in lists became colons, table cells standing in for "no value"
became the word they meant (required, unset, not a config key), and the
mid-sentence asides became commas, parentheses, or two sentences.
This commit is contained in:
Michel de Graaf
2026-07-25 03:18:37 +02:00
parent 13c58feafe
commit d84360a803
+43 -43
View File
@@ -18,7 +18,7 @@ by music enthusiasts around the world to share niche music. This repository is
that client plus [`soulseek-rs-lib`](./soulseek-rs-lib), the protocol library it that client plus [`soulseek-rs-lib`](./soulseek-rs-lib), the protocol library it
is built on. is built on.
**[re-invention.nl/soulseek-rs](https://re-invention.nl/soulseek-rs/)** — what it **[re-invention.nl/soulseek-rs](https://re-invention.nl/soulseek-rs/)**: what it
does, how to install it, and every `config.toml` setting. does, how to install it, and every `config.toml` setting.
## 🎥 Demo ## 🎥 Demo
@@ -27,20 +27,20 @@ does, how to install it, and every `config.toml` setting.
## Features ## Features
- **Search & download** — search the network and queue downloads in the TUI, - **Search & download**: search the network and queue downloads in the TUI,
or fetch a track in one command with `get` or fetch a track in one command with `get`
- **Sharing** — `shares add` a directory and your files show up in searches; - **Sharing**: `shares add` a directory and your files show up in searches;
`serve` stays online so peers can browse and download them `serve` stays online so peers can browse and download them
- **Browse** — list any user's shared files and download straight from the tree - **Browse**: list any user's shared files and download straight from the tree
- **Chat rooms** — list, join, and talk in public rooms, several open at once - **Chat rooms**: list, join, and talk in public rooms, several open at once
- **Private messages** — send and receive messages, with an inbox in the TUI - **Private messages**: send and receive messages, with an inbox in the TUI
- **Firewalled peers** — downloads and browsing fall back to server-brokered - **Firewalled peers**: downloads and browsing fall back to server-brokered
connections when a peer can't be reached directly connections when a peer can't be reached directly
- **Automatic port mapping** — opens your listen port via UPnP-IGD and - **Automatic port mapping**: opens your listen port via UPnP-IGD and
NAT-PMP, with a `portmap` subcommand to test your router NAT-PMP, with a `portmap` subcommand to test your router
- **TUI and CLI** — a full terminal interface, plus a one-shot command surface - **TUI and CLI**: a full terminal interface, plus a one-shot command surface
built for scripts and agents: every feature is reachable without a terminal, built for scripts and agents, where every feature is reachable without a
stdout carries records only, and the exit code says what happened terminal, stdout carries records only, and the exit code says what happened
## Project Goals ## Project Goals
@@ -163,11 +163,11 @@ first: the config commands need no account at all, and `whoami` answers
Eleven of these need no credentials, because they never touch the network: Eleven of these need no credentials, because they never touch the network:
`config path|list|get|set`, `shares list|add|remove`, `portmap` and `config path|list|get|set`, `shares list|add|remove`, `portmap` and
`skills install|uninstall|list`. Note that `skills install|uninstall|list`. Note that
`shares status` and `shares reindex` are not among them — they report what the `shares status` and `shares reindex` are not among them, because they report
network will actually see, which means logging in and letting the library scan what the network will actually see, which means logging in and letting the
the folders. library scan the folders.
Every command emits records except the four that perform an action — Every command emits records except the four that perform an action.
`room say`, `message send`, `shares add` and `shares remove` say nothing on `room say`, `message send`, `shares add` and `shares remove` say nothing on
stdout and answer with their exit code alone. `room listen`, `message read` stdout and answer with their exit code alone. `room listen`, `message read`
and `serve` stream records as they arrive, until `--duration` seconds pass or, and `serve` stream records as they arrive, until `--duration` seconds pass or,
@@ -211,9 +211,9 @@ column layout does not: `search` adds `duration`, `slots`, `speed` and
`port`. Fields the server never answered are `null` rather than absent, so a `port`. Fields the server never answered are `null` rather than absent, so a
missing reply cannot be misread as a zero. missing reply cannot be misread as a zero.
`download --stdin` reads back either shape it emits — a JSON object per line, `download --stdin` reads back either shape it emits: a JSON object per line,
or tab-separated text whose first field is the user and last field is the or tab-separated text whose first field is the user and last field is the
path — so `search` and `browse` pipe straight into it. path. That is why `search` and `browse` pipe straight into it.
#### Recipes #### Recipes
@@ -261,9 +261,9 @@ binary, so a `cargo install` is all you need to hand it to your agent:
soulseek-rs skills install soulseek-rs skills install
``` ```
That writes `SKILL.md` into every agent it finds in your home directory — That writes `SKILL.md` into every agent it finds in your home directory,
currently Claude Code (`~/.claude/skills`) and opencode currently Claude Code (`~/.claude/skills`) and opencode
(`~/.config/opencode/skill`) — and prints one record per target: (`~/.config/opencode/skill`), and prints one record per target:
``` ```
installed claude /home/you/.claude/skills/soulseek-rs/SKILL.md installed claude /home/you/.claude/skills/soulseek-rs/SKILL.md
@@ -272,7 +272,7 @@ unchanged opencode /home/you/.config/opencode/skill/soulseek-rs/SKILL.md
Running it again is how you update after upgrading the binary; an identical Running it again is how you update after upgrading the binary; an identical
copy reports `unchanged` rather than rewriting it. `skills list` shows what is copy reports `unchanged` rather than rewriting it. `skills list` shows what is
there without touching anything, and `skills uninstall` removes it again — there without touching anything, and `skills uninstall` removes it again, but
only when the file it finds is the one this binary wrote. only when the file it finds is the one this binary wrote.
For an agent this table does not cover, name the directory yourself, or commit For an agent this table does not cover, name the directory yourself, or commit
@@ -298,8 +298,8 @@ soulseek-rs shares reindex # after adding files on disk
soulseek-rs serve --follow soulseek-rs serve --follow
``` ```
Uploads are only visible inside a running `serve` — every other command is a Uploads are only visible inside a running `serve`, because every other command
short-lived process with nothing to serve — so there is no separate is a short-lived process with nothing to serve, so there is no separate
`uploads list`. Managing transfers across commands (pause, resume, a queue `uploads list`. Managing transfers across commands (pause, resume, a queue
that outlives one invocation) needs the resident daemon that `serve` would that outlives one invocation) needs the resident daemon that `serve` would
grow into; it is not there yet. grow into; it is not there yet.
@@ -314,9 +314,9 @@ file, in that order of precedence. Flags work before or after the subcommand.
| Flag | Environment variable | `config.toml` key | Default | | Flag | Environment variable | `config.toml` key | Default |
| ---- | -------------------- | ----------------- | ------- | | ---- | -------------------- | ----------------- | ------- |
| `--username` | `SOULSEEK_USERNAME` | `username` | — (required) | | `--username` | `SOULSEEK_USERNAME` | `username` | required |
| `--password` | `SOULSEEK_PASSWORD` | — (never stored in the file) | — (required) | | `--password` | `SOULSEEK_PASSWORD` | never stored in the file | required |
| `--password-cmd` | `SOULSEEK_PASSWORD_CMD` | `password_cmd` | — | | `--password-cmd` | `SOULSEEK_PASSWORD_CMD` | `password_cmd` | unset |
| `--server` | `SOULSEEK_SERVER` | `server` | `server.slsknet.org:2416` | | `--server` | `SOULSEEK_SERVER` | `server` | `server.slsknet.org:2416` |
| `--download-dir` | `SOULSEEK_DOWNLOAD_DIR` | `download_dir` | `<Downloads>/Soulseek` | | `--download-dir` | `SOULSEEK_DOWNLOAD_DIR` | `download_dir` | `<Downloads>/Soulseek` |
| `--shared-dir` (repeatable) | `SOULSEEK_SHARED_DIR` | `shared_dir` / `shared_dirs` | the download dir | | `--shared-dir` (repeatable) | `SOULSEEK_SHARED_DIR` | `shared_dir` / `shared_dirs` | the download dir |
@@ -324,7 +324,7 @@ file, in that order of precedence. Flags work before or after the subcommand.
| `--no-listener` / `--listener` | `SOULSEEK_NO_LISTENER` | `disable_listener` | listener on | | `--no-listener` / `--listener` | `SOULSEEK_NO_LISTENER` | `disable_listener` | listener on |
| `--max-concurrent-downloads` | `SOULSEEK_MAX_CONCURRENT_DOWNLOADS` | `max_concurrent_downloads` | `5` | | `--max-concurrent-downloads` | `SOULSEEK_MAX_CONCURRENT_DOWNLOADS` | `max_concurrent_downloads` | `5` |
| `--search-timeout` | `SOULSEEK_SEARCH_TIMEOUT` | `search_timeout` | `10` | | `--search-timeout` | `SOULSEEK_SEARCH_TIMEOUT` | `search_timeout` | `10` |
| `--log-file` | `SOULSEEK_LOG_FILE` | — | stderr | | `--log-file` | `SOULSEEK_LOG_FILE` | not a config key | stderr |
Boolean environment variables accept the usual `1`/`0`/`true`/`false`/`yes`/`no`. Boolean environment variables accept the usual `1`/`0`/`true`/`false`/`yes`/`no`.
`--config <FILE>` (or `SOULSEEK_CONFIG`) reads a different config file and `--config <FILE>` (or `SOULSEEK_CONFIG`) reads a different config file and
@@ -335,9 +335,9 @@ config and state directories wholesale. Every variable above is also read from
a `.env` file in the working directory, which is often the tidiest way to hand a `.env` file in the working directory, which is often the tidiest way to hand
a container its credentials. a container its credentials.
`config get` and `config set` cover the nine settings the file can hold — `config get` and `config set` cover the nine settings the file can hold
`username`, `server`, `listener_port`, `disable_listener`, `download_dir`, (`username`, `server`, `listener_port`, `disable_listener`, `download_dir`,
`shared_dirs`, `max_concurrent_downloads`, `search_timeout`, `password_cmd` — `shared_dirs`, `max_concurrent_downloads`, `search_timeout`, `password_cmd`),
and name the lot back at you when you ask for something else. Setting a key to and name the lot back at you when you ask for something else. Setting a key to
an empty string clears it, and `shared_dirs` takes a comma-separated list. an empty string clears it, and `shared_dirs` takes a comma-separated list.
Every wait expressed in seconds (`--search-timeout`, each `--timeout`, each Every wait expressed in seconds (`--search-timeout`, each `--timeout`, each
@@ -348,12 +348,12 @@ hanging until the heat death of the universe.
Four ways to supply a password, in the order they are consulted: Four ways to supply a password, in the order they are consulted:
1. `--password-stdin` — read the first line of stdin, like `docker login`. 1. `--password-stdin`: read the first line of stdin, like `docker login`.
2. `--password` / `SOULSEEK_PASSWORD` — convenient, but visible in `ps`. 2. `--password` / `SOULSEEK_PASSWORD`: convenient, but visible in `ps`.
3. The **OS keychain** (macOS Keychain, Windows Credential Manager, Linux 3. The **OS keychain** (macOS Keychain, Windows Credential Manager, Linux
Secret Service), under service `soulseek-rs` and your username. The TUI Secret Service), under service `soulseek-rs` and your username. The TUI
stores your password here after a successful login. stores your password here after a successful login.
4. `password_cmd` / `--password-cmd` — a shell command whose stdout is the 4. `password_cmd` / `--password-cmd`: a shell command whose stdout is the
password, e.g. `pass show soulseek`. Best for headless boxes with no password, e.g. `pass show soulseek`. Best for headless boxes with no
keychain. keychain.
@@ -377,7 +377,7 @@ soulseek-rs message read --duration 60 # or --follow to run until killed
In the interactive TUI: In the interactive TUI:
- press `m` to compose — type `<recipient> <message>` and `Enter` to send; - press `m` to compose, then type `<recipient> <message>` and `Enter` to send;
- press `i` to open the inbox popup listing sent and received messages - press `i` to open the inbox popup listing sent and received messages
(incoming messages arrive automatically while the TUI is open). The `i` (incoming messages arrive automatically while the TUI is open). The `i`
shortcut shows an unread counter, e.g. `i inbox (3)`. shortcut shows an unread counter, e.g. `i inbox (3)`.
@@ -396,7 +396,7 @@ In the interactive TUI, press `c` to open the chat-rooms popup:
- the **room list** is browsable and `/`-filterable and shows each room's - the **room list** is browsable and `/`-filterable and shows each room's
user count (busiest first); press `Enter` to join the highlighted room; user count (busiest first); press `Enter` to join the highlighted room;
- several rooms can be **open at once** as tabs — `Tab`/`Shift-Tab` switch - several rooms can be **open at once** as tabs: `Tab`/`Shift-Tab` switch
between them, `x` leaves the active room, `l` returns to the room list; between them, `x` leaves the active room, `l` returns to the room list;
- in a room, press `Enter` to type a message and `Enter` again to send; - in a room, press `Enter` to type a message and `Enter` again to send;
- the room's **member list** is selectable with `↑`/`↓`; press `b` to browse - the room's **member list** is selectable with `↑`/`↓`; press `b` to browse
@@ -434,7 +434,7 @@ verdict is in the exit code, `soulseek-rs portmap || notify-me` works as a
health check. health check.
If both you and a peer are behind routers with no forwarded port, browsing that If both you and a peer are behind routers with no forwarded port, browsing that
peer can't work — that's a fundamental Soulseek/peer-to-peer limitation, not a peer can't work. That's a fundamental Soulseek/peer-to-peer limitation, not a
bug. bug.
## Development ## Development
@@ -475,10 +475,10 @@ are **server-optional**: they run when a server is available and otherwise skip
They locate a server in this order: They locate a server in this order:
1. `SOULSEEK_TEST_SERVER=host:port` — connect to an already-running server, or 1. `SOULSEEK_TEST_SERVER=host:port`: connect to an already-running server, or
2. `SOULFIND_BIN=/path/to/soulfind` (or a `soulfind/bin/soulfind` checkout in a 2. `SOULFIND_BIN=/path/to/soulfind` (or a `soulfind/bin/soulfind` checkout in a
parent directory) — spawn soulfind on an ephemeral port with a throwaway parent directory), which spawns soulfind on an ephemeral port with a
database. throwaway database.
```bash ```bash
# Build soulfind once (see its BUILDING.md), then: # Build soulfind once (see its BUILDING.md), then:
@@ -487,7 +487,7 @@ SOULFIND_BIN=/path/to/soulfind/bin/soulfind \
``` ```
> On macOS, soulfind's `sqlite3_config` call is rejected by the system SQLite > On macOS, soulfind's `sqlite3_config` call is rejected by the system SQLite
> and the server dies at startup with `SQLite error 7 (out of memory)` — which > and the server dies at startup with `SQLite error 7 (out of memory)`, which
> the suite reports as a skip. Run the tests with > the suite reports as a skip. Run the tests with
> `DYLD_LIBRARY_PATH=/opt/homebrew/opt/sqlite/lib` so soulfind picks up > `DYLD_LIBRARY_PATH=/opt/homebrew/opt/sqlite/lib` so soulfind picks up
> Homebrew's SQLite instead; no patching needed. > Homebrew's SQLite instead; no patching needed.
@@ -500,14 +500,14 @@ runs against a freshly built soulfind rather than silently skipping.
`.github/workflows/ci.yml` runs on every push and pull request: `.github/workflows/ci.yml` runs on every push and pull request:
- **Format & Clippy** — `cargo fmt --all --check` and - **Format & Clippy**: `cargo fmt --all --check` and
`cargo clippy --workspace --all-targets -- -D warnings`. `cargo clippy --workspace --all-targets -- -D warnings`.
- **Test** — `cargo test --verbose` on Linux, macOS, and Windows. - **Test**: `cargo test --verbose` on Linux, macOS, and Windows.
- **End-to-end** — builds soulfind from source (LDC + `dub build :server`), - **End-to-end**: builds soulfind from source (LDC + `dub build :server`),
points `SOULFIND_BIN` at it, and runs both the library and CLI e2e suites points `SOULFIND_BIN` at it, and runs both the library and CLI e2e suites
with `SOULSEEK_E2E_REQUIRED=1`, so a missing server fails instead of skipping. with `SOULSEEK_E2E_REQUIRED=1`, so a missing server fails instead of skipping.
## License ## License
This project is licensed under the MIT License — see the [LICENSE](./LICENSE) This project is licensed under the MIT License. See the [LICENSE](./LICENSE)
file for details. file for details.