Files
soulseek-rs-modded/website/src/pages/docs.tsx
T
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

253 lines
9.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Link } from 'react-router'
import { Button } from '@/components/ui/button'
import { Code } from '@/components/ui/inline-code'
import {
Cols,
Eyebrow,
PageHead,
Prose,
Section,
SectionHead,
} from '@/components/ui/layout'
import { Callout, Panel } from '@/components/ui/panel'
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"],
['c', 'chat rooms'],
['m', 'compose a private message'],
['i', 'inbox (unread counter)'],
['/', 'filter the current list'],
['a / A', 'select all / none'],
['1–3', 'focus a pane'],
['q', 'quit'],
]
const ROOM_KEYS: readonly (readonly [string, string])[] = [
['Enter', 'join room / send message'],
['Tab · ⇧Tab', 'switch between open rooms'],
['x', 'leave the active room'],
['l', 'back to the room list'],
['↑ ↓', 'select a member'],
['b', "browse the member's files"],
['m', 'message the member'],
]
const PANES: readonly { num?: number; title: string; body: string }[] = [
{
num: 1,
title: 'Searches',
body: 'Your queries and how many results each returned. Press s to search the network; each search keeps its own result set.',
},
{
num: 2,
title: 'Results',
body: 'Files from the network: size, user, bitrate, speed, free slots. Space selects, Enter queues a download.',
},
{
num: 3,
title: 'Downloads / Uploads',
body: 'Live transfers in both directions with progress and speed: queued, active, complete, failed.',
},
{
title: 'Info',
body: 'Everything about the selected file: user, size, path, bitrate, length, queue position, free slots.',
},
]
const KeyRow = ({ combo, description }: { combo: string; description: string }) => (
<div className="grid grid-cols-[120px_1fr] items-baseline gap-3 border-b border-hairline py-[7px]">
<span>
<span className="inline-block min-w-5 rounded-md border border-line px-[7px] text-center text-xs text-primary shadow-[0_1px_0_0_var(--border-default)]">
{combo}
</span>
</span>
<span className="text-[13px] text-secondary">{description}</span>
</div>
)
export const Docs = () => {
return (
<>
<PageHead
eyebrow="docs · quick start"
title="Install it, run it, drive it from the keyboard."
>
This is the short version. The full reference (every flag, every message) lives in
the README and on docs.rs.
</PageHead>
<Section>
<SectionHead eyebrow="1 · first run" title="Two commands to a running client.">
You need a Rust toolchain. After <Code>cargo install</Code>, the binary is on your
PATH.
</SectionHead>
<Cols start>
<Terminal
lines={[
{ t: 'cmd', text: 'cargo install soulseek-rs' },
{ t: 'cmd', text: 'soulseek-rs' },
{ t: 'cm', text: '# the TUI opens; log in or register on first run' },
]}
/>
<Prose>
<p>
On first run soulseek-rs shows a login screen: sign in with an existing
Soulseek account or register a new one, right in the TUI. Your password is
stored in the OS keychain, not in plain text.
</p>
<p>
Downloads land in a conventional folder by default, and your TUI state
(searches, downloads, rooms) is saved and restored across restarts.
</p>
</Prose>
</Cols>
</Section>
<Section>
<SectionHead eyebrow="2 · the layout" title="Four panes.">
The whole client is these four boxes. Press <Code>1</Code>–<Code>3</Code> to focus
one; the focused pane&rsquo;s legend turns green.
</SectionHead>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
{PANES.map((pane) => (
<Panel key={pane.title} num={pane.num} title={pane.title}>
<p className="text-[13px] leading-[22px] text-secondary">{pane.body}</p>
</Panel>
))}
</div>
</Section>
<Section>
<SectionHead eyebrow="3 · keys" title="It's all keyboard.">
The bottom bar always shows the keys for where you are. The essentials:
</SectionHead>
<Cols>
<div>
<Eyebrow className="mb-4 block">anywhere</Eyebrow>
<div className="flex flex-col">
{GLOBAL_KEYS.map(([combo, description]) => (
<KeyRow key={combo} combo={combo} description={description} />
))}
</div>
</div>
<div>
<Eyebrow className="mb-4 block">
in the chat-rooms popup <span className="text-muted">(press c)</span>
</Eyebrow>
<div className="flex flex-col">
{ROOM_KEYS.map(([combo, description]) => (
<KeyRow key={combo} combo={combo} description={description} />
))}
</div>
</div>
</Cols>
</Section>
<Section>
<SectionHead
eyebrow="4 · from the shell"
title="Every command also runs as a one-off."
>
Skip the TUI when you&rsquo;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 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 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,
worth running once.{' '}
<Link to="/install" className="text-link hover:text-link-hover">
More on being reachable
</Link>
</p>
</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. 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>
</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
that peer can&rsquo;t work, that&rsquo;s a fundamental Soulseek/peer-to-peer
limitation, not a bug.
</p>
</Callout>
</div>
</Section>
<Section band>
<Panel title="the rest" className="bg-raised [&>span]:bg-panel">
<Cols center className="gap-6">
<Prose>
<p>
Deeper docs live where they stay current: the full README on GitHub, and the
generated API reference for the library on docs.rs.
</p>
</Prose>
<div className="flex flex-wrap gap-2 sm:gap-3">
<Button href={LINKS.gh} variant="accent">
README
</Button>
<Button href={LINKS.docsrs}>docs.rs</Button>
<Button href={LINKS.changelog}>Changelog</Button>
</div>
</Cols>
</Panel>
</Section>
</>
)
}