use crate::config::Config;
use crate::ctx::Ctx;
use crate::errors;
use crate::registry::Registry;
pub const fn cli_frontend_compiled() -> bool {
cfg!(feature = "cli")
}
pub const fn http_frontend_compiled() -> bool {
cfg!(feature = "http")
}
pub const fn mcp_frontend_compiled() -> bool {
cfg!(feature = "mcp")
}
pub fn main(cmds: &[&dyn crate::builder::Definable]) -> ! {
if !cmds.is_empty() {
for cmd in cmds {
if let Err(e) = cmd.register(Registry::default()) {
eprintln!("{e}");
std::process::exit(2);
}
}
}
let args: Vec<String> = std::env::args().skip(1).collect();
std::process::exit(run(Registry::default(), args));
}
pub fn main_config(cfg: Config) -> ! {
let args: Vec<String> = std::env::args().skip(1).collect();
std::process::exit(run_config(Registry::default(), args, cfg));
}
pub fn run(reg: &Registry, args: Vec<String>) -> i32 {
run_config(reg, args, Config::default())
}
pub fn run_config(reg: &Registry, args: Vec<String>, cfg: Config) -> i32 {
run_internal(reg, args, cfg, false).0
}
pub fn try_run(reg: &Registry, args: Vec<String>) -> (i32, bool) {
try_run_config(reg, args, Config::default())
}
pub fn try_run_config(reg: &Registry, args: Vec<String>, cfg: Config) -> (i32, bool) {
run_internal(reg, args, cfg, true)
}
fn cli_known_top(reg: &Registry, first: &str) -> bool {
if first.starts_with('-') {
return true; }
for e in reg.all() {
if e.cli.skip {
continue;
}
if e.name.split('.').next() == Some(first) {
return true;
}
if e.cli.aliases.iter().any(|a| a == first) {
return true;
}
if e.cli.default {
return true; }
}
false
}
fn run_internal(reg: &Registry, args: Vec<String>, cfg: Config, composable: bool) -> (i32, bool) {
let (serve, mcp_word, help_word) = match resolve_modes(&cfg) {
Ok(m) => m,
Err(e) => {
eprintln!("{e}");
return (2, true);
}
};
if let Err(e) = check_reserved(reg, &serve, &mcp_word, &help_word) {
eprintln!("{e}");
return (2, true);
}
if reg.names().is_empty() {
return (0, true);
}
for a in &args {
if a == "--" {
break;
}
if a == "-v" || a == "--version" {
let bin = crate::cli::app::bin_name();
println!("{bin} version {}", crate::version::version());
return (0, true);
}
}
let mut cfg = cfg;
let args = match crate::builtins::strip_xyz_flags(args, &mut cfg) {
Ok(a) => a,
Err(e) => {
eprintln!("xyz: {e}");
return (2, true);
}
};
if cfg.log_level != crate::logx::Level::Unset {
crate::logx::set_level(cfg.log_level);
}
let resolved_lang = if !cfg.lang.is_empty() {
match crate::lang::XyzLang::parse(&cfg.lang) {
Some(l) => l,
None => {
eprintln!("xyz: invalid --xyz.lang {:?} (want en|zh-CN)", cfg.lang);
return (2, true);
}
}
} else {
crate::lang::XyzLang::detect()
};
crate::lang::set(
resolved_lang,
cfg.translations.get(resolved_lang.as_str()).cloned(),
);
if args.is_empty() || args[0] == help_word || args[0] == "--help" || args[0] == "-h" {
let mut stdout = std::io::stdout();
let _ = crate::overview::print_overview(
&mut stdout,
reg,
&serve,
&mcp_word,
cfg.capabilities,
&cfg.help_before,
&cfg.help_after,
);
return (0, true);
}
let ctx = Ctx::new();
spawn_signal_watcher(ctx.clone());
crate::logx::debugf(format_args!(
"dispatch: mode word='{}' addr={} tokens={} timeout={:?} cors={}",
args[0],
cfg.addr,
cfg.bearer_tokens.len(),
cfg.timeout,
cfg.cors_origins.len()
));
if args[0] == serve {
if cfg.capabilities.no_http {
crate::logx::warnf(format_args!(
"{}",
crate::lang::tf("warn.mode_disabled", &[&serve, "HTTP"])
));
return (1, true);
}
return (run_serve(&ctx, reg, &args[1..], cfg), true);
}
if args[0] == mcp_word {
if cfg.capabilities.no_mcp {
crate::logx::warnf(format_args!(
"{}",
crate::lang::tf("warn.mode_disabled", &[&mcp_word, "MCP"])
));
return (1, true);
}
return (run_mcp(&ctx, reg, &args[1..], cfg), true);
}
if cfg.capabilities.no_cli {
crate::logx::warnf(format_args!(
"{}",
crate::lang::tf("warn.no_cli", &[&mcp_word, &serve])
));
return (1, true);
}
if composable && !args.is_empty() && !cli_known_top(reg, &args[0]) {
return (0, false);
}
(run_cli(&ctx, reg, &args, &cfg), true)
}
fn resolve_modes(cfg: &Config) -> errors::Result<(String, String, String)> {
let mut serve = cfg.modes.serve.clone();
let mut mcp_word = cfg.modes.mcp.clone();
let mut help_word = cfg.modes.help.clone();
if serve.is_empty() {
serve = "serve".to_string();
}
if mcp_word.is_empty() {
mcp_word = "mcp".to_string();
}
if help_word.is_empty() {
help_word = "help".to_string();
}
for w in [&serve, &mcp_word, &help_word] {
if w.starts_with('-') || w.chars().any(|c| c == ' ' || c == '\t') {
return Err(errors::Error::new(
errors::Kind::Internal,
format!("xyz: invalid mode word {w:?} (no leading dash, no whitespace)"),
));
}
}
if serve == mcp_word || serve == help_word || mcp_word == help_word {
return Err(errors::Error::new(
errors::Kind::Internal,
format!(
"xyz: mode words must be pairwise distinct (serve={serve:?} mcp={mcp_word:?} help={help_word:?})"
),
));
}
Ok((serve, mcp_word, help_word))
}
fn check_reserved(
reg: &Registry,
serve: &str,
mcp_word: &str,
help_word: &str,
) -> errors::Result<()> {
for name in reg.names() {
let top = name.split('.').next().unwrap_or("");
if top == serve || top == mcp_word || top == help_word {
return Err(errors::Error::new(
errors::Kind::Internal,
format!(
"xyz: command {name:?}: top-level name {top:?} is reserved for mode dispatch"
),
));
}
}
Ok(())
}
fn spawn_signal_watcher(ctx: Ctx) {
#[cfg(not(any(feature = "http-stack", feature = "cli")))]
let _ = &ctx;
#[cfg(feature = "http-stack")]
{
std::thread::spawn(move || {
let rt = match tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
{
Ok(rt) => rt,
Err(_) => return,
};
rt.block_on(async move {
let _ = tokio::signal::ctrl_c().await;
ctx.cancel();
});
});
}
#[cfg(all(not(feature = "http-stack"), feature = "cli"))]
{
let _ = ctrlc::set_handler(move || {
ctx.cancel();
});
}
}
#[cfg(feature = "cli")]
fn run_cli(ctx: &Ctx, reg: &Registry, args: &[String], cfg: &Config) -> i32 {
crate::cli::run_context(
ctx,
reg,
args,
crate::cli::Options {
format: Some(cfg.format.clone()),
..Default::default()
},
)
}
#[cfg(not(feature = "cli"))]
fn run_cli(_ctx: &Ctx, _reg: &Registry, _args: &[String], _cfg: &Config) -> i32 {
eprintln!("xyz: {}", crate::lang::tf("stub.not_compiled", &["CLI"]));
1
}
#[cfg(feature = "http")]
fn run_serve(ctx: &Ctx, reg: &Registry, args: &[String], cfg: Config) -> i32 {
crate::httpapi::serve(ctx, reg, args, cfg)
}
#[cfg(not(feature = "http"))]
fn run_serve(_ctx: &Ctx, _reg: &Registry, _args: &[String], _cfg: Config) -> i32 {
eprintln!("xyz: {}", crate::lang::tf("stub.not_compiled", &["HTTP"]));
1
}
#[cfg(feature = "mcp")]
fn run_mcp(ctx: &Ctx, reg: &Registry, args: &[String], cfg: Config) -> i32 {
crate::mcp::run_with_config(ctx, reg, args, cfg)
}
#[cfg(not(feature = "mcp"))]
fn run_mcp(_ctx: &Ctx, _reg: &Registry, _args: &[String], _cfg: Config) -> i32 {
eprintln!("xyz: {}", crate::lang::tf("stub.not_compiled", &["MCP"]));
1
}