feat(cli): expose sharing, peer lookup and settings as one-shot commands
An agent driving the binary could search, fetch and chat, but it could not be a peer: nothing kept a session online to serve files, nothing reported an upload, shares could only be set at startup, and there was no way to ask whether a peer was worth queueing to or to check credentials without doing something else. New commands: serve [--follow] stay online sharing; one record per upload whoami credentials, connection and what we offer user <NAME> a peer's presence and share counts room users <ROOM> the member roster shares list|add|remove|status|reindex config path|list|get|set `config` and `shares list|add|remove` need no account, so they run before the credential check — asking a script to log in for a `config get` would be a poor trade. `serve` is the only place uploads are visible: they live in the process serving them, so a one-shot `uploads list` could print nothing but an empty list. Library additions, all request/poll pairs matching the existing conventions: - Client::room_members, kept current from the join and leave events - Client::request_user_info / user_info, backed by new handlers for GetUserStatus (7) and GetUserStats (36) — codes the dispatcher previously logged as unhandled. The wire format is taken from soulfind's own source and pinned by an e2e test that asserts a peer's real share count. - UserInfo carries presence and stats as Options, so a reply that never arrived cannot be read as "offline" or "shares nothing" - request_user_info clears the previous answer, so the documented poll loop observes this request rather than the last one - ServerMessage and ClientOperation are now #[non_exhaustive]: every protocol message the client learns adds a variant, and that should not break callers - the crate root and prelude re-export every type a Client method returns Also pins the output contract with tests: under --json, stdout carries only newline-delimited JSON across fifteen command shapes while progress still goes to stderr, and a failing command writes nothing to stdout at all.
This commit is contained in:
+48
-6
@@ -8,7 +8,7 @@ mod port_mapping;
|
||||
mod ui;
|
||||
|
||||
use clap::Parser;
|
||||
use cli::{Cli, Commands, parse_server_address};
|
||||
use cli::{Cli, Commands, ConfigCommand, SharesCommand, parse_server_address};
|
||||
use commands::Ctx;
|
||||
use output::{CliError, CliResult, Exit, Out};
|
||||
use soulseek_rs::{ClientSettings, PeerAddress};
|
||||
@@ -50,14 +50,56 @@ fn run(mut cli: Cli, out: &Out) -> CliResult {
|
||||
return run_default_tui(&cli, &resolved, config_path, &file_config);
|
||||
};
|
||||
|
||||
// A local network probe needs no account, so it runs before credentials
|
||||
// are demanded.
|
||||
if matches!(command, Commands::Portmap) {
|
||||
return commands::portmap(out, resolved.listener_port);
|
||||
// Anything that only reads the network stack or the config file runs
|
||||
// before credentials are demanded: asking a script for a login it does not
|
||||
// need would be a poor trade for a `config get`.
|
||||
let store = commands::settings::Store {
|
||||
path: config_path,
|
||||
config: file_config,
|
||||
};
|
||||
match command {
|
||||
Commands::Portmap => {
|
||||
return commands::portmap(out, resolved.listener_port);
|
||||
}
|
||||
Commands::Config(ConfigCommand::Path) => {
|
||||
return commands::settings::config_path(out, &store);
|
||||
}
|
||||
Commands::Config(ConfigCommand::List) => {
|
||||
commands::settings::config_list(out, &store);
|
||||
return Ok(());
|
||||
}
|
||||
Commands::Config(ConfigCommand::Get { ref key }) => {
|
||||
return commands::settings::config_get(out, &store, key);
|
||||
}
|
||||
Commands::Config(ConfigCommand::Set { ref key, ref value }) => {
|
||||
return commands::settings::config_set(out, &store, key, value);
|
||||
}
|
||||
Commands::Shares(SharesCommand::List) => {
|
||||
return commands::settings::shares_list(
|
||||
out,
|
||||
&store,
|
||||
&resolved.shared_dirs,
|
||||
);
|
||||
}
|
||||
Commands::Shares(SharesCommand::Add { ref directory }) => {
|
||||
return commands::settings::shares_add(out, &store, directory);
|
||||
}
|
||||
Commands::Shares(SharesCommand::Remove { ref directory }) => {
|
||||
return commands::settings::shares_remove(out, &store, directory);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let ctx = context(&cli, &resolved, out)?;
|
||||
commands::run(&ctx, command)
|
||||
match command {
|
||||
Commands::Shares(SharesCommand::Status) => {
|
||||
commands::settings::shares_status(&ctx)
|
||||
}
|
||||
Commands::Shares(SharesCommand::Reindex) => {
|
||||
commands::settings::shares_reindex(&ctx)
|
||||
}
|
||||
other => commands::run(&ctx, other),
|
||||
}
|
||||
}
|
||||
|
||||
/// Which config file to read: an explicit `--config`, the platform default,
|
||||
|
||||
Reference in New Issue
Block a user