mod mcp;
use std::env;
use std::path::PathBuf;
use std::process::ExitCode;
use weavatrix_rust::{Analyzer, Weavatrix, operations};
fn main() -> ExitCode {
match run(env::args().skip(1).collect()) {
Ok(code) => code,
Err(message) => {
eprintln!("weavatrix: {message}");
ExitCode::FAILURE
}
}
}
fn run(arguments: Vec<String>) -> Result<ExitCode, String> {
if arguments.first().is_some_and(|value| value == "--version") {
println!(
"weavatrix {} (engine {})",
env!("CARGO_PKG_VERSION"),
weavatrix_rust::VERSION
);
return Ok(ExitCode::SUCCESS);
}
if arguments
.first()
.is_some_and(|value| value == "--help" || value == "-h")
{
print_help();
return Ok(ExitCode::SUCCESS);
}
match arguments.first().map(String::as_str) {
Some("mcp") => serve_mcp(&arguments),
Some("list-tools") => list_operations(&arguments),
Some("tool") => call_operation(&arguments),
Some("analyze") => analyze(arguments),
_ => {
print_help();
Err("expected the `mcp`, `analyze`, `tool`, or `list-tools` command".into())
}
}
}
fn serve_mcp(arguments: &[String]) -> Result<ExitCode, String> {
let mut repository = ".";
let mut profile = mcp::McpProfile::All;
let mut output_format = None;
for argument in arguments.iter().skip(1) {
if let Some(value) = argument.strip_prefix("--profile=") {
profile = value.parse()?;
} else if let Some(value) = argument.strip_prefix("--output-format=") {
output_format = Some(value.to_owned());
} else if argument.starts_with('-') {
return Err(format!("unknown MCP option: {argument}"));
} else {
repository = argument;
}
}
let options = mcp::ServeOptions {
profile,
default_payload: mcp::parse_output_format(&default_output_format(output_format))?,
};
let mut server = mcp::build_server(repository, options).map_err(|error| error.to_string())?;
mcport::serve(&mut server)
.map(|()| ExitCode::SUCCESS)
.map_err(|error| error.to_string())
}
fn default_output_format(flag: Option<String>) -> String {
flag.or_else(|| {
env::var("WEAVATRIX_OUTPUT_FORMAT")
.ok()
.filter(|value| !value.trim().is_empty())
})
.unwrap_or_else(|| "json".to_owned())
}
fn list_operations(arguments: &[String]) -> Result<ExitCode, String> {
let mut profile = mcp::McpProfile::All;
for argument in arguments.iter().skip(1) {
if let Some(value) = argument.strip_prefix("--profile=") {
profile = value.parse()?;
} else {
return Err(format!("unknown list-tools option: {argument}"));
}
}
println!(
"{}",
blazingly_json::to_string_pretty(&operations::catalog_for_profile(profile))
.map_err(|error| error.to_string())?
);
Ok(ExitCode::SUCCESS)
}
fn call_operation(arguments: &[String]) -> Result<ExitCode, String> {
let name = arguments
.get(1)
.ok_or_else(|| "tool requires an operation name".to_owned())?;
let repository = arguments.get(2).map_or(".", String::as_str);
let input = arguments
.get(3)
.map_or_else(
|| Ok(blazingly_json::json!({})),
|value| blazingly_json::from_str(value),
)
.map_err(|error| format!("invalid operation JSON: {error}"))?;
let mut engine = Weavatrix::open(repository).map_err(|error| error.to_string())?;
let output = operations::call(&mut engine, name, input)?;
println!(
"{}",
blazingly_json::to_string_pretty(&output).map_err(|error| error.to_string())?
);
if matches!(name.as_str(), "verify_architecture" | "verify_capabilities")
&& output["state"] == "BLOCKED"
{
return Ok(ExitCode::FAILURE);
}
Ok(ExitCode::SUCCESS)
}
fn analyze(arguments: Vec<String>) -> Result<ExitCode, String> {
let mut repository = PathBuf::from(".");
let mut pretty = false;
let mut legacy = false;
for argument in arguments.into_iter().skip(1) {
if argument == "--pretty" {
pretty = true;
} else if argument == "--format=legacy" {
legacy = true;
} else if argument == "--format=snapshot" {
legacy = false;
} else if argument.starts_with('-') {
return Err(format!("unknown analyze option: {argument}"));
} else {
repository = PathBuf::from(argument);
}
}
let analyzer = Analyzer::default();
let json = if legacy {
analyzer.analyze_legacy_json(repository, pretty)
} else {
analyzer.analyze_json(repository, pretty)
}
.map_err(|error| error.to_string())?;
println!("{json}");
Ok(ExitCode::SUCCESS)
}
fn print_help() {
println!(
"Weavatrix repository intelligence for coding agents\n\n\
Usage:\n weavatrix mcp [REPOSITORY] [--profile=all|code|seo] \
[--output-format=json|text|structured]\n\
weavatrix analyze [REPOSITORY] [--pretty] [--format=snapshot|legacy]\n\
weavatrix list-tools [--profile=all|code|seo]\n\
weavatrix tool NAME [REPOSITORY] ['{{\"argument\":\"value\"}}']\n\
weavatrix --version\n\n\
Output format (also WEAVATRIX_OUTPUT_FORMAT; a call may still name its own):\n\
json structuredContent plus the text mirror clients without \
structured\n output need. The default.\n\
structured structuredContent alone. The mirror is the pretty-printed copy \
of\n the payload, so dropping it roughly halves every answer.\n\
text the concise text block alone, no structuredContent."
);
}