Commit Graph
274 Commits
Author SHA1 Message Date
Michel de Graaf 9b87bd6a0f feat!: wishlist, upload-queue privilege recognition, and search filters (#20)
Closes the last two gaps in Soulseek's list of features an alternative client
must implement to be tolerated, and adds the result filters Nicotine+ has had
all along.

Wishlist: server code 103 is sent and code 104's announced interval is kept
rather than logged and discarded, so the re-search timer runs off the schedule
the server set. `wish add|remove|list|run` plus `serve --wishlist`; wishes
persist in config.toml as a TOML array so a comma inside a query survives.
`wish run` prints search-shaped records, so it pipes into `download --stdin`.

Privilege recognition: the code-69 user list is parsed instead of counted and
dropped, and code 92 answers `whoami` as `privilege_seconds`. Uploads now go
through a queue with a slot cap (`--upload-slots`, default 2) that serves
privileged users first — the cap is what makes the ordering mean anything, so
they are one change. `PlaceInQueueRequest` (peer code 51) is answered, where it
previously had no handler at all, and a departed peer's slot is released so a
stalled peer cannot wedge uploads shut.

Search filters: `--exclude`, `--extension`, `--min-size`, `--max-size` beside
the existing `--min-bitrate` and `--free-slots`, in one flattened arg group
shared by `search`, `get` and `wish run`.

Also serialises the library e2e suite behind the same gate `cli_e2e` already
uses: the new queue tests hold peer sockets open on purpose, which starved
concurrently-spawned soulfinds and failed unrelated download tests with
`login: Timeout`.

BREAKING CHANGE: `UploadStatus` gains a `Queued(u32)` variant, so an exhaustive
match on it no longer compiles, and `cli::Commands` gains `Wish`.
2026-07-26 14:20:07 +02:00
Michel de Graaf eda762d067 fix(ci): don't publish a release on a run that rebuilds an older tag 2026-07-26 13:17:49 +02:00
Michel de Graaf 2fdb567a0e ci: name the missing tap secret instead of leaking gh's generic advice 2026-07-26 13:02:05 +02:00
Michel de Graaf 5161d0b758 chore: drop the tap token probe 2026-07-26 13:01:49 +02:00
Michel de Graaf 45f7df3db3 chore: probe repository_dispatch 2026-07-26 13:00:47 +02:00
Michel de Graaf f6586cf321 chore: probe dispatch permission 2026-07-26 12:59:56 +02:00
Michel de Graaf d6b8bec940 chore: release v9.1.0 (#19) 2026-07-26 12:59:49 +02:00
Michel de Graaf 626dcb5d76 chore: temporary tap token probe 2026-07-26 12:59:01 +02:00
Michel de Graaf af4434e764 test: prove the documented examples against the binary (#15)
* test: prove the documented examples against the binary

The README, the website and the shipped SKILL.md all promise specific
records, keys and exit codes, and nothing was checking that the binary
still agrees. Cover the claims that were unverified: the eleven
credential-free commands, the one-day bound on every wait, the config
directory relocation, `serve` refusing to run without a listener, the
four action commands that answer with an exit code alone, the search
filters, `--password-stdin`, `room listen`'s join records, and every
`--json` field the docs name for search, browse, whoami, user, shares,
room, download, serve and portmap.

The library README's examples now compile as doctests: lib.rs includes
it as the crate docs and the fences are `no_run`, so a signature change
cannot leave the advertised usage stale.

Two things were wrong. SKILL.md listed `queued` as a `serve` upload
status, which `upload_record` never emits — an agent branching on it
would wait for a state that never arrives. And the library README still
pointed at the 7.x lib.

* test: cover the completions commands the docs now advertise
2026-07-26 12:56:40 +02:00
Michel de Graaf 1eef886b88 fix(ci): stop the skipped ci-gate from cascading past the release job 2026-07-26 12:54:40 +02:00
Michel de Graaf 8dc140eea2 fix(ci): gate the binaries job on the resolved tag so manual rebuilds run 2026-07-26 12:51:56 +02:00
Michel de Graaf a72dd64081 feat: resume an interrupted download instead of refetching it (#18)
Downloads buffered the whole file in memory and wrote it out only on
success, so an interrupted transfer left nothing behind and always
started over. The START_DOWNLOAD offset was hardcoded to zero on the
download side and discarded on the upload side, so neither half of the
protocol's resume support was wired up.

Bytes now stream into `<file>.part` as they arrive and it is renamed
only once the file is whole. A new attempt sends the partial file's
length as the START_DOWNLOAD offset, and `serve_file` seeks to the
offset a peer asks for rather than replaying from the start. A `.part`
at or past the expected size is treated as stale and restarted.

Partial files are kept out of the share index, since the download
directory is shared by default.
2026-07-26 12:51:48 +02:00
Michel de Graaf 3b75332e0c ci: push the new release to the homebrew tap instead of waiting for its cron 2026-07-26 12:49:14 +02:00
Michel de Graaf 481c815983 chore: release v9.0.0 (#17) 2026-07-26 12:21:11 +02:00
Michel de Graaf 534b09a20a feat: install tab completion for bash, zsh, and fish (#16)
`completions install` generates the script from the same clap definition
the binary parses with, so it cannot drift from `--help`, and writes it
where each shell already looks. fish scans its own completions directory
unprompted; bash and zsh get one marked `source` line appended to their rc
file, because nothing puts a per-user directory on their search path by
default. `completions uninstall` takes back exactly those lines and leaves
the rest of the file alone.
2026-07-26 12:18:53 +02:00
Michel de Graaf 72f2e31a2c docs: point the library snippets at the 8.x lib
v8.0.0 is on crates.io, so the Cargo.toml examples in the readme and on the
install page were a major behind.

Replace the 5.x upgrade note with one that does not name a version, so it
stops going stale every release.
2026-07-26 11:51:48 +02:00
Michel de Graaf 9869b11f6d docs: cut the padding from the readme
Drop the "This structure allows" bullets justifying a two-crate workspace,
collapse four one-line "To run X:" stubs into a single block, and lose the
adverbs, the emoji heading, and the bolded emphasis.

Project Goals no longer claims a hard no-external-crates rule for the
library, which stopped being the stance; it says lean instead. Also fixes
"Headless mode daemon mode".
2026-07-26 11:45:33 +02:00
Michel de Graaf 45b83546ae chore: release v8.0.0 (#14) 2026-07-26 11:42:01 +02:00
Michel de Graaf abf83fa30a style(website): cut the padding from the page copy
Remove three cross-page duplications: the "what --help cannot say" list
appeared on both home and docs, the config precedence rule twice on
install, and the soulfind link twice in adjacent community paragraphs.

Drop the em dashes, the adverbs, and the rhetorical triads while there.
2026-07-26 11:41:48 +02:00
Michel de Graaf d2f3dd13d7 feat: log in with soulseek-rs's own client major version (176) (#13)
The Login message hardcoded SoulseekQt's major version 157, so the
server could not tell soulseek-rs apart from the official client. The
protocol document now reserves a unique major version per project, so
claim 176 and send it via a new ClientVersion on ClientSettings.

Clients and bots built on the library keep major 176 and pick their own
minor version (default 1); the reference CLI/TUI logs in with minor 100.

Refs #12
2026-07-25 21:51:58 +02:00
Michel de Graaf e2c2b5063a style(website): tighten the agent-skill copy 2026-07-25 11:09:50 +02:00
Michel de Graaf e79250c337 fix(website): bump the library install snippet to the 7.x lib 2026-07-25 10:55:49 +02:00
github-actions[bot]andMichel de Graaf 69f3621f72 chore: release v7.1.0 (#11)
Co-authored-by: Michel de Graaf <michel@re-invention.nl>
2026-07-25 07:59:54 +02:00
Michel de Graaf 3ff51c2799 style(website): widen the mobile page gutter to 24px
The wordmark and the hero mark sat 18px off the edge, close enough to read as
flush against it on a phone. The nav bar and Wrap carry the same gutter, so
both move together.
2026-07-25 05:39:56 +02:00
Michel de Graaf 96e6bcdb8a feat(website): route the OS links to per-platform install steps
Clicking macOS, Linux or Windows on the home page landed on the file-path
cards, which say where config lives, not how to install. Step 1 now carries
one card per OS at those anchors: brew on macOS, cargo on Linux, cargo or the
prebuilt .exe on Windows. The path cards keep their own -paths anchors.

The hero logo drew into a fixed inline width, so the mobile height overrides
shrank the mark inside a box sized for the desktop one. An aspect-ratio lets
the width follow, and the mark is now larger and centred against the title.
2026-07-25 05:35:01 +02:00
Michel de Graaf 5090af97a2 fix(lib): give the login verdict the wait the caller already allows
The actor gave the server 5 seconds to answer a login before reporting
Timeout, while Client::login documents and allows 45. Under load — 24
e2e tests sharing one soulfind — 5 seconds is not always enough, which
is how CI failed on a docs-only commit.
2026-07-25 05:28:44 +02:00
Michel de Graaf c074ac3d7b docs: show the agent session in the README agent skills section 2026-07-25 05:14:37 +02:00
Michel de Graaf dab17774ab docs: use recorded terminal sessions for both demos
The README demo is a fresh recording of the v7 TUI, and the agent
section now shows a real agent session — loading the skill and driving
the CLI — in place of the hand-written terminal mock.

Both are asciinema recordings rendered to SVG at 10x, with frames
coalesced at 20fps to keep them small.
2026-07-25 05:07:37 +02:00
Michel de Graaf 307f1713b8 docs: replace the demo gif with an animated SVG of the TUI
Recorded with asciinema against the live network and rendered with
svg-term, so the demo is vector text instead of pixels: 20 KB gzipped
against 400 KB, crisp at any width, and it links through to the site.
2026-07-25 04:15:32 +02:00
Michel de Graaf e43253a7db ci: let a token opt the release PR into running CI
release-plz opens the release PR as app/github-actions, and GitHub parks
workflow runs from that actor at action_required, so the PR shows no checks
until someone approves each one by hand. Passing a PAT instead makes the PR
come from that identity and its CI runs on its own.

The fallback keeps today's behaviour until RELEASE_PLZ_TOKEN exists, so this
is inert until you set it. Only the PR job needs it; tagging and publishing
are fine with GITHUB_TOKEN.
2026-07-25 04:08:37 +02:00
Michel de Graaf 0b332c5170 ci: let the tap update its own formula
The homebrew job needed a cross-repo PAT in HOMEBREW_TAP_TOKEN, which resolved
to an empty string and failed every release with "Input required and not
supplied: token". A tap can write to itself with the GITHUB_TOKEN it already
has, so the formula is now generated by an Update formula workflow in
michel/homebrew-tap that reads the latest release and commits when it changes.

That removes the secret from the release path entirely. Verified by corrupting
the formula and watching the workflow put it back.
2026-07-25 04:00:20 +02:00
Michel de Graaf c27f2b8467 ci: stop cancelling master runs, and revert the release job conditions
The empty homebrew tap was never a condition problem. The release job ran, the
matrix built all six targets, and the tap step then failed with "Input required
and not supplied: token" because HOMEBREW_TAP_TOKEN resolves to an empty
string. Four rounds of edits to those `if` expressions chased a symptom, so
they go back to what they were.

What stays is the CI concurrency change: a cancelled master run leaves the
release gate waiting on a CI run for a commit nothing will push again.
2026-07-25 03:56:10 +02:00
Michel de Graaf 53ff1045cb fix(ci): gate the binary and formula jobs on the event, and show the tag
Three context spellings were tried in these job conditions and all three left
the jobs skipped: `inputs.tag`, `github.event.inputs.tag`, and
`needs.release.outputs.tag`. The run logs show `Resolve tag` succeeding with
INPUT_TAG=v7.0.0 and writing the output, so the value exists in the step and
is gone by the time a downstream `if` reads it.

`github.event_name` is the one context this file already proves works in a job
condition, because ci-gate skips correctly on dispatch. Gate on that, and echo
both outputs at the end of the release job so the next failure names itself
instead of costing four runs.
2026-07-25 03:51:06 +02:00
Michel de Graaf a5339ac949 fix(ci): gate the binary and formula jobs on the resolved tag
Neither `inputs.tag` nor `github.event.inputs.tag` evaluates in a job-level
`if` on workflow_dispatch, so both spellings left the binaries and homebrew
jobs skipped on every manual recovery run, while the step-level condition on
the same input fired correctly and resolved the tag. The run log settles it:
INPUT_TAG reached the step as v7.0.0 and the jobs still skipped.

`Resolve tag` already runs exactly when there is something to build, so its
output is the honest signal, and a `needs` output does evaluate here.
2026-07-25 03:45:43 +02:00
Michel de Graaf c6d41796c9 feat(tui): give the panes the colour the demo promised
The cursor row set a foreground, and ratatui only skips a cell's colour
when that field is None — so selecting a row repainted its gold sizes,
blue usernames and status glyphs into one flat cream. Table cursors now
tint the background only, over a warm oxide wash instead of neutral.

Browse files, their sizes and the settings popup rendered in the raw
terminal foreground; they take the palette now, and free slots read
green when a peer has one.
2026-07-25 03:44:07 +02:00
Michel de Graaf fce4b765b7 fix(ci): make the manual tag input actually reach the recovery jobs
The `tag` dispatch input exists so binaries and the homebrew formula can be
rebuilt for a tag that already shipped. It never worked: the job-level
conditions read `inputs.tag`, which is empty in a job `if` on
workflow_dispatch, and `null != ''` coerces to false, so both jobs silently
skipped. That is why v7.0.0 reached crates.io with an empty homebrew tap.
The step-level condition used the same expression and did fire, which is what
made the two disagree. `github.event.inputs.tag` is populated in both places.
2026-07-25 03:43:34 +02:00
Michel de Graaf fe079b4c94 fix(website): give the wordmark cursor the gap it was drawn with
The cursor sat 0.06em after the text, close enough to read as a stray glyph.
The wordmark SVG puts it 15.4 units after a 27-unit block, so 0.57 cursor
widths, which is 0.33em at the component's 0.58em cursor.
2026-07-25 03:38:10 +02:00
Michel de Graaf 430a985152 fix(website): give the wordmark cursor the gap it was drawn with
The cursor sat 0.06em after the text, close enough to read as a stray glyph.
The wordmark SVG puts it 15.4 units after a 27-unit block, so 0.57 cursor
widths, which is 0.33em at the component's 0.58em cursor.
2026-07-25 03:38:03 +02:00
Michel de Graaf 06459ddced docs: use the wordmark at the top of the README
The mark is a glyph; the wordmark reads "soulseek-rs" outright, which is what
a README header wants. Both tones ship so prefers-color-scheme can pick one.
Named for the background they sit on rather than their Figma tone, because the
existing logo-ink/logo-light pair means the opposite of what it sounds like.
2026-07-25 03:34:44 +02:00
Michel de Graaf 6b755872ff docs: mention Homebrew where the README lists how users install
The project-structure summary still named cargo as the only route.
2026-07-25 03:22:45 +02:00
Michel de Graaf 9e41456507 docs: follow up the v7 release and the homebrew section
The homebrew install text arrived with an em-dash in it, and both READMEs still
told library users to depend on version 6.
2026-07-25 03:22:04 +02:00
Michel de Graaf 12c8147f71 docs: point the README logo at the website's copy
The logo already lived in website/public; copying it into fixtures meant two
files to keep in step for no gain. The README now references the original.
2026-07-25 03:21:33 +02:00
github-actions[bot] 9de1547680 chore: release v7.0.0 (#8)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-25 03:21:14 +02:00
Michel de Graaf 9c966672af feat: install via homebrew (#9)
Add a release job that regenerates Formula/soulseek-rs.rb in michel/homebrew-tap
from the archives the binaries matrix uploads, and lead with
`brew install michel/tap/soulseek-rs` in the READMEs and on the website.
2026-07-25 03:18:53 +02:00
Michel de Graaf d84360a803 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.
2026-07-25 03:18:37 +02:00
Michel de Graaf 13c58feafe docs: open the README with the logo and the site's own intro
The logo ships in both variants the site uses, picked by prefers-color-scheme
so it stays legible on either GitHub theme — the names are inverted from what
you would guess, `ink` being the cream one for dark backgrounds. The opening
copy now matches the site rather than describing the project twice in two
voices, and still says the repository holds a library as well as a client.
2026-07-25 02:55:47 +02:00
Michel de Graaf 692e8008e0 docs: link the website from the crates and the READMEs
The site went up but nothing pointed at it. `homepage` in the workspace
package metadata gives both crates.io pages a link, and each README opens with
one, so someone landing on the crate or the repo can find the install guide
and the config reference.
2026-07-25 02:53:27 +02:00
Michel de Graaf 9d58bb9b64 refactor: clear the quality-scan findings
`render_shortcuts` was 144 lines of "which shortcuts apply" wrapped around 40
lines of drawing; the decision moves into a `shortcuts` method beside the
`rooms_shortcuts` helper that already worked this way. Three identically
shaped comments over the popup guards collapse into one that carries the part
worth knowing — an open popup owns the keyboard, so the order matters. The
state tests trade bare `unwrap()` for `expect()` so a failure names the step.
2026-07-25 02:32:11 +02:00
Michel de Graaf 14b0321dbc 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.
2026-07-25 02:32:05 +02:00
Michel de Graaf cd41688803 docs(website): explain what the skill unlocks, and document config.toml
The agent-native copy described what the skill contains rather than what it
buys you. A new home-page section shows the shape of the request that becomes
possible — describe the outcome, let the agent write the pipeline — and names
the specific knowledge --help cannot carry: which JSON keys a record has, that
exit 4 means widen the query, that a remote path goes back byte-identical,
that --follow never returns.

The install page gains a reference for all nine config.toml settings with
their defaults and an example file, next to the paths that were already there.
The list is the one `config set` accepts, checked against FileConfig::KEYS.
2026-07-25 02:27:47 +02:00