use crate::repl::runner::Runner;
use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use std::ffi::OsString;
pub mod attach;
pub mod auth;
pub mod console;
pub mod daemon;
pub mod external;
pub mod hub;
#[derive(Parser, Debug)]
#[command(name = "crabtalk", about = "Crabtalk CLI client")]
pub struct Cli {
#[command(subcommand)]
pub command: Command,
}
impl Cli {
pub fn log_filter(&self) -> Option<&'static str> {
match self.command {
Command::Daemon(ref d)
if matches!(d.command, daemon::DaemonCommand::Run) && d.verbose > 0 =>
{
Some(match d.verbose {
1 => "crabtalk=info",
2 => "crabtalk=debug",
_ => "crabtalk=trace",
})
}
Command::Hub(_) => Some("crabtalk=info"),
_ => None,
}
}
pub async fn run(self) -> Result<()> {
match self.command {
Command::Auth(cmd) => cmd.run().await,
Command::Attach(cmd) => {
let runner = connect_default_or_tcp(cmd.tcp).await?;
cmd.run(runner).await
}
Command::Console(cmd) => {
let runner = connect_default().await?;
let selected = cmd.run(runner).await?;
if let Some(path) = selected {
let runner = connect_default().await?;
let mut repl = crate::repl::ChatRepl::new(runner, "crab".into())?;
repl.resume(path).await
} else {
Ok(())
}
}
Command::Hub(cmd) => {
let mut runner = connect_default().await?;
cmd.run(&mut runner).await
}
Command::Daemon(cmd) => cmd.run().await,
Command::Ls => {
let run_dir = &*wcore::paths::RUN_DIR;
let mut found = false;
if let Ok(entries) = std::fs::read_dir(run_dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) != Some("port") {
continue;
}
let Some(name) = path.file_stem().and_then(|s| s.to_str()) else {
continue;
};
if name == "crabtalk" {
continue;
}
if let Ok(contents) = std::fs::read_to_string(&path)
&& let Ok(port) = contents.trim().parse::<u16>()
{
let alive = std::net::TcpStream::connect(("127.0.0.1", port)).is_ok();
let status = if alive { "running" } else { "stale" };
println!("{name}\t:{port}\t{status}");
found = true;
}
}
}
if !found {
println!("no services running");
}
Ok(())
}
Command::External(args) => external::run(args),
}
}
}
#[derive(Subcommand, Debug)]
pub enum Command {
Attach(attach::Attach),
Auth(auth::Auth),
Console(console::Console),
Hub(hub::Hub),
Daemon(daemon::Daemon),
Ls,
#[command(external_subcommand)]
External(Vec<OsString>),
}
async fn connect_default_or_tcp(use_tcp: bool) -> Result<Runner> {
if use_tcp {
connect_tcp().await
} else {
connect_default().await
}
}
pub(crate) async fn connect_default() -> Result<Runner> {
#[cfg(unix)]
{
let socket_path = &*wcore::paths::SOCKET_PATH;
Runner::connect(socket_path).await.with_context(|| {
format!(
"failed to connect to crabtalk daemon at {}. Is crabtalk daemon running?",
socket_path.display()
)
})
}
#[cfg(not(unix))]
{
connect_tcp().await
}
}
pub(crate) async fn connect_tcp() -> Result<Runner> {
let tcp_port_file = &*wcore::paths::TCP_PORT_FILE;
let port_str = std::fs::read_to_string(tcp_port_file).with_context(|| {
format!(
"failed to read TCP port file at {}. Is crabtalk daemon running?",
tcp_port_file.display()
)
})?;
let port: u16 = port_str
.trim()
.parse()
.with_context(|| format!("invalid port in {}", tcp_port_file.display()))?;
Runner::connect_tcp(port).await.with_context(|| {
format!("failed to connect to crabtalk daemon via TCP on port {port}. Is crabtalk daemon running?")
})
}