docs(website): lead with the agent-native story
`skills install` becomes the featured card on the home page, in the wide slot the grid gives its first entry: one command hands a coding agent the JSON keys of every record, the exit-code meanings, and the --json rule, with nothing to clone because the skill ships inside the binary. The docs page gains a callout and the command in the shell listing. Also catches the site up with the scriptable command surface the last two releases introduced: `get`, `shares add`, `serve`, `room list`, `message send` and `--json` replace the flags and spellings that no longer exist.
This commit is contained in:
+2
-2
@@ -8,7 +8,7 @@
|
||||
<title>soulseek-rs, a Soulseek client for your terminal</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="soulseek-rs is a keyboard-driven Soulseek client and a Rust protocol library. Search, share, browse, and chat from your terminal."
|
||||
content="soulseek-rs is a keyboard-driven Soulseek client and a Rust protocol library, with scriptable subcommands. Search, share, browse, and chat from your terminal."
|
||||
/>
|
||||
<meta name="theme-color" content="#14110F" media="(prefers-color-scheme: dark)" />
|
||||
<meta name="theme-color" content="#F2EDE4" media="(prefers-color-scheme: light)" />
|
||||
@@ -17,7 +17,7 @@
|
||||
<meta property="og:title" content="soulseek-rs" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="A Soulseek client and protocol library for Rust. Search, share, browse, and chat from your terminal."
|
||||
content="A Soulseek client and protocol library for Rust. Search, share, browse, and chat from your terminal or a script."
|
||||
/>
|
||||
<meta property="og:url" content="https://re-invention.nl/soulseek-rs/" />
|
||||
<link rel="canonical" href="https://re-invention.nl/soulseek-rs/" />
|
||||
|
||||
@@ -32,8 +32,13 @@ const ALTERNATIVES: readonly Alternative[] = [
|
||||
links: [{ label: 'Nicotine+', href: LINKS.nicotine }],
|
||||
},
|
||||
{
|
||||
question: 'Want to point automation at the network?',
|
||||
answer: <>slskd does that today. Daemon mode here is unbuilt.</>,
|
||||
question: 'Want a queue that outlives the command?',
|
||||
answer: (
|
||||
<>
|
||||
Every command runs one-shot and exits: JSON on stdout, an exit code, nothing left
|
||||
running. No resident queue to pause, resume, or drive remotely. slskd has that.
|
||||
</>
|
||||
),
|
||||
links: [{ label: 'slskd', href: LINKS.slskd }],
|
||||
},
|
||||
]
|
||||
@@ -45,8 +50,8 @@ const CITIZENSHIP = [
|
||||
title: 'Share, and share real files',
|
||||
body: (
|
||||
<>
|
||||
Point <code>--shared-dir</code> at the collection you actually listen to. Full
|
||||
albums, honest bitrates, nothing transcoded up from a lossy source and relabelled.
|
||||
Point <code>shares add</code> at the collection you actually listen to. Full albums,
|
||||
honest bitrates, nothing transcoded up from a lossy source and relabelled.
|
||||
</>
|
||||
),
|
||||
},
|
||||
@@ -67,8 +72,8 @@ const CITIZENSHIP = [
|
||||
title: 'Stay online',
|
||||
body: (
|
||||
<>
|
||||
Leave it running in a tmux session on the box that is already on. A share that
|
||||
appears for an hour a week is close to no share at all.
|
||||
Leave <code>serve --follow</code> running in a tmux session on the box that is
|
||||
already on. A share that appears for an hour a week is close to no share at all.
|
||||
</>
|
||||
),
|
||||
},
|
||||
|
||||
+39
-13
@@ -15,6 +15,7 @@ import { Terminal } from '@/components/ui/terminal'
|
||||
import { LINKS } from '@/lib/links'
|
||||
|
||||
const GLOBAL_KEYS: readonly (readonly [string, string])[] = [
|
||||
['s', 'search the network'],
|
||||
['Space', 'select a result'],
|
||||
['Enter', 'download / send'],
|
||||
['b', "browse the owner's files"],
|
||||
@@ -41,7 +42,7 @@ const PANES: readonly { num?: number; title: string; body: string }[] = [
|
||||
{
|
||||
num: 1,
|
||||
title: 'Searches',
|
||||
body: 'Your queries and how many results each returned. Type / to search the network; each search keeps its own result set.',
|
||||
body: 'Your queries and how many results each returned. Press s to search the network; each search keeps its own result set.',
|
||||
},
|
||||
{
|
||||
num: 2,
|
||||
@@ -153,27 +154,40 @@ export const Docs = () => {
|
||||
eyebrow="4 · from the shell"
|
||||
title="Every command also runs as a one-off."
|
||||
>
|
||||
Skip the TUI when you’re scripting or mid-task.
|
||||
Skip the TUI when you’re scripting or mid-task. stdout is data, stderr is
|
||||
progress, and the exit code is the verdict.
|
||||
</SectionHead>
|
||||
<Cols start>
|
||||
<Terminal
|
||||
label="shell"
|
||||
lines={[
|
||||
{ t: 'cmd', text: 'soulseek-rs "public domain field recordings"' },
|
||||
{ t: 'cm', text: '# open the TUI straight onto a search' },
|
||||
{ t: 'cmd', text: 'soulseek-rs message <username> "hello there"' },
|
||||
{ t: 'cmd', text: 'soulseek-rs rooms' },
|
||||
{ t: 'cm', text: '# list public rooms, busiest first' },
|
||||
{ t: 'cmd', text: 'soulseek-rs chat <room> "hello room"' },
|
||||
{ t: 'cmd', text: 'soulseek-rs portmap' },
|
||||
{ t: 'cmd', text: 'soulseek-rs get "public domain field recordings"' },
|
||||
{ t: 'cm', text: '# search, pick the best, download it' },
|
||||
{ t: 'cmd', text: 'soulseek-rs search "netlabel 2004" --min-bitrate 320' },
|
||||
{ t: 'cmd', text: 'soulseek-rs browse <username> --json' },
|
||||
{ t: 'cmd', text: 'soulseek-rs room list' },
|
||||
{ t: 'cm', text: '# public rooms, busiest first' },
|
||||
{ t: 'cmd', text: 'soulseek-rs room say <room> "hello room"' },
|
||||
{ t: 'cmd', text: 'soulseek-rs message send <username> "hello there"' },
|
||||
{ t: 'cmd', text: 'soulseek-rs serve --follow' },
|
||||
{ t: 'cm', text: '# stay online sharing; every upload as it happens' },
|
||||
{ t: 'cmd', text: 'soulseek-rs skills install' },
|
||||
{ t: 'cm', text: '# hand all of the above to your coding agent' },
|
||||
]}
|
||||
/>
|
||||
<Prose>
|
||||
<p>
|
||||
The subcommands mirror the TUI: <Code>search</Code>, <Code>message</Code>,{' '}
|
||||
<Code>browse</Code>, <Code>rooms</Code>, <Code>chat</Code>, and{' '}
|
||||
<Code>portmap</Code>. Pass a room and a message to <Code>chat</Code> to say
|
||||
one thing and exit.
|
||||
The command groups: <Code>search</Code>, <Code>get</Code>,{' '}
|
||||
<Code>download</Code>, <Code>browse</Code>, <Code>room</Code>,{' '}
|
||||
<Code>message</Code>, <Code>serve</Code>, <Code>user</Code>,{' '}
|
||||
<Code>whoami</Code>, <Code>shares</Code>, <Code>config</Code>,{' '}
|
||||
<Code>portmap</Code>, <Code>skills</Code>. Records are tab-separated fields,
|
||||
one per line, or NDJSON with <Code>--json</Code>.
|
||||
</p>
|
||||
<p>
|
||||
No TTY, no daemon. <Code>search --json</Code> pipes through <Code>jq</Code>{' '}
|
||||
into <Code>download --stdin</Code>. Every ending has its own exit code: 4 found
|
||||
nothing, 3 could not reach the server, 6 the transfer died.
|
||||
</p>
|
||||
<p>
|
||||
<Code>portmap</Code> tests whether your router will let peers connect back,
|
||||
@@ -185,6 +199,18 @@ export const Docs = () => {
|
||||
</Prose>
|
||||
</Cols>
|
||||
<div className="mt-6">
|
||||
<Callout title="give it to your agent">
|
||||
<p>
|
||||
<Code>soulseek-rs skills install</Code> writes a <Code>SKILL.md</Code> into
|
||||
every coding agent on the machine — Claude Code and opencode today,{' '}
|
||||
<Code>--dir</Code> for anything else. It carries what <Code>--help</Code>{' '}
|
||||
cannot: the JSON keys of every record, what each exit code means, and the
|
||||
idioms worth reaching for. Run it again after an upgrade to update it, and{' '}
|
||||
<Code>skills uninstall</Code> takes it back out.
|
||||
</p>
|
||||
</Callout>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<Callout tone="warn" title="the one limit worth knowing up front">
|
||||
<p>
|
||||
If both you and a peer are behind routers with no forwarded port, browsing
|
||||
|
||||
+34
-15
@@ -20,22 +20,41 @@ const PLATFORMS: readonly { name: OsName; note: string; anchor: string }[] = [
|
||||
]
|
||||
|
||||
const FEATURES = [
|
||||
{
|
||||
glyph: '✦',
|
||||
color: 'var(--accent-text)',
|
||||
title: 'Agent native',
|
||||
cmd: 'soulseek-rs skills install',
|
||||
body: (
|
||||
<>
|
||||
One command hands your coding agent the whole surface: the JSON keys every record
|
||||
carries, what each exit code means, and the rule that it always asks for{' '}
|
||||
<code>--json</code>. The skill ships inside the binary, so there is nothing to
|
||||
clone and nothing to configure — it finds Claude Code and opencode on its own.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
glyph: '→',
|
||||
color: 'var(--accent-text)',
|
||||
title: 'Search & download',
|
||||
cmd: 'soulseek-rs "field recordings"',
|
||||
body: <>Search the network, pick results in the TUI, and queue downloads with pause, resume, and retry.</>,
|
||||
cmd: 'soulseek-rs get "field recordings"',
|
||||
body: (
|
||||
<>
|
||||
Queue downloads in the TUI with pause, resume, and retry, or let <code>get</code>{' '}
|
||||
search, pick the best, and download in one command.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
glyph: '↑',
|
||||
color: 'var(--status-success)',
|
||||
title: 'Sharing',
|
||||
cmd: 'soulseek-rs --shared-dir ~/Music',
|
||||
cmd: 'soulseek-rs shares add ~/Music',
|
||||
body: (
|
||||
<>
|
||||
Point <code>--shared-dir</code> at a directory and your files show up in searches;
|
||||
peers browse and download them.
|
||||
Point <code>shares add</code> at a directory and your files show up in searches;{' '}
|
||||
<code>serve</code> stays online so peers can browse and download them.
|
||||
</>
|
||||
),
|
||||
},
|
||||
@@ -50,14 +69,14 @@ const FEATURES = [
|
||||
glyph: '#',
|
||||
color: 'var(--status-warning)',
|
||||
title: 'Chat rooms',
|
||||
cmd: 'soulseek-rs 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 <user> "hi"',
|
||||
cmd: 'soulseek-rs message send <user> "hi"',
|
||||
body: <>Send and receive PMs, with an inbox and an unread counter in the TUI.</>,
|
||||
},
|
||||
{
|
||||
@@ -88,11 +107,11 @@ const FEATURES = [
|
||||
glyph: '▮',
|
||||
color: 'var(--accent-text)',
|
||||
title: 'TUI and CLI',
|
||||
cmd: 'soulseek-rs',
|
||||
cmd: 'soulseek-rs search "rjd2" --json',
|
||||
body: (
|
||||
<>
|
||||
A full terminal interface, plus scriptable subcommands: search, message, browse,
|
||||
rooms, chat, portmap.
|
||||
A full terminal interface, plus scriptable subcommands: stdout is data, stderr is
|
||||
progress, the exit code is the verdict.
|
||||
</>
|
||||
),
|
||||
},
|
||||
@@ -203,10 +222,10 @@ export const Home = () => {
|
||||
</Callout>
|
||||
<Callout title="agents and automation">
|
||||
<p>
|
||||
Every command runs as a one-off from the shell: <Code>search</Code>,{' '}
|
||||
<Code>browse</Code>, <Code>rooms</Code>, <Code>chat</Code>,{' '}
|
||||
<Code>message</Code>, <Code>portmap</Code>. Point cron, a script, or an
|
||||
agent at them and read the output back. No daemon, no API to stand up.{' '}
|
||||
Every command runs as a one-off from the shell. Point cron, a script, or an
|
||||
agent at them and read JSON records back off stdout. No daemon, no API to
|
||||
stand up — and <Code>skills install</Code> teaches the agent the surface
|
||||
for you.{' '}
|
||||
<Link to="/docs" className="text-link hover:text-link-hover">
|
||||
See the commands
|
||||
</Link>
|
||||
@@ -243,7 +262,7 @@ export const Home = () => {
|
||||
<div className="flex flex-col gap-3.5">
|
||||
<Eyebrow>get started</Eyebrow>
|
||||
<h2 className="text-[26px] leading-[34px] font-medium text-balance sm:text-title sm:leading-[var(--text-title--line-height)]">
|
||||
Install it, then press <span className="text-accent-text">/</span> to search.
|
||||
Install it, then press <span className="text-accent-text">s</span> to search.
|
||||
</h2>
|
||||
<p className="text-secondary">
|
||||
Needs a Rust toolchain. From there it’s one command, then the TUI opens.
|
||||
|
||||
@@ -11,7 +11,7 @@ use std::time::Duration;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut client = Client::new("username", "password");
|
||||
client.connect();
|
||||
client.connect()?;
|
||||
client.login()?;
|
||||
|
||||
let results = client.search("public domain field recordings", Duration::from_secs(10))?;
|
||||
@@ -117,8 +117,8 @@ export const Install = () => {
|
||||
]}
|
||||
/>
|
||||
<p className="text-[13px] text-muted">
|
||||
Cargo puts the binary on your PATH. Run <Code>soulseek-rs</Code> and the TUI
|
||||
opens.
|
||||
Cargo puts the binary on your PATH. Run <Code>soulseek-rs</Code> in a terminal
|
||||
and the TUI opens, in a script or a pipe it wants a subcommand instead.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2 sm:gap-3">
|
||||
<Button href={LINKS.releases}>Prebuilt binaries</Button>
|
||||
@@ -205,7 +205,7 @@ export const Install = () => {
|
||||
<Section>
|
||||
<SectionHead eyebrow="being reachable" title="Let peers connect back.">
|
||||
Browsing and downloading are peer-to-peer, so at least one side has to accept an
|
||||
incoming connection.
|
||||
incoming connection. <Code>serve</Code> refuses to start with the listener off.
|
||||
</SectionHead>
|
||||
<Cols start>
|
||||
<Prose>
|
||||
@@ -218,13 +218,18 @@ export const Install = () => {
|
||||
yourself.
|
||||
</p>
|
||||
<p>
|
||||
The port is your <Code>--listener-port</Code> (env <Code>LISTENER_PORT</Code>,
|
||||
default <Code>2234</Code>); it’s renewed automatically and removed on
|
||||
exit. Pass <Code>--disable-listener</Code> to turn it off. Check your own
|
||||
network without launching the client:
|
||||
The port is your <Code>--listener-port</Code> (env{' '}
|
||||
<Code>SOULSEEK_LISTENER_PORT</Code>, default <Code>2234</Code>); it’s
|
||||
renewed automatically and removed on exit. Pass <Code>--no-listener</Code> to
|
||||
turn it off. Check your own network without launching the client:
|
||||
</p>
|
||||
<div className="mt-4">
|
||||
<Terminal lines={[{ t: 'cmd', text: 'soulseek-rs portmap' }]} />
|
||||
<Terminal
|
||||
lines={[
|
||||
{ t: 'cmd', text: 'soulseek-rs portmap' },
|
||||
{ t: 'cm', text: "# exits 0 when it works, 4 when it doesn't" },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</Prose>
|
||||
<Callout tone="warn" title="honest about limits">
|
||||
@@ -288,13 +293,17 @@ export const Install = () => {
|
||||
<FeatureCard title="Secrets">
|
||||
Your password goes to the OS keychain, never a plain-text file. If you would
|
||||
rather it never be stored at all, have <code>password_cmd</code> shell out to
|
||||
your own password manager and print it on demand.
|
||||
your own password manager and print it on demand. For CI and containers,{' '}
|
||||
<code>--password-stdin</code> reads it off a pipe so it never reaches{' '}
|
||||
<code>ps</code> or the environment.
|
||||
</FeatureCard>
|
||||
<FeatureCard title="Settings and state">
|
||||
<code>config.toml</code> is layered: flags beat environment variables, which
|
||||
beat the file, which beats the defaults. State (searches, downloads, rooms) is
|
||||
versioned JSON and restores on restart; anything unreadable is set aside as{' '}
|
||||
<code>.bak</code> instead of being overwritten.
|
||||
beat the file, which beats the defaults. <code>config get</code> and{' '}
|
||||
<code>config set</code> read and write that file without opening an editor.
|
||||
State (searches, downloads, rooms) is versioned JSON and restores on restart;
|
||||
anything unreadable is set aside as <code>.bak</code> instead of being
|
||||
overwritten.
|
||||
</FeatureCard>
|
||||
</Cols>
|
||||
</Section>
|
||||
@@ -302,14 +311,15 @@ export const Install = () => {
|
||||
<Section band>
|
||||
<SectionHead eyebrow="uninstall" title="Removing it completely.">
|
||||
Three things exist on disk: the binary, the config, and the state. Nothing else is
|
||||
written, and there is no telemetry to opt out of.
|
||||
written unless you pointed <Code>--log-file</Code> at a file, and there is no
|
||||
telemetry to opt out of.
|
||||
</SectionHead>
|
||||
<Cols start>
|
||||
<Terminal
|
||||
label="macOS · Linux"
|
||||
lines={[
|
||||
{ t: 'cmd', text: 'cargo uninstall soulseek-rs' },
|
||||
{ t: 'cm', text: '# config, state and any cached shares' },
|
||||
{ t: 'cm', text: '# config and state: searches, downloads, rooms, messages' },
|
||||
{
|
||||
t: 'cmd',
|
||||
text: 'rm -rf ~/.config/soulseek-rs ~/.local/share/soulseek-rs',
|
||||
|
||||
Reference in New Issue
Block a user