nils-codex-cli 0.7.3

CLI crate for nils-codex-cli in the nils-cli workspace.
Documentation
use nils_test_support::bin;
use nils_test_support::cmd::{self, CmdOptions, CmdOutput};
use pretty_assertions::assert_eq;
use std::path::PathBuf;

fn codex_cli_bin() -> PathBuf {
    bin::resolve("codex-cli")
}

fn run(args: &[&str]) -> CmdOutput {
    let bin = codex_cli_bin();
    cmd::run(&bin, args, &[], None)
}

fn stdout(output: &CmdOutput) -> String {
    output.stdout_text()
}

fn stderr(output: &CmdOutput) -> String {
    output.stderr_text()
}

fn assert_exit(output: &CmdOutput, code: i32) {
    assert_eq!(output.code, code);
}

#[test]
fn main_no_args_prints_help_and_exits_zero() {
    let output = run(&[]);
    assert_exit(&output, 0);
    assert!(stdout(&output).contains("codex-cli"));
}

#[test]
fn main_help_subcommand_exits_zero() {
    let output = run(&["help"]);
    assert_exit(&output, 0);
    assert!(stdout(&output).contains("codex-cli"));
}

#[test]
fn main_agent_and_config_without_subcommand_print_help() {
    let output = run(&["agent"]);
    assert_exit(&output, 0);
    assert!(stdout(&output).contains("Agent command group"));

    let output = run(&["config"]);
    assert_exit(&output, 0);
    assert!(stdout(&output).contains("Configuration command group"));
}

#[test]
fn main_agent_prompt_is_gated_and_config_show_exits_zero() {
    let options = CmdOptions::default().with_env("CODEX_ALLOW_DANGEROUS_ENABLED", "false");
    let bin = codex_cli_bin();
    let output = cmd::run_with(&bin, &["agent", "prompt", "hello"], &options);
    assert_exit(&output, 1);
    assert!(stderr(&output).contains("disabled (set CODEX_ALLOW_DANGEROUS_ENABLED=true)"));

    let output = run(&["config", "show"]);
    assert_exit(&output, 0);
}

#[test]
fn main_unknown_command_exits_64() {
    let output = run(&["not-a-real-command"]);
    assert_exit(&output, 64);
    assert!(!stderr(&output).trim().is_empty());
}

#[test]
fn main_removed_provider_neutral_groups_use_clap_parse_errors() {
    for group in ["provider", "debug", "workflow", "automation"] {
        let output = run(&[group]);
        assert_exit(&output, 64);
        let err = stderr(&output);
        assert!(
            err.contains("unrecognized subcommand"),
            "stderr should include clap parse error: {err}"
        );
        assert!(
            err.contains(group),
            "stderr should include rejected command token: {err}"
        );
    }
}

#[test]
fn main_help_excludes_provider_neutral_groups() {
    let output = run(&["--help"]);
    assert_exit(&output, 0);
    let help = stdout(&output);
    for group in ["provider", "debug", "workflow", "automation"] {
        assert!(
            !help.contains(group),
            "unexpected provider-neutral group in help: {group}\n{help}"
        );
    }
}

#[test]
fn main_help_includes_json_output_modes_for_diag_and_auth() {
    let diag_help = run(&["diag", "rate-limits", "--help"]);
    assert_exit(&diag_help, 0);
    let diag_text = stdout(&diag_help);
    assert!(diag_text.contains("--json"));
    assert!(diag_text.contains("--format"));
    assert!(diag_text.contains("--clear-cache"));

    let auth_help = run(&["auth", "current", "--help"]);
    assert_exit(&auth_help, 0);
    let auth_text = stdout(&auth_help);
    assert!(auth_text.contains("--json"));
    assert!(auth_text.contains("--format"));
}

#[test]
fn main_help_includes_ephemeral_flag_for_agent_commands() {
    for path in [
        ["agent", "prompt", "--help"],
        ["agent", "advice", "--help"],
        ["agent", "knowledge", "--help"],
        ["agent", "commit", "--help"],
    ] {
        let output = run(&path);
        assert_exit(&output, 0);
        let help = stdout(&output);
        assert!(
            help.contains("--ephemeral"),
            "missing --ephemeral in help:\n{help}"
        );
    }
}

#[test]
fn main_help_includes_completion_export_entrypoint() {
    let output = run(&["--help"]);
    assert_exit(&output, 0);
    let help = stdout(&output);
    assert!(help.contains("completion"));
    assert!(help.contains("Export shell completion script"));
}

#[test]
fn main_completion_exports_bash_and_zsh_scripts() {
    let zsh = run(&["completion", "zsh"]);
    assert_exit(&zsh, 0);
    let zsh_text = stdout(&zsh);
    assert!(zsh_text.contains("#compdef codex-cli"));
    assert!(zsh_text.contains("completion:Export shell completion script"));
    assert!(zsh_text.contains(":shell -- Shell to generate completion script for:(bash zsh)"));

    let bash = run(&["completion", "bash"]);
    assert_exit(&bash, 0);
    let bash_text = stdout(&bash);
    assert!(bash_text.contains("_codex-cli()"));
    assert!(bash_text.contains("complete -F _codex-cli"));
    assert!(bash_text.contains("opts=\"-h --help bash zsh\""));
}

#[test]
fn main_completion_rejects_unknown_shell_with_usage_error() {
    let output = run(&["completion", "fish"]);
    assert_exit(&output, 64);
    let err = stderr(&output);
    assert!(err.contains("invalid value"));
    assert!(err.contains("bash"));
    assert!(err.contains("zsh"));
}