use std::io;
use clap::{Parser, Subcommand};
use term_session::auto_spawn::connect_or_spawn_server;
use term_session_client::run_session;
use term_session_muxio_service_definitions::ChannelName;
#[derive(Parser, Debug)]
#[command(
name = env!("CARGO_PKG_NAME"),
version = env!("CARGO_PKG_VERSION"),
about = env!("CARGO_PKG_DESCRIPTION"),
long_about = concat!(env!("CARGO_PKG_NAME"), " ", env!("CARGO_PKG_VERSION"), ": ", env!("CARGO_PKG_DESCRIPTION")),
)]
struct Cli {
#[command(subcommand)]
command: Option<Command>,
#[arg(long, hide = true)]
daemon: bool,
#[arg(long)]
gateway: Option<String>,
#[arg(long, hide = true)]
daemon_selfcheck: Option<std::path::PathBuf>,
#[arg(short, long)]
channel: Option<String>,
#[arg(value_name = "CMD", num_args = 0.., trailing_var_arg = true, allow_hyphen_values = true)]
cmd: Vec<String>,
}
#[derive(Subcommand, Debug)]
enum Command {
#[command(name = "attach")]
Attach {
#[arg(short, long)]
channel: Option<String>,
#[arg(value_name = "CMD", num_args = 0.., trailing_var_arg = true, allow_hyphen_values = true)]
cmd: Vec<String>,
},
#[command(name = "ls", alias = "list")]
List,
Kill {
channel: String,
#[arg(long)]
kill_session: bool,
},
#[command(name = "kill-client")]
KillClient {
channel: String,
client_id: usize,
},
#[command(name = "stop")]
Stop,
}
fn main() -> io::Result<()> {
let cli = Cli::parse();
if let Some(ref gw) = cli.gateway {
unsafe {
std::env::set_var("TERM_WM_GATEWAY", gw);
}
}
if cli.daemon {
return term_session::run_daemon(cli.daemon_selfcheck);
}
match cli.command {
Some(Command::Attach { channel, cmd }) => attach(channel, &cmd),
Some(Command::List) => list(),
Some(Command::Kill {
channel,
kill_session: _,
}) => kill(&channel),
Some(Command::KillClient { channel, client_id }) => {
term_session::kill_client(&channel, client_id)?;
println!("Detached client {client_id} from channel {channel}");
Ok(())
}
Some(Command::Stop) => stop(),
None => attach(cli.channel, &cli.cmd),
}
}
fn attach(channel: Option<String>, cmd: &[String]) -> io::Result<()> {
let channel_str = term_session::resolve_channel(channel);
let channel = ChannelName::parse(&channel_str).map_err(|e| {
io::Error::new(io::ErrorKind::InvalidInput, format!("Invalid channel: {e}"))
})?;
let socket_name = connect_or_spawn_server(None)?;
run_session(&socket_name, &channel.to_string(), cmd)
}
fn list() -> io::Result<()> {
let resp = term_session::list_channels()?;
println!(
"Gateway Daemon PID: {} | Socket: {}",
resp.gateway_pid, resp.socket
);
if resp.channels.is_empty() {
println!("\nNo channels.");
return Ok(());
}
for ch in &resp.channels {
let session = ch
.session
.as_ref()
.map(|s| format!("session size: {}x{}", s.cols, s.rows))
.unwrap_or_else(|| "none".to_string());
let nclients = ch.clients.len();
println!();
println!("channel: {}", ch.name);
println!(
" created: {}",
term_session::format_unix_relative(ch.created_at_unix)
);
println!(" session: {}", session);
println!(
" clients: {}",
if nclients == 0 {
"none".to_string()
} else {
format!("{nclients} connected")
}
);
for c in &ch.clients {
println!(" - conn: {} (pid {})", c.conn_id, c.pid);
println!(" host: {}", c.hostname);
println!(" size: {}x{}", c.cols, c.rows);
println!(
" connected: {}",
term_session::format_unix_relative(c.connected_at_unix)
);
}
}
Ok(())
}
fn kill(channel: &str) -> io::Result<()> {
term_session::kill_channel(channel)?;
println!("Killed channel {channel}");
Ok(())
}
fn stop() -> io::Result<()> {
term_session::stop_gateway()?;
println!("Gateway shutdown initiated");
Ok(())
}