murliv1.0.2
{ } github
§ Language Reference

Rust ● stable

Adapters for clap (derive and builder APIs) and argh. The Rust implementation shares the same wire format and exit code taxonomy as the Go reference.

Installation

tomlCargo.toml
[dependencies]
murli = { version = "0.1", features = ["clap"] }         # clap adapter
murli = { version = "0.1", features = ["argh"] }         # argh adapter
murli = { version = "0.1", features = ["clap", "argh"] } # both

clap — derive API

Flatten GlobalArgs into your top-level struct, then call handle_builtins before dispatching commands.

rustmain.rs
use clap::{CommandFactory, Parser};
use murli::clap::GlobalArgs;
use murli::Writer;
use serde_json::json;

#[derive(Parser)]
#[command(name = "mytool", about = "My deployment tool")]
struct Cli {
    #[command(flatten)]
    murli: GlobalArgs,    // --agent --schema --force --dry-run --output --profile
    #[command(subcommand)]
    command: Commands,
}

#[derive(clap::Subcommand)]
enum Commands {
    Deploy { env: String },
}

fn main() {
    let args = Cli::parse();
    // handles --schema and mutation guard; exits if consumed
    murli::clap::handle_builtins(&args.murli, &Cli::command(), None);
    let mut writer = Writer::from_args(&args.murli);

    match args.command {
        Commands::Deploy { env } => {
            if writer.is_dry_run() {
                writer.write_plan(&format!("Would deploy to {env}"), &json!({"env": env}));
            } else {
                writer.write_success(&format!("Deployed to {env}"), &json!({"env": env}));
            }
        }
    }
}

clap — builder API

murli::clap::enable adds all murli flags to an existing Command and mounts describe, doctor, and profile as real subcommands.

rustmain.rs
fn main() {
    let mut cmd = build_my_command();
    murli::clap::enable(&mut cmd);
    let matches = cmd.get_matches();
    // exits if describe/doctor/profile/--schema was invoked
    murli::clap::handle_matches(&matches, &build_my_command());
    let writer = murli::Writer::new(
        matches.get_flag("agent"),
        None, false, false, env!("CARGO_PKG_VERSION"),
    );
    // dispatch using matches
}

argh

argh 0.1.x limitation argh 0.1.x does not support #[argh(flatten)]. Parse GlobalArgs separately alongside your own args struct, or wait for flatten support in a future argh release.
rustmain.rs
use argh::FromArgs;
use murli::argh::GlobalArgs;
use serde_json::json;

#[derive(FromArgs, Debug)]
#[argh(description = "My deployment tool")]
struct Cli {
    /// Target environment
    #[argh(option)]
    env: String,
}

fn main() {
    let raw: Vec<String> = std::env::args().skip(1).collect();
    let strs: Vec<&str> = raw.iter().map(|s| s.as_str()).collect();
    let murli_args = GlobalArgs::from_args(&["mytool"], &strs).unwrap_or_default();
    murli::argh::handle_builtins(&murli_args, None, "mytool", "My deployment tool");
    let mut writer = murli::argh::writer_from_args(&murli_args);

    let args: Cli = argh::from_env();
    writer.write_success(&format!("Deployed to {}", args.env), &json!({"env": args.env}));
}

Writer

Obtain via Writer::from_args(&global_args) (derive) or Writer::new(...) (builder/custom).

Output methods

MethodWrites toWhen
write_success(&str, &Value)stdoutCommand completed successfully
write_plan(&str, &Value)stdoutDry-run: show what would happen
write_error(AgentError)stderr + exitCommand failed; process exits with error code
write_event(&Value)stdoutOne NDJSON line per call for streaming output
write_progress(ProgressEvent)stdout (agent) / stderr (TTY)Typed progress update
log(&str)stderrDiagnostic line with deduplication

State methods

MethodReturns
is_tty() → boolTrue if stdout is a terminal
is_forced() → boolTrue if --force or --yes was passed
is_dry_run() → boolTrue if --dry-run was passed
Testing Use write_error_to_streams instead of write_error in tests — it writes the error envelope without calling process::exit, so the test process continues.

AgentError

Convenience constructors

rust
writer.write_error(AgentError::user_error("flag --env is required", "pass --env prod"));
writer.write_error(AgentError::tool_error("database connection failed"));
writer.write_error(AgentError::not_found("index not found", "run `mytool index build`"));
writer.write_error(AgentError::rate_limited("API limit hit", 5000));

Full struct

rust
writer.write_error(AgentError {
    code:        EXIT_NOT_FOUND,
    error_type:  "index_missing".into(),
    message:     "Semantic index not found".into(),
    suggestion:  "Run `mytool index build`".into(),
    recoverable: false,
    doc_url:     "https://example.com/docs".into(),
    ..Default::default()
});

Differences from the Go implementation

Featuremurli-gomurli-rs
Flag injectionDynamic at runtimeclap: #[command(flatten)]; argh: separate parse
argh flattenN/ANot available in argh 0.1.x
--yes aliasSibling flagvisible_alias = "yes" on --force
enum field nameEnum []stringenum_values: Vec<String> (reserved word)
argh describeAuto-introspected at runtimeMetadata-driven (argh has no runtime command tree)
Process exit hookmurli.ExitFuncwrite_error_to_streams test helper
Profile path~/./dirs::config_dir()//