use clap::{Args, Parser, Subcommand, ValueEnum};
#[derive(Parser, Debug)]
#[command(
name = "ilo",
version,
about = "Token-minimal programming language for AI agents"
)]
#[command(args_conflicts_with_subcommands = true)]
#[command(disable_help_subcommand = true)]
#[command(disable_help_flag = true)]
#[command(disable_version_flag = true)]
pub struct Cli {
#[command(subcommand)]
pub cmd: Option<Cmd>,
#[command(flatten)]
pub global: Global,
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
pub args: Vec<String>,
}
#[derive(Args, Debug, Clone)]
pub struct Global {
#[arg(long, short = 'a', global = true)]
pub ansi: bool,
#[arg(long, short = 't', global = true, conflicts_with = "ansi")]
pub text: bool,
#[arg(long, short = 'j', global = true, conflicts_with_all = ["ansi", "text"])]
pub json: bool,
#[arg(long = "no-hints", short = 'n', global = true)]
pub no_hints: bool,
}
#[derive(Subcommand, Debug)]
pub enum Cmd {
Run(RunArgs),
Repl,
Serv(ServArgs),
#[command(alias = "tool")]
Tools(ToolsArgs),
Graph(GraphArgs),
Compile(CompileArgs),
Build(CompileArgs),
Check(CheckArgs),
#[command(alias = "help")]
Spec(SpecArgs),
Explain(ExplainArgs),
Skill(SkillArgs),
Version,
}
#[derive(Args, Debug)]
pub struct RunArgs {
pub source: String,
#[arg(skip = Engine::Default)]
pub engine: Engine,
#[arg(long = "run-tree", conflicts_with_all = ["run", "run_vm", "jit", "run_llvm"])]
pub run_tree: bool,
#[arg(long = "run", conflicts_with_all = ["run_tree", "run_vm", "jit", "run_llvm"])]
pub run: bool,
#[arg(long = "run-vm", conflicts_with_all = ["run", "run_tree", "jit", "run_llvm"])]
pub run_vm: bool,
#[arg(long = "jit", conflicts_with_all = ["run", "run_tree", "run_vm", "run_llvm"])]
pub jit: bool,
#[arg(long = "run-llvm", conflicts_with_all = ["run", "run_tree", "run_vm", "jit"])]
pub run_llvm: bool,
#[arg(long)]
pub bench: bool,
#[arg(long)]
pub emit: Option<String>,
#[arg(long = "explain", short = 'x')]
pub explain: bool,
#[arg(long, short = 'd', aliases = ["fmt"])]
pub dense: bool,
#[arg(long, short = 'e', aliases = ["fmt-expanded"])]
pub expanded: bool,
#[arg(long = "ast")]
pub ast: bool,
#[arg(long = "tools")]
pub tools_path: Option<String>,
#[arg(long = "mcp")]
pub mcp_path: Option<String>,
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
pub rest: Vec<String>,
}
#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
pub enum Engine {
Default,
Tree,
Vm,
Cranelift,
Llvm,
}
impl RunArgs {
pub fn effective_engine(&self) -> Engine {
if self.run || self.run_tree {
Engine::Tree
} else if self.run_vm {
Engine::Vm
} else if self.jit {
Engine::Cranelift
} else if self.run_llvm {
Engine::Llvm
} else {
self.engine
}
}
}
#[derive(Args, Debug)]
pub struct ServArgs {
#[arg(long = "mcp", short = 'm')]
pub mcp_path: Option<String>,
#[arg(long = "tools")]
pub tools_path: Option<String>,
}
#[derive(Args, Debug)]
pub struct ToolsArgs {
#[arg(long = "mcp", short = 'm')]
pub mcp_path: Option<String>,
#[arg(long = "tools")]
pub tools_path: Option<String>,
#[arg(long, value_enum)]
pub format: Option<ToolsFormat>,
#[arg(long)]
pub human: bool,
#[arg(long)]
pub ilo: bool,
#[arg(long)]
pub json: bool,
#[arg(long, short = 'f')]
pub full: bool,
#[arg(long, short = 'g')]
pub graph: bool,
}
#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
pub enum ToolsFormat {
Human,
Ilo,
Json,
}
#[derive(Args, Debug)]
pub struct GraphArgs {
pub file: String,
#[arg(long = "fn")]
pub fn_name: Option<String>,
#[arg(long)]
pub reverse: bool,
#[arg(long)]
pub subgraph: bool,
#[arg(long)]
pub budget: Option<usize>,
#[arg(long)]
pub dot: bool,
}
#[derive(Args, Debug)]
pub struct CompileArgs {
pub source: String,
#[arg(short = 'o')]
pub output: Option<String>,
pub func: Option<String>,
#[arg(long)]
pub bench: bool,
}
#[derive(Args, Debug)]
pub struct CheckArgs {
pub source: String,
}
#[derive(Args, Debug)]
pub struct SpecArgs {
pub topic: Option<String>,
}
#[derive(Args, Debug)]
pub struct ExplainArgs {
pub code: String,
}
#[derive(Args, Debug)]
pub struct SkillArgs {
#[command(subcommand)]
pub cmd: SkillCmd,
}
#[derive(Subcommand, Debug)]
pub enum SkillCmd {
List,
Get { name: String },
Path { name: String },
Show { name: String },
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum OutputMode {
Ansi,
Text,
Json,
}
impl Global {
pub fn output_mode(&self) -> OutputMode {
if self.ansi {
return OutputMode::Ansi;
}
if self.text {
return OutputMode::Text;
}
if self.json {
return OutputMode::Json;
}
use std::io::IsTerminal;
let is_tty = std::io::stderr().is_terminal();
let no_color = std::env::var("NO_COLOR").is_ok();
if is_tty && !no_color {
OutputMode::Ansi
} else if is_tty {
OutputMode::Text
} else {
OutputMode::Json
}
}
pub fn explicit_json(&self) -> bool {
self.json
}
}
pub fn reject_unknown_flags(args: &[String]) -> Result<(), String> {
reject_unknown_flags_with_allowlist(args, &[])
}
pub fn reject_unknown_flags_with_allowlist(
args: &[String],
allowlist: &[&str],
) -> Result<(), String> {
for a in args {
if a == "--" {
return Ok(());
}
let head = a.split_once('=').map(|(h, _)| h).unwrap_or(a.as_str());
if looks_like_clean_long_flag(head)
&& !allowlist.contains(&head)
&& !allowlist.contains(&a.as_str())
{
return Err(format!(
"error: unrecognised flag '{a}'. Use 'ilo --help' for valid flags. To pass it as a literal arg, separate with '--' first."
));
}
}
Ok(())
}
fn looks_like_clean_long_flag(s: &str) -> bool {
let Some(rest) = s.strip_prefix("--") else {
return false;
};
if rest.is_empty() {
return false; }
let bytes = rest.as_bytes();
if !bytes[0].is_ascii_lowercase() {
return false;
}
let mut prev_dash = false;
for (i, &b) in bytes.iter().enumerate() {
if b == b'-' {
if prev_dash || i + 1 == bytes.len() {
return false;
}
prev_dash = true;
} else if b.is_ascii_lowercase() || b.is_ascii_digit() {
prev_dash = false;
} else {
return false;
}
}
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_run_subcommand() {
let cli = Cli::try_parse_from(["ilo", "run", "file.ilo", "func", "42"]).unwrap();
match cli.cmd {
Some(Cmd::Run(r)) => {
assert_eq!(r.source, "file.ilo");
assert_eq!(r.rest, vec!["func", "42"]);
}
other => panic!("expected Run, got {other:?}"),
}
}
#[test]
fn parse_repl_subcommand() {
let cli = Cli::try_parse_from(["ilo", "repl"]).unwrap();
assert!(matches!(cli.cmd, Some(Cmd::Repl)));
}
#[test]
fn parse_serv_with_mcp() {
let cli = Cli::try_parse_from(["ilo", "serv", "--mcp", "cfg.json"]).unwrap();
match cli.cmd {
Some(Cmd::Serv(s)) => assert_eq!(s.mcp_path.as_deref(), Some("cfg.json")),
other => panic!("expected Serv, got {other:?}"),
}
}
#[test]
fn parse_tools_with_flags() {
let cli =
Cli::try_parse_from(["ilo", "tools", "--mcp", "p.json", "--full", "--graph"]).unwrap();
match cli.cmd {
Some(Cmd::Tools(t)) => {
assert_eq!(t.mcp_path.as_deref(), Some("p.json"));
assert!(t.full);
assert!(t.graph);
}
other => panic!("expected Tools, got {other:?}"),
}
}
#[test]
fn parse_graph_subcommand() {
let cli =
Cli::try_parse_from(["ilo", "graph", "file.ilo", "--fn", "main", "--dot"]).unwrap();
match cli.cmd {
Some(Cmd::Graph(g)) => {
assert_eq!(g.file, "file.ilo");
assert_eq!(g.fn_name.as_deref(), Some("main"));
assert!(g.dot);
}
other => panic!("expected Graph, got {other:?}"),
}
}
#[test]
fn parse_compile_subcommand() {
let cli =
Cli::try_parse_from(["ilo", "compile", "prog.ilo", "-o", "out", "--bench"]).unwrap();
match cli.cmd {
Some(Cmd::Compile(c)) => {
assert_eq!(c.source, "prog.ilo");
assert_eq!(c.output.as_deref(), Some("out"));
assert!(c.bench);
}
other => panic!("expected Compile, got {other:?}"),
}
}
#[test]
fn parse_global_json_flag() {
let cli = Cli::try_parse_from(["ilo", "--json", "repl"]).unwrap();
assert!(cli.global.json);
assert_eq!(cli.global.output_mode(), OutputMode::Json);
}
#[test]
fn parse_global_ansi_flag() {
let cli = Cli::try_parse_from(["ilo", "-a", "repl"]).unwrap();
assert!(cli.global.ansi);
assert_eq!(cli.global.output_mode(), OutputMode::Ansi);
}
#[test]
fn parse_global_text_flag() {
let cli = Cli::try_parse_from(["ilo", "--text", "repl"]).unwrap();
assert!(cli.global.text);
assert_eq!(cli.global.output_mode(), OutputMode::Text);
}
#[test]
fn parse_global_no_hints() {
let cli = Cli::try_parse_from(["ilo", "-n", "repl"]).unwrap();
assert!(cli.global.no_hints);
}
#[test]
fn parse_explain_subcommand() {
let cli = Cli::try_parse_from(["ilo", "explain", "ILO-T005"]).unwrap();
match cli.cmd {
Some(Cmd::Explain(e)) => assert_eq!(e.code, "ILO-T005"),
other => panic!("expected Explain, got {other:?}"),
}
}
#[test]
fn parse_version_subcommand() {
let cli = Cli::try_parse_from(["ilo", "version"]).unwrap();
assert!(matches!(cli.cmd, Some(Cmd::Version)));
}
#[test]
fn parse_tool_alias() {
let cli = Cli::try_parse_from(["ilo", "tool", "--mcp", "p.json"]).unwrap();
assert!(matches!(cli.cmd, Some(Cmd::Tools(_))));
}
#[test]
fn parse_spec_subcommand_lang() {
let cli = Cli::try_parse_from(["ilo", "spec", "lang"]).unwrap();
match cli.cmd {
Some(Cmd::Spec(s)) => assert_eq!(s.topic.as_deref(), Some("lang")),
other => panic!("expected Spec, got {other:?}"),
}
}
#[test]
fn parse_spec_subcommand_ai() {
let cli = Cli::try_parse_from(["ilo", "spec", "ai"]).unwrap();
match cli.cmd {
Some(Cmd::Spec(s)) => assert_eq!(s.topic.as_deref(), Some("ai")),
other => panic!("expected Spec, got {other:?}"),
}
}
#[test]
fn engine_flag_run_tree() {
let cli = Cli::try_parse_from(["ilo", "run", "--run-tree", "code"]).unwrap();
if let Some(Cmd::Run(r)) = cli.cmd {
assert_eq!(r.effective_engine(), Engine::Tree);
}
}
#[test]
fn engine_flag_run_vm() {
let cli = Cli::try_parse_from(["ilo", "run", "--run-vm", "code"]).unwrap();
if let Some(Cmd::Run(r)) = cli.cmd {
assert_eq!(r.effective_engine(), Engine::Vm);
}
}
#[test]
fn default_positional_args_fallback() {
let cli = Cli::try_parse_from(["ilo", "f>n;42", "5"]).unwrap();
assert!(cli.cmd.is_none());
assert_eq!(cli.args, vec!["f>n;42", "5"]);
}
#[test]
fn tools_json_shorthand() {
let cli = Cli::try_parse_from(["ilo", "tools", "--mcp", "p.json", "--json"]).unwrap();
if let Some(Cmd::Tools(t)) = cli.cmd {
assert!(t.json);
}
}
#[test]
fn tools_ilo_shorthand() {
let cli = Cli::try_parse_from(["ilo", "tools", "--mcp", "p.json", "--ilo"]).unwrap();
if let Some(Cmd::Tools(t)) = cli.cmd {
assert!(t.ilo);
}
}
#[test]
fn tools_human_shorthand() {
let cli = Cli::try_parse_from(["ilo", "tools", "--mcp", "p.json", "--human"]).unwrap();
if let Some(Cmd::Tools(t)) = cli.cmd {
assert!(t.human);
}
}
#[test]
fn compile_with_func() {
let cli = Cli::try_parse_from(["ilo", "compile", "prog.ilo", "entry"]).unwrap();
if let Some(Cmd::Compile(c)) = cli.cmd {
assert_eq!(c.func.as_deref(), Some("entry"));
}
}
#[test]
fn graph_with_budget() {
let cli = Cli::try_parse_from(["ilo", "graph", "f.ilo", "--budget", "100"]).unwrap();
if let Some(Cmd::Graph(g)) = cli.cmd {
assert_eq!(g.budget, Some(100));
}
}
#[test]
fn graph_with_reverse() {
let cli = Cli::try_parse_from(["ilo", "graph", "f.ilo", "--reverse"]).unwrap();
if let Some(Cmd::Graph(g)) = cli.cmd {
assert!(g.reverse);
}
}
#[test]
fn graph_with_subgraph() {
let cli = Cli::try_parse_from(["ilo", "graph", "f.ilo", "--subgraph"]).unwrap();
if let Some(Cmd::Graph(g)) = cli.cmd {
assert!(g.subgraph);
}
}
#[test]
fn run_with_bench() {
let cli = Cli::try_parse_from(["ilo", "run", "--bench", "code", "func", "42"]).unwrap();
if let Some(Cmd::Run(r)) = cli.cmd {
assert!(r.bench);
assert_eq!(r.source, "code");
}
}
#[test]
fn run_with_emit_python() {
let cli = Cli::try_parse_from(["ilo", "run", "--emit", "python", "code"]).unwrap();
if let Some(Cmd::Run(r)) = cli.cmd {
assert_eq!(r.emit.as_deref(), Some("python"));
}
}
#[test]
fn run_with_explain() {
let cli = Cli::try_parse_from(["ilo", "run", "--explain", "code"]).unwrap();
if let Some(Cmd::Run(r)) = cli.cmd {
assert!(r.explain);
}
}
#[test]
fn run_with_dense() {
let cli = Cli::try_parse_from(["ilo", "run", "--dense", "code"]).unwrap();
if let Some(Cmd::Run(r)) = cli.cmd {
assert!(r.dense);
}
}
#[test]
fn run_with_expanded() {
let cli = Cli::try_parse_from(["ilo", "run", "--expanded", "code"]).unwrap();
if let Some(Cmd::Run(r)) = cli.cmd {
assert!(r.expanded);
}
}
#[test]
fn serv_with_tools() {
let cli = Cli::try_parse_from(["ilo", "serv", "--tools", "http.json"]).unwrap();
if let Some(Cmd::Serv(s)) = cli.cmd {
assert_eq!(s.tools_path.as_deref(), Some("http.json"));
}
}
#[test]
fn run_with_tools_and_mcp() {
let cli = Cli::try_parse_from(["ilo", "run", "--tools", "http.json", "code"]).unwrap();
if let Some(Cmd::Run(r)) = cli.cmd {
assert_eq!(r.tools_path.as_deref(), Some("http.json"));
}
}
#[test]
fn help_alias_for_spec() {
let cli = Cli::try_parse_from(["ilo", "help", "ai"]).unwrap();
assert!(matches!(cli.cmd, Some(Cmd::Spec(_))));
}
#[test]
fn engine_flag_jit() {
let cli = Cli::try_parse_from(["ilo", "run", "--jit", "code"]).unwrap();
if let Some(Cmd::Run(r)) = cli.cmd {
assert_eq!(r.effective_engine(), Engine::Cranelift);
} else {
panic!("expected Run subcommand");
}
}
#[test]
fn engine_flag_run_llvm() {
let cli = Cli::try_parse_from(["ilo", "run", "--run-llvm", "code"]).unwrap();
if let Some(Cmd::Run(r)) = cli.cmd {
assert_eq!(r.effective_engine(), Engine::Llvm);
} else {
panic!("expected Run subcommand");
}
}
#[test]
fn engine_flag_run_alias() {
let cli = Cli::try_parse_from(["ilo", "run", "--run", "code"]).unwrap();
if let Some(Cmd::Run(r)) = cli.cmd {
assert_eq!(r.effective_engine(), Engine::Tree);
} else {
panic!("expected Run subcommand");
}
}
#[test]
fn engine_default_when_no_flags() {
let r = RunArgs {
source: "code".to_string(),
engine: Engine::Default,
run_tree: false,
run: false,
run_vm: false,
jit: false,
run_llvm: false,
bench: false,
emit: None,
explain: false,
dense: false,
expanded: false,
ast: false,
tools_path: None,
mcp_path: None,
rest: vec![],
};
assert_eq!(r.effective_engine(), Engine::Default);
}
#[test]
fn output_mode_no_color_env_returns_text_when_tty_unavailable() {
let g = Global {
ansi: false,
text: false,
json: false,
no_hints: false,
};
assert!(!g.explicit_json());
let mode = g.output_mode();
assert!(
matches!(mode, OutputMode::Ansi | OutputMode::Text | OutputMode::Json),
"output_mode should return a valid mode"
);
}
#[test]
fn global_explicit_json_true_when_json_flag_set() {
let g = Global {
ansi: false,
text: false,
json: true,
no_hints: false,
};
assert!(g.explicit_json());
assert_eq!(g.output_mode(), OutputMode::Json);
}
#[test]
fn global_explicit_json_false_when_text_set() {
let g = Global {
ansi: false,
text: true,
json: false,
no_hints: false,
};
assert!(!g.explicit_json());
assert_eq!(g.output_mode(), OutputMode::Text);
}
#[test]
fn global_explicit_json_false_when_ansi_set() {
let g = Global {
ansi: true,
text: false,
json: false,
no_hints: false,
};
assert!(!g.explicit_json());
assert_eq!(g.output_mode(), OutputMode::Ansi);
}
#[test]
fn tools_format_human_parse() {
let cli =
Cli::try_parse_from(["ilo", "tools", "--mcp", "p.json", "--format", "human"]).unwrap();
if let Some(Cmd::Tools(t)) = cli.cmd {
assert_eq!(t.format, Some(ToolsFormat::Human));
}
}
#[test]
fn tools_format_ilo_parse() {
let cli =
Cli::try_parse_from(["ilo", "tools", "--mcp", "p.json", "--format", "ilo"]).unwrap();
if let Some(Cmd::Tools(t)) = cli.cmd {
assert_eq!(t.format, Some(ToolsFormat::Ilo));
}
}
#[test]
fn tools_format_json_parse() {
let cli =
Cli::try_parse_from(["ilo", "tools", "--mcp", "p.json", "--format", "json"]).unwrap();
if let Some(Cmd::Tools(t)) = cli.cmd {
assert_eq!(t.format, Some(ToolsFormat::Json));
}
}
#[test]
fn graph_with_fn_name() {
let cli = Cli::try_parse_from(["ilo", "graph", "f.ilo", "--fn", "main"]).unwrap();
if let Some(Cmd::Graph(g)) = cli.cmd {
assert_eq!(g.fn_name.as_deref(), Some("main"));
}
}
#[test]
fn unknown_long_flag_rejected() {
let args = vec![
"main.ilo".to_string(),
"--engine".to_string(),
"tree".to_string(),
];
let err = reject_unknown_flags(&args).unwrap_err();
assert!(err.contains("--engine"), "msg={err}");
assert!(err.contains("unrecognised flag"));
assert!(err.contains("'--' first"));
}
#[test]
fn unknown_long_flag_no_value_rejected() {
let args = vec!["main.ilo".to_string(), "--foo".to_string()];
assert!(reject_unknown_flags(&args).is_err());
}
#[test]
fn unknown_hyphenated_flag_rejected() {
let args = vec!["main.ilo".to_string(), "--some-long-flag".to_string()];
assert!(reject_unknown_flags(&args).is_err());
}
#[test]
fn dash_dash_separator_escapes_subsequent_flags() {
let args = vec![
"main.ilo".to_string(),
"--".to_string(),
"--foo".to_string(),
"--engine".to_string(),
];
assert!(reject_unknown_flags(&args).is_ok());
}
#[test]
fn plain_positional_args_accepted() {
let args = vec!["main.ilo".to_string(), "func".to_string(), "42".to_string()];
assert!(reject_unknown_flags(&args).is_ok());
}
#[test]
fn negative_number_not_treated_as_flag() {
let args = vec![
"main.ilo".to_string(),
"-1".to_string(),
"-3.14".to_string(),
];
assert!(reject_unknown_flags(&args).is_ok());
}
#[test]
fn equals_form_unknown_flag_rejected() {
let args = vec!["main.ilo".to_string(), "--foo=bar".to_string()];
let err = reject_unknown_flags(&args).unwrap_err();
assert!(err.contains("--foo=bar"), "msg={err}");
assert!(err.contains("unrecognised flag"));
}
#[test]
fn equals_form_engine_rejected() {
let args = vec!["main.ilo".to_string(), "--engine=tree".to_string()];
let err = reject_unknown_flags(&args).unwrap_err();
assert!(err.contains("--engine=tree"), "msg={err}");
}
#[test]
fn equals_form_allowlisted_head_accepted() {
let args = vec!["main.ilo".to_string(), "--bench=on".to_string()];
assert!(reject_unknown_flags_with_allowlist(&args, &["--bench"]).is_ok());
}
#[test]
fn equals_form_after_dash_dash_accepted() {
let args = vec![
"main.ilo".to_string(),
"--".to_string(),
"--foo=bar".to_string(),
];
assert!(reject_unknown_flags(&args).is_ok());
}
#[test]
fn equals_form_with_empty_value_rejected() {
let args = vec!["main.ilo".to_string(), "--foo=".to_string()];
assert!(reject_unknown_flags(&args).is_err());
}
#[test]
fn equals_form_with_non_flag_head_accepted() {
let args = vec!["main.ilo".to_string(), "key=value".to_string()];
assert!(reject_unknown_flags(&args).is_ok());
}
#[test]
fn trailing_dash_not_treated_as_flag() {
let args = vec!["main.ilo".to_string(), "--foo-".to_string()];
assert!(reject_unknown_flags(&args).is_ok());
}
#[test]
fn doubled_dash_inside_not_treated_as_flag() {
let args = vec!["main.ilo".to_string(), "--foo--bar".to_string()];
assert!(reject_unknown_flags(&args).is_ok());
}
#[test]
fn empty_args_ok() {
let args: Vec<String> = vec![];
assert!(reject_unknown_flags(&args).is_ok());
}
#[test]
fn looks_like_clean_long_flag_shapes() {
assert!(looks_like_clean_long_flag("--foo"));
assert!(looks_like_clean_long_flag("--engine"));
assert!(looks_like_clean_long_flag("--some-long-flag"));
assert!(looks_like_clean_long_flag("--a1"));
assert!(!looks_like_clean_long_flag("--"));
assert!(!looks_like_clean_long_flag("-x"));
assert!(!looks_like_clean_long_flag("--Foo"));
assert!(!looks_like_clean_long_flag("--foo=bar"));
assert!(!looks_like_clean_long_flag("--foo-"));
assert!(!looks_like_clean_long_flag("--foo--bar"));
assert!(!looks_like_clean_long_flag("--1foo"));
assert!(!looks_like_clean_long_flag("foo"));
assert!(!looks_like_clean_long_flag("-1"));
}
#[test]
fn run_with_mcp_path() {
let cli = Cli::try_parse_from(["ilo", "run", "--mcp", "cfg.json", "code"]).unwrap();
if let Some(Cmd::Run(r)) = cli.cmd {
assert_eq!(r.mcp_path.as_deref(), Some("cfg.json"));
}
}
}