docs: document the skills command and the scriptable surface

The READMEs still described the flags and spellings the one-shot rework
replaced, and said nothing about `skills`. The root README gains an agent
skills section covering install, update and removal; the crate READMEs catch
up with the current command set, record shapes and library API.
This commit is contained in:
Michel de Graaf
2026-07-25 02:32:05 +02:00
parent cd41688803
commit 14b0321dbc
3 changed files with 207 additions and 44 deletions
+105 -27
View File
@@ -10,10 +10,10 @@ music enthusiasts around the world to share niche music.
## Features
- **Search & download** — search the network, pick results in the TUI, and
queue downloads with pause, resume, and retry
- **Sharing** — point `--shared-dir` at a directory and your files show up in
searches; peers can browse and download them
- **Search & download** — search the network and queue downloads in the TUI,
or fetch a track in one command with `get`
- **Sharing** — `shares add` a directory and your files show up in searches;
`serve` stays online so peers can browse and download them
- **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
- **Private messages** — send and receive messages, with an inbox in the TUI
@@ -136,16 +136,25 @@ soulseek-rs user <NAME> # a peer's status and share counts
soulseek-rs shares list|add|remove|status|reindex
soulseek-rs config path|list|get|set
soulseek-rs portmap # test automatic port mapping
soulseek-rs skills install|uninstall|list # teach a coding agent this CLI
```
`whoami`, `config` and `shares list|add|remove` are the ones a script runs
first: the config commands need no account at all, and `whoami` answers
"are these credentials good and what am I offering" in one call.
Every command emits records except the two that perform an action —
`room say` and `message send` say nothing and answer with their exit code
alone. `room listen` and `message read` stream records as they arrive, until
`--duration` seconds pass or, with `--follow`, until they are interrupted.
Eleven of these need no credentials, because they never touch the network:
`config path|list|get|set`, `shares list|add|remove`, `portmap` and
`skills install|uninstall|list`. Note that
`shares status` and `shares reindex` are not among them — they report what the
network will actually see, which means logging in and letting the library scan
the folders.
Every command emits records except the four that perform an action —
`room say`, `message send`, `shares add` and `shares remove` say nothing on
stdout and answer with their exit code alone. `room listen`, `message read`
and `serve` stream records as they arrive, until `--duration` seconds pass or,
with `--follow`, until they are interrupted.
#### Record shapes
@@ -154,12 +163,37 @@ a path may contain anything except a tab (tabs and newlines in peer-supplied
text are replaced with spaces so a record is always exactly one line):
```
search user size bitrate path
browse user size path
room users room
download local-path
search user size bitrate path
browse user size path
download / get local-path
room list users room
room users user
room listen type room user message
message read timestamp user message
serve status user sent/size path
user user status average-speed shared-files
whoami user server shared-folders shared-files
shares list ok|missing directory
shares status folders files
config get|set|list|path key value
portmap ok|failed backend external
```
`room listen`'s `type` is `message`, `join` or `leave`; a join or leave leaves
the message field empty. `serve`'s `status` is `uploading`, `completed`,
`cancelled` or `failed`. `user`'s `status` is `online`, `away` or `offline`,
and any field the server did not answer prints as `-`.
`--json` carries more than text does, because an object has room for fields a
column layout does not: `search` adds `duration`, `slots`, `speed` and
`free_slot`; `browse` adds `directory`; `download` adds the `user`, remote
`path` and `size` alongside the local `file`; `whoami` adds `listening`,
`listen_port` and `download_dir`; `user` adds `privileged` and
`shared_folders`; `shares status` adds the `directories` array; `serve` adds
`bytes_sent`, `size`, `speed` and a `reason` on failure; `portmap` adds
`port`. Fields the server never answered are `null` rather than absent, so a
missing reply cannot be misread as a zero.
`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
path — so `search` and `browse` pipe straight into it.
@@ -199,13 +233,46 @@ soulseek-rs serve --follow --json | tee -a uploads.ndjson
soulseek-rs user someuser --json | jq -e '.status != "offline"'
```
### Agent skills
The scriptable surface above is meant to be driven by coding agents as much as
by shell scripts, but `--help` cannot tell an agent which JSON keys a record
carries or what exit 4 means. That lives in a skill file shipped inside the
binary, so a `cargo install` is all you need to hand it to your agent:
```bash
soulseek-rs skills install
```
That writes `SKILL.md` into every agent it finds in your home directory —
currently Claude Code (`~/.claude/skills`) and opencode
(`~/.config/opencode/skill`) — and prints one record per target:
```
installed claude /home/you/.claude/skills/soulseek-rs/SKILL.md
unchanged opencode /home/you/.config/opencode/skill/soulseek-rs/SKILL.md
```
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
there without touching anything, and `skills uninstall` removes it again —
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
the skill next to a project so everyone working on it gets the same brief:
```bash
soulseek-rs skills install --dir ~/.config/some-agent/skills
soulseek-rs skills install --dir .claude/skills # commit it with the repo
```
### Sharing
`serve` is the mode that makes this client a source: it stays logged in, keeps
the listener and the share index alive, answers searches and browse requests
from the network, and prints one record per upload as it changes state
(`uploading`, `completed`, `cancelled`, `failed`). It ends after `--duration`
seconds, or runs until interrupted with `--follow`.
seconds (an hour by default), or runs until interrupted with `--follow`.
```bash
soulseek-rs shares add ~/Music # remembered in config.toml
@@ -228,26 +295,37 @@ Downloads run under a deadline (`--timeout`, 300s by default) and
Every setting can come from a flag, an environment variable, or the config
file, in that order of precedence. Flags work before or after the subcommand.
| Flag | Environment variable | `config.toml` key |
| ---- | -------------------- | ----------------- |
| `--username` | `SOULSEEK_USERNAME` | `username` |
| `--password` | `SOULSEEK_PASSWORD` | — (never stored in the file) |
| `--password-cmd` | `SOULSEEK_PASSWORD_CMD` | `password_cmd` |
| `--server` | `SOULSEEK_SERVER` | `server` |
| `--download-dir` | `SOULSEEK_DOWNLOAD_DIR` | `download_dir` |
| `--shared-dir` (repeatable) | `SOULSEEK_SHARED_DIR` | `shared_dir` / `shared_dirs` |
| `--listener-port` | `SOULSEEK_LISTENER_PORT` | `listener_port` |
| `--no-listener` / `--listener` | `SOULSEEK_NO_LISTENER` | `disable_listener` |
| `--max-concurrent-downloads` | `SOULSEEK_MAX_CONCURRENT_DOWNLOADS` | `max_concurrent_downloads` |
| `--search-timeout` | `SOULSEEK_SEARCH_TIMEOUT` | `search_timeout` |
| `--log-file` | `SOULSEEK_LOG_FILE` | — |
| Flag | Environment variable | `config.toml` key | Default |
| ---- | -------------------- | ----------------- | ------- |
| `--username` | `SOULSEEK_USERNAME` | `username` | — (required) |
| `--password` | `SOULSEEK_PASSWORD` | — (never stored in the file) | — (required) |
| `--password-cmd` | `SOULSEEK_PASSWORD_CMD` | `password_cmd` | — |
| `--server` | `SOULSEEK_SERVER` | `server` | `server.slsknet.org:2416` |
| `--download-dir` | `SOULSEEK_DOWNLOAD_DIR` | `download_dir` | `<Downloads>/Soulseek` |
| `--shared-dir` (repeatable) | `SOULSEEK_SHARED_DIR` | `shared_dir` / `shared_dirs` | the download dir |
| `--listener-port` | `SOULSEEK_LISTENER_PORT` | `listener_port` | `2234` |
| `--no-listener` / `--listener` | `SOULSEEK_NO_LISTENER` | `disable_listener` | listener on |
| `--max-concurrent-downloads` | `SOULSEEK_MAX_CONCURRENT_DOWNLOADS` | `max_concurrent_downloads` | `5` |
| `--search-timeout` | `SOULSEEK_SEARCH_TIMEOUT` | `search_timeout` | `10` |
| `--log-file` | `SOULSEEK_LOG_FILE` | — | stderr |
Boolean environment variables accept the usual `1`/`0`/`true`/`false`/`yes`/`no`.
`--config <FILE>` (or `SOULSEEK_CONFIG`) reads a different config file and
`--no-config` ignores it entirely, which is what an isolated or containerised
run wants. The config file lives at `~/.config/soulseek-rs/config.toml` on
macOS and Linux; `SOULSEEK_CONFIG_DIR` and `SOULSEEK_STATE_DIR` relocate the
config and state directories wholesale.
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 container its credentials.
`config get` and `config set` cover the nine settings the file can hold —
`username`, `server`, `listener_port`, `disable_listener`, `download_dir`,
`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
an empty string clears it, and `shared_dirs` takes a comma-separated list.
Every wait expressed in seconds (`--search-timeout`, each `--timeout`, each
`--duration`) is bounded to one day, so a mistyped flag is rejected rather than
hanging until the heat death of the universe.
#### Credentials for unattended runs