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.
This commit is contained in:
Michel de Graaf
2026-07-25 02:27:47 +02:00
parent 17e4bf9fd3
commit cd41688803
3 changed files with 195 additions and 7 deletions
+10 -3
View File
@@ -203,9 +203,16 @@ export const Docs = () => {
<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>--dir</Code> for anything else. After it lands you describe the
outcome instead of the pipeline: &ldquo;mirror that peer&rsquo;s FLAC
folder&rdquo;, &ldquo;watch this room for an hour and save anything
mentioning the label&rdquo;.
</p>
<p>
It carries what <Code>--help</Code> cannot: the JSON keys of every record,
that exit 4 means widen the query rather than retry it, that remote paths go
back byte-identical, and that <Code>--follow</Code> never returns so an agent
passes <Code>--duration</Code>. Run it again after an upgrade to update it;{' '}
<Code>skills uninstall</Code> takes it back out.
</p>
</Callout>
+51 -4
View File
@@ -27,10 +27,10 @@ const FEATURES = [
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.
Run it once and asking beats scripting: your agent knows the record shapes, the
exit codes, and the filters, so &ldquo;find the 1998 pressing, lossless only&rdquo;
becomes a pipeline it writes itself. No MCP server, no wrapper — the skill ships
inside the binary and finds Claude Code and opencode on its own.
</>
),
},
@@ -257,6 +257,53 @@ export const Home = () => {
</div>
</Section>
<Section id="agents">
<SectionHead eyebrow="agent native" title="Ask for the record. Skip the pipeline.">
A CLI an agent can drive is not the same as a CLI an agent knows how to drive.
<Code>skills install</Code> closes that gap in one command.
</SectionHead>
<Cols start>
<Terminal
label="your agent, after skills install"
lines={[
{ t: 'cm', text: '"get me Geogaddi in FLAC, only peers with a free slot"' },
{
t: 'cmd',
text: 'soulseek-rs search "boards of canada geogaddi" \\',
},
{ t: 'code', text: ' --free-slots --json \\' },
{ t: 'code', text: " | jq -c 'select(.path | endswith(\".flac\"))' \\" },
{ t: 'code', text: ' | soulseek-rs download --stdin --json' },
{ t: 'out', text: '{"file":"~/Music/Soulseek/01 Ready Lets Go.flac",…}' },
{ t: 'cm', text: '# 23 tracks. no wrapper script, no hand-holding.' },
]}
/>
<Prose>
<p>
<Code>--help</Code> lists flags. It cannot say which JSON keys a record
carries, that exit 4 means widen the query rather than retry it, that a
remote path is backslash-separated and has to go back byte-identical, or
that <Code>--follow</Code> never returns and an agent wants{' '}
<Code>--duration</Code> instead. An agent without that guesses, and guessing
against someone else&rsquo;s file server is how you get a hung job.
</p>
<p>
The skill is that missing half. Once it lands, the interesting requests stop
being pipelines you write: mirror a peer&rsquo;s FLAC folder overnight, watch
a room and save anything that mentions a label, check whether a user is worth
queueing behind before committing to a 2 GB transfer.
</p>
<p>
It stays a CLI — no daemon, no MCP server, no API key. Upgrading the binary
upgrades the skill; run <Code>skills install</Code> again to take it.{' '}
<Link to="/docs" className="text-link hover:text-link-hover">
See the commands
</Link>
</p>
</Prose>
</Cols>
</Section>
<Section band>
<Cols center>
<div className="flex flex-col gap-3.5">
+134
View File
@@ -67,6 +67,98 @@ const PLATFORMS: readonly PlatformNote[] = [
},
]
interface Setting {
key: string
fallback: string
body: React.ReactNode
}
const SETTINGS: readonly Setting[] = [
{
key: 'username',
fallback: 'unset',
body: <>Your Soulseek account name. The TUI writes it here after a first login.</>,
},
{
key: 'server',
fallback: 'server.slsknet.org:2416',
body: <>The server to log in to, as host:port.</>,
},
{
key: 'listener_port',
fallback: '2234',
body: (
<>
The TCP port peers connect back on. Forward it, or let <code>portmap</code> ask
your router.
</>
),
},
{
key: 'disable_listener',
fallback: 'false',
body: (
<>
<code>true</code> refuses incoming peer connections. You can still download from
reachable peers, but firewalled ones can no longer reach you.
</>
),
},
{
key: 'download_dir',
fallback: 'the Downloads path above',
body: <>Where finished transfers land.</>,
},
{
key: 'shared_dirs',
fallback: 'the download folder',
body: (
<>
Folders offered to the network, as a list. An explicitly empty list shares
nothing. <code>shared_dir</code> is the older single-folder spelling and still
reads; <code>shares add</code> writes the list form.
</>
),
},
{
key: 'max_concurrent_downloads',
fallback: '5',
body: <>Transfers allowed to run at once. The rest queue.</>,
},
{
key: 'search_timeout',
fallback: '10',
body: (
<>
Seconds a search collects replies before reporting. Results trickle in from
separate peers, so a short window finds less.
</>
),
},
{
key: 'password_cmd',
fallback: 'unset',
body: (
<>
A shell command whose stdout is the password, used when the keychain has none.
The password itself never belongs in this file.
</>
),
},
]
const CONFIG_EXAMPLE: TermLine[] = [
'username = "your-name"',
'download_dir = "~/Music/Soulseek"',
'shared_dirs = ["~/Music/Soulseek", "~/Music/Library"]',
'max_concurrent_downloads = 3',
'search_timeout = 15',
'listener_port = 2234',
'',
'# never store the password itself; shell out for it instead',
'password_cmd = "pass show soulseek"',
].map((text) => ({ t: 'code' as const, text }))
interface StepProps {
n: number
title: string
@@ -308,6 +400,48 @@ export const Install = () => {
</Cols>
</Section>
<Section id="config">
<SectionHead eyebrow="config.toml" title="Nine settings, all optional.">
Every one has a working default, so an empty file — or no file — is valid. Flags
beat environment variables, which beat this file, which beats the defaults.{' '}
<Code>config list</Code> prints the effective value of each.
</SectionHead>
<Cols start>
<div className="flex flex-col">
{SETTINGS.map((setting) => (
<div
key={setting.key}
className="grid grid-cols-1 gap-x-4 border-t border-hairline py-3.5 first:border-t-0 first:pt-0 sm:grid-cols-[minmax(0,15rem)_1fr]"
>
<div className="flex min-w-0 flex-col gap-1">
<code className="font-mono text-[12.5px] break-all text-primary">
{setting.key}
</code>
<span className="text-[11px] uppercase tracking-[var(--tracking-label)] text-secondary">
{setting.fallback}
</span>
</div>
<p className="mt-1.5 text-[13px] leading-[21px] text-pretty text-secondary sm:mt-0 [&_code]:font-mono [&_code]:text-primary">
{setting.body}
</p>
</div>
))}
</div>
<div className="flex flex-col gap-4">
<Terminal label="config.toml" lines={CONFIG_EXAMPLE} />
<Callout title="editing it without an editor">
<p>
<Code>config path</Code> prints the file in use, <Code>config get</Code>{' '}
reads one setting and <Code>config set</Code> writes one. Unknown keys are
rejected rather than silently ignored, so a typo fails loudly instead of
quietly doing nothing. <Code>--no-config</Code> ignores the file entirely
and <Code>--config FILE</Code> points at another one.
</p>
</Callout>
</div>
</Cols>
</Section>
<Section band>
<SectionHead eyebrow="uninstall" title="Removing it completely.">
Three things exist on disk: the binary, the config, and the state. Nothing else is