pub mod agent_packaging;
pub mod agent_rules;
pub mod append;
pub mod apply_fragment;
#[cfg(feature = "ast")]
pub mod ast;
pub mod batch;
pub mod create;
pub mod delete;
pub mod doc;
pub mod explain;
pub mod init;
pub mod list_files;
#[cfg(feature = "mcp")]
pub mod mcp;
pub mod md;
pub mod output;
pub mod patch;
pub mod prepend;
pub mod read;
pub mod rename;
pub mod replace;
pub mod schema;
pub mod search;
pub mod status;
pub mod tidy;
pub mod tx;
pub mod undo;
pub mod write_dispatch;
pub mod write_mode;
pub mod write_targets;
use crate::cli::Cli;
use clap::Subcommand;
#[derive(Debug, Subcommand)]
pub enum Command {
#[command(display_order = 10)]
Append(append::AppendArgs),
#[command(display_order = 11)]
Create(create::CreateArgs),
#[command(display_order = 12)]
Delete(delete::DeleteArgs),
#[command(display_order = 11)]
Prepend(prepend::PrependArgs),
#[command(display_order = 13)]
Read(read::ReadArgs),
#[command(display_order = 14)]
Rename(rename::RenameArgs),
#[command(display_order = 15, name = "list-files")]
ListFiles(list_files::ListFilesArgs),
#[command(display_order = 20)]
Search(search::SearchArgs),
#[command(display_order = 21)]
Replace(replace::ReplaceArgs),
#[command(display_order = 22, name = "apply-fragment")]
ApplyFragment(apply_fragment::ApplyFragmentArgs),
#[command(display_order = 23)]
Patch(patch::PatchArgs),
#[command(display_order = 23)]
Tidy(tidy::TidyArgs),
#[command(display_order = 24)]
Batch(batch::BatchArgs),
#[command(display_order = 30)]
Doc(doc::DocArgs),
#[command(display_order = 31)]
Md(md::MdArgs),
#[cfg(feature = "ast")]
#[command(display_order = 40)]
Ast(ast::AstArgs),
#[command(display_order = 50)]
Tx(crate::tx::TxArgs),
#[command(display_order = 51)]
Explain(explain::ExplainArgs),
#[command(display_order = 52)]
Undo(undo::UndoArgs),
#[command(display_order = 53)]
Status(status::StatusArgs),
#[command(display_order = 54)]
Schema(schema::SchemaArgs),
#[command(display_order = 55)]
AgentRules(agent_rules::AgentRulesArgs),
#[command(display_order = 56)]
Init(init::InitArgs),
#[cfg(feature = "mcp")]
#[command(
display_order = 57,
after_help = "\
ENVIRONMENT:
PATCHLOOM_MCP_SURFACE=core|full
Tool inventory at handshake (env only; no CLI flag). Unset or `full`
keeps the full inventory (product default). `core` registers the
12-tool pack for small agents. See README.md (MCP / coding agents)."
)]
McpServer {
#[arg(long)]
log: Option<String>,
#[cfg(feature = "mcp-http")]
#[arg(long)]
http: bool,
#[cfg(feature = "mcp-http")]
#[arg(long, default_value = "127.0.0.1", requires = "http")]
host: String,
#[cfg(feature = "mcp-http")]
#[arg(long, default_value_t = 8080, requires = "http")]
port: u16,
#[cfg(feature = "mcp-http")]
#[arg(long, requires_all = ["http", "tls_key"])]
tls_cert: Option<std::path::PathBuf>,
#[cfg(feature = "mcp-http")]
#[arg(long, requires_all = ["http", "tls_cert"])]
tls_key: Option<std::path::PathBuf>,
#[cfg(feature = "mcp-http")]
#[arg(long, requires = "http")]
allow_unauthenticated: bool,
#[cfg(feature = "mcp-http")]
#[arg(long, requires = "http", action = clap::ArgAction::Append, value_name = "NAME")]
allowed_host: Vec<String>,
},
#[command(display_order = 58)]
Completions {
#[arg(value_enum)]
shell: clap_complete::Shell,
},
}
fn emit_completions(shell: clap_complete::Shell) -> anyhow::Result<u8> {
let mut cmd = <Cli as clap::CommandFactory>::command();
let mut buf = Vec::new();
clap_complete::generate(shell, &mut cmd, "patchloom", &mut buf);
crate::json_emit::write_stdout_ignore_epipe(&buf, false)?;
Ok(crate::exit::SUCCESS)
}
fn load_project_config(global: &mut crate::cli::global::GlobalFlags) -> anyhow::Result<()> {
let Ok(cwd) = global.resolve_cwd() else {
return Ok(());
};
if let Some((config, _)) = crate::config::find_and_load_strict(&cwd)? {
crate::config::apply_config(global, &config);
}
Ok(())
}
pub fn dispatch(cli: Cli) -> anyhow::Result<u8> {
let mut global = cli.global;
match cli.command {
#[cfg(feature = "mcp")]
Command::McpServer {
log,
#[cfg(feature = "mcp-http")]
http,
#[cfg(feature = "mcp-http")]
host,
#[cfg(feature = "mcp-http")]
port,
#[cfg(feature = "mcp-http")]
tls_cert,
#[cfg(feature = "mcp-http")]
tls_key,
#[cfg(feature = "mcp-http")]
allow_unauthenticated,
#[cfg(feature = "mcp-http")]
allowed_host,
} => {
load_project_config(&mut global)?;
#[cfg(feature = "mcp-http")]
if http {
return mcp::run_mcp_http_server(
&global,
log,
mcp::McpHttpListen {
host: &host,
port,
tls_cert: tls_cert.as_deref(),
tls_key: tls_key.as_deref(),
allow_unauthenticated,
allowed_hosts: &allowed_host,
},
);
}
mcp::run_mcp_server(&global, log)
}
Command::Schema(args) => schema::run(args, &global),
Command::AgentRules(args) => agent_rules::run(args, &global),
Command::Init(args) => init::run(args, &global),
Command::Completions { shell } => {
if global.json || global.jsonl {
global.emit_error_json_kind(
Some("invalid_input"),
"completions does not support --json/--jsonl; omit those flags to print a shell script",
)?;
return Ok(crate::exit::FAILURE);
}
emit_completions(shell)
}
Command::Read(args) => {
load_project_config(&mut global)?;
read::run(args, &global)
}
Command::Explain(args) => {
load_project_config(&mut global)?;
explain::run(args, &global)
}
Command::Undo(args) => {
load_project_config(&mut global)?;
undo::run(args, &global)
}
Command::Search(args) => {
load_project_config(&mut global)?;
search::run(args, &global)
}
Command::Status(args) => {
load_project_config(&mut global)?;
status::run(args, &global)
}
Command::ListFiles(args) => {
load_project_config(&mut global)?;
list_files::run(args, &global)
}
Command::Append(args) => {
global.merge_write(&args.write);
load_project_config(&mut global)?;
append::run(args, &global)
}
Command::Prepend(args) => {
global.merge_write(&args.write);
load_project_config(&mut global)?;
prepend::run(args, &global)
}
Command::Create(args) => {
global.merge_write(&args.write);
load_project_config(&mut global)?;
create::run(args, &global)
}
Command::Delete(args) => {
global.merge_write(&args.write);
load_project_config(&mut global)?;
delete::run(args, &global)
}
Command::Rename(args) => {
global.merge_write(&args.write);
load_project_config(&mut global)?;
rename::run(args, &global)
}
Command::Replace(args) => {
global.merge_write(&args.write);
load_project_config(&mut global)?;
replace::run(args, &global)
}
Command::ApplyFragment(args) => {
global.merge_write(&args.write);
load_project_config(&mut global)?;
apply_fragment::run(args, &global)
}
Command::Patch(args) => {
global.merge_write(&args.write);
load_project_config(&mut global)?;
patch::run(args, &global)
}
Command::Md(args) => {
global.merge_write(&args.write);
load_project_config(&mut global)?;
md::run(args, &global)
}
Command::Doc(args) => {
global.merge_write(&args.write);
load_project_config(&mut global)?;
doc::run(args, &global)
}
Command::Tidy(args) => {
global.merge_write(&args.write);
load_project_config(&mut global)?;
tidy::run(args, &global)
}
Command::Tx(args) => {
global.merge_write(&args.write);
load_project_config(&mut global)?;
tx::run(args, &global)
}
Command::Batch(args) => {
global.merge_write(&args.write);
load_project_config(&mut global)?;
batch::run(args, &global)
}
#[cfg(feature = "ast")]
Command::Ast(args) => {
match args.command {
ast::AstCommand::Rename(ref a) => global.merge_write(&a.write),
ast::AstCommand::Replace(ref a) => global.merge_write(&a.write),
ast::AstCommand::ReplaceSymbol(ref a) => global.merge_write(&a.write),
ast::AstCommand::DeleteSymbol(ref a) => global.merge_write(&a.write),
ast::AstCommand::Insert(ref a) => global.merge_write(&a.write),
ast::AstCommand::Wrap(ref a) => global.merge_write(&a.write),
ast::AstCommand::Imports(ref a) => global.merge_write(&a.write),
ast::AstCommand::Reorder(ref a) => global.merge_write(&a.write),
ast::AstCommand::Group(ref a) => global.merge_write(&a.write),
ast::AstCommand::Move(ref a) => global.merge_write(&a.write),
ast::AstCommand::ExtractToFile(ref a) => global.merge_write(&a.write),
ast::AstCommand::Split(ref a) => global.merge_write(&a.write),
ast::AstCommand::RewriteSignature(ref a) => global.merge_write(&a.write),
_ => {}
}
load_project_config(&mut global)?;
ast::run(args, &global)
}
}
}