feat(xy): clap CLI scaffold

This commit is contained in:
2026-05-25 11:49:47 +02:00
parent 49c006df10
commit 71808783c4
3 changed files with 87 additions and 4 deletions
+10
View File
@@ -0,0 +1,10 @@
use crate::paths::Paths;
use anyhow::{bail, Result};
pub async fn list(_p: Paths) -> Result<i32> { bail!("not implemented") }
pub async fn status(_p: Paths, _name: String) -> Result<i32> { bail!("not implemented") }
pub async fn start(_p: Paths, _all: bool, _name: Option<String>) -> Result<i32> { bail!("not implemented") }
pub async fn stop(_p: Paths, _all: bool, _name: Option<String>) -> Result<i32> { bail!("not implemented") }
pub async fn restart(_p: Paths, _all: bool, _name: Option<String>) -> Result<i32> { bail!("not implemented") }
pub async fn reload(_p: Paths) -> Result<i32> { bail!("not implemented") }
pub async fn logs(_p: Paths, _name: String, _tail: Option<u32>, _follow: bool) -> Result<i32> { bail!("not implemented") }
+4
View File
@@ -0,0 +1,4 @@
use crate::paths::Paths;
use anyhow::{bail, Result};
pub async fn run(_paths: Paths) -> Result<()> { bail!("not implemented") }
+73 -4
View File
@@ -1,8 +1,77 @@
use clap::{Parser, Subcommand};
mod cli;
mod daemon;
mod paths;
#[allow(dead_code)]
mod pidfile;
fn main() {
let p = paths::Paths::resolve().unwrap();
eprintln!("xy: socket would be at {}", p.socket.display());
#[derive(Debug, Parser)]
#[command(name = "xy", version, about = "HTTP MCP server supervisor")]
struct Cli {
#[command(subcommand)]
cmd: Cmd,
}
#[derive(Debug, Subcommand)]
enum Cmd {
/// Run the daemon in the foreground.
Daemon,
/// List all configured servers with state.
List,
/// Show detailed status for a single server.
Status { name: String },
/// Start a server (or all configured servers with --all).
Start {
#[arg(long, conflicts_with = "name")] all: bool,
#[arg(required_unless_present = "all")] name: Option<String>,
},
/// Stop a server (or --all).
Stop {
#[arg(long, conflicts_with = "name")] all: bool,
#[arg(required_unless_present = "all")] name: Option<String>,
},
/// Restart a server (or --all).
Restart {
#[arg(long, conflicts_with = "name")] all: bool,
#[arg(required_unless_present = "all")] name: Option<String>,
},
/// Re-read config dir and reconcile running servers.
Reload,
/// Stream a server's log.
Logs {
name: String,
#[arg(long)] tail: Option<u32>,
#[arg(short = 'f', long)] follow: bool,
},
}
#[tokio::main]
async fn main() -> std::process::ExitCode {
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")))
.with_writer(std::io::stderr)
.init();
let cli = Cli::parse();
let paths = match paths::Paths::resolve() {
Ok(p) => p,
Err(e) => { eprintln!("xy: failed to resolve XDG paths: {e}"); return std::process::ExitCode::from(3); }
};
let result: anyhow::Result<i32> = match cli.cmd {
Cmd::Daemon => daemon::run(paths).await.map(|_| 0),
Cmd::List => cli::list(paths).await,
Cmd::Status { name } => cli::status(paths, name).await,
Cmd::Start { all, name } => cli::start(paths, all, name).await,
Cmd::Stop { all, name } => cli::stop(paths, all, name).await,
Cmd::Restart { all, name } => cli::restart(paths, all, name).await,
Cmd::Reload => cli::reload(paths).await,
Cmd::Logs { name, tail, follow } => cli::logs(paths, name, tail, follow).await,
};
match result {
Ok(code) => std::process::ExitCode::from(code as u8),
Err(e) => { eprintln!("xy: {e:#}"); std::process::ExitCode::from(1) }
}
}