import { Link } from 'react-router' import { Logo } from '@/components/brand/logo' import { HeroTerminal } from '@/components/tui/hero-terminal' import { Button } from '@/components/ui/button' import { CommandPill } from '@/components/ui/command-pill' import { FeatureCard } from '@/components/ui/feature-card' import { Code } from '@/components/ui/inline-code' import { Cols, Eyebrow, Prose, Section, SectionHead, Wrap } from '@/components/ui/layout' import { OsIcon, type OsName } from '@/components/ui/os-icon' import { Callout } from '@/components/ui/panel' import { Terminal } from '@/components/ui/terminal' import { INSTALL_CMD, LINKS } from '@/lib/links' import { cn } from '@/lib/utils' const PLATFORMS: readonly { name: OsName; note: string; anchor: string }[] = [ { name: 'macOS', note: 'script or brew', anchor: 'macos' }, { name: 'Linux', note: 'script or cargo', anchor: 'linux' }, { name: 'Windows', note: 'cargo or .exe', anchor: 'windows' }, ] // aislop-ignore-file code-quality/duplicate-block -- FEATURES is a data table; its entries repeat the field names the record shape requires, not logic. The rule matches the punctuation skeleton between entries. A factory would trade named fields for positional string args. const FEATURES = [ { glyph: '✦', color: 'var(--accent-text)', title: 'Agent native', cmd: 'soulseek-rs skills install', body: ( <> Run it once and your agent knows the record shapes, the exit codes, and the filters, so “find the 1998 pressing, lossless only” becomes a pipeline it writes itself. The skill ships in the binary and finds your coding agents on its own. No MCP server. ), }, { glyph: '→', color: 'var(--accent-text)', title: 'Search & download', cmd: 'soulseek-rs get "field recordings"', body: ( <> Queue downloads in the TUI with pause, resume, and retry, or let get{' '} search, pick the best, and download in one command. ), }, { glyph: '↑', color: 'var(--status-success)', title: 'Sharing', cmd: 'soulseek-rs shares add ~/Music', body: ( <> Point shares add at a directory and your files show up in searches;{' '} serve stays online so peers can browse and download them. ), }, { glyph: '◈', color: 'var(--accent-text)', title: 'Daemon mode', cmd: 'soulseek-rs daemon', body: ( <> One session every other command borrows. Leave it on a NAS to share and download around the clock, or let an agent fan out twenty commands on one login. It speaks JSON-RPC, so your own tools can drive it too. ), }, { glyph: '›', color: 'var(--status-info)', title: 'Browse', cmd: 'soulseek-rs browse ', body: <>List any user’s shared files and download straight from the tree., }, { glyph: '#', color: 'var(--status-warning)', title: 'Chat rooms', cmd: 'soulseek-rs room list', body: <>List, join, and talk in public rooms, several open at once as tabs., }, { glyph: '@', color: 'var(--status-info)', title: 'Private messages', cmd: 'soulseek-rs message send "hi"', body: <>Send and receive PMs, with an inbox and an unread counter in the TUI., }, { glyph: '↔', color: 'var(--status-info)', title: 'Firewalled peers', cmd: 'soulseek-rs --listener-port 2234', body: ( <> Downloads and browsing fall back to a server-brokered connection when a peer can’t be reached directly. ), }, { glyph: '↯', color: 'var(--status-warning)', title: 'Automatic port mapping', cmd: 'soulseek-rs portmap', body: ( <> Opens your listen port via UPnP-IGD and NAT-PMP, with a portmap{' '} subcommand to test your router. ), }, { glyph: '▮', color: 'var(--accent-text)', title: 'TUI and CLI', cmd: 'soulseek-rs search "rjd2" --json', body: ( <> A full terminal interface, plus scriptable subcommands: stdout is data, stderr is progress, the exit code is the verdict. ), }, ] as const const Hero = () => (
Soulseek client · terminal · Rust

A Soulseek client for the terminal. Built for agents and people who live there.

Search the network, share your files, browse someone’s collection, join a room. It runs over ssh on the machine where your music already lives.

) const RunsOn = () => (
Runs on
    {PLATFORMS.map((platform, i) => (
  • {platform.name} {platform.note}
  • ))}
) const About = () => (
No charts, no recommendations. You search, you find the person who has it, you download it from them.

Soulseek is a peer-to-peer network from the early 2000s. People still use it to share niche music by hand: the album that exists in one person’s folder and nowhere else.

soulseek-rs speaks that protocol, as a third-party client rather than the official app. It shares, browses, does rooms and private messages, because a client that only downloads makes you a leech on people who left their machines on for you.{' '} What sharing asks of you

Reach for soulseek-rs-lib to build a custom client or an agent on the protocol — every message modern Soulseek still uses, implemented and tested against a real server.{' '} Use the library

Every command runs as a one-off. Point a script or an agent at it and read JSON records off stdout. Start soulseek-rs daemon first when several run at once, so they share one login instead of cutting each other off.{' '} skills install hands your agent that habit, plus the JSON keys and exit codes --help leaves out.{' '} See the commands

) const Features = () => (
Everything happens in the TUI, and every command also runs as a one-off.
{FEATURES.map((feature, i) => ( {feature.body} ))}
) const Agents = () => (
An agent can run any CLI. skills install teaches it to run this one well. an agent loading the soulseek-rs skill and downloading tracks

--help lists flags. It cannot say which JSON keys a record carries, or that exit 4 means widen the query rather than retry it. An agent guesses instead, and guessing against a stranger’s file server hangs the job.

skills install fills that in, so you ask for outcomes instead of writing pipelines: fill the gaps in a discography from whoever has a free slot, or read a peer’s profile for their limits before queueing 2 GB behind them.

Still a CLI: no MCP server, no API key. The skill tells your agent to start a daemon first, so a batch of parallel requests is one login rather than one per command. Upgrading the binary upgrades the skill; rerun skills install to take it.{' '} See the commands

) const GetStarted = () => (
get started

Install it, then press s to search.

One command on macOS or Linux, then the TUI opens. No Rust toolchain needed.

) export const Home = () => ( <> )