nils-codex-cli 0.7.3

CLI crate for nils-codex-cli in the nils-cli workspace.
Documentation
use codex_cli::prompts::{self, PromptTemplateError};
use nils_test_support::{EnvGuard, GlobalStateLock};
use pretty_assertions::assert_eq;
use std::fs;
use std::path::Path;

fn set_mode(path: &Path, mode: u32) {
    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        let mut perms = fs::metadata(path).expect("meta").permissions();
        perms.set_mode(mode);
        fs::set_permissions(path, perms).expect("chmod");
    }
}

#[test]
fn prompts_resolve_uses_feature_dir_fallback_when_zdotdir_prompts_missing() {
    let lock = GlobalStateLock::new();
    let dir = tempfile::TempDir::new().expect("tempdir");

    let zdotdir = dir.path().join("zdotdir");
    fs::create_dir_all(&zdotdir).expect("zdotdir");

    let script_dir = dir.path().join("scripts");
    let feature_prompts = script_dir.join("_features").join("codex").join("prompts");
    fs::create_dir_all(&feature_prompts).expect("feature prompts");
    fs::write(
        feature_prompts.join("actionable-advice.md"),
        "Hello $ARGUMENTS\n",
    )
    .expect("write template");

    let zdotdir_str = zdotdir.to_string_lossy().to_string();
    let script_dir_str = script_dir.to_string_lossy().to_string();
    let _zdotdir_env = EnvGuard::set(&lock, "ZDOTDIR", &zdotdir_str);
    let _script_env = EnvGuard::set(&lock, "ZSH_SCRIPT_DIR", &script_dir_str);

    let resolved = prompts::resolve_prompts_dir().expect("resolve prompts dir");
    assert_eq!(resolved, feature_prompts);

    let (path, content) = prompts::read_template("actionable-advice").expect("read template");
    assert_eq!(path, feature_prompts.join("actionable-advice.md"));
    assert!(content.contains("Hello $ARGUMENTS"));
}

#[test]
fn prompts_resolve_returns_none_when_no_prompts_dirs_exist() {
    let lock = GlobalStateLock::new();
    let dir = tempfile::TempDir::new().expect("tempdir");

    let zdotdir = dir.path().join("zdotdir");
    fs::create_dir_all(&zdotdir).expect("zdotdir");

    let script_dir = dir.path().join("scripts");
    let feature_dir = script_dir.join("_features").join("codex");
    fs::create_dir_all(&feature_dir).expect("feature dir");

    let zdotdir_str = zdotdir.to_string_lossy().to_string();
    let script_dir_str = script_dir.to_string_lossy().to_string();
    let _zdotdir_env = EnvGuard::set(&lock, "ZDOTDIR", &zdotdir_str);
    let _script_env = EnvGuard::set(&lock, "ZSH_SCRIPT_DIR", &script_dir_str);

    assert!(prompts::resolve_prompts_dir().is_none());
}

#[test]
fn prompts_read_template_errors_when_prompts_dir_missing() {
    let lock = GlobalStateLock::new();
    let dir = tempfile::TempDir::new().expect("tempdir");

    let zdotdir = dir.path().join("zdotdir");
    fs::create_dir_all(&zdotdir).expect("zdotdir");
    let script_dir = dir.path().join("scripts");
    fs::create_dir_all(&script_dir).expect("scripts dir");

    let zdotdir_str = zdotdir.to_string_lossy().to_string();
    let script_dir_str = script_dir.to_string_lossy().to_string();
    let _zdotdir_env = EnvGuard::set(&lock, "ZDOTDIR", &zdotdir_str);
    let _script_env = EnvGuard::set(&lock, "ZSH_SCRIPT_DIR", &script_dir_str);

    let err = prompts::read_template("anything").unwrap_err();
    assert!(matches!(err, PromptTemplateError::PromptsDirNotFound));
}

#[test]
fn prompts_read_template_errors_when_template_missing() {
    let lock = GlobalStateLock::new();
    let dir = tempfile::TempDir::new().expect("tempdir");

    let zdotdir = dir.path().join("zdotdir");
    let prompts_dir = zdotdir.join("prompts");
    fs::create_dir_all(&prompts_dir).expect("prompts dir");

    let zdotdir_str = zdotdir.to_string_lossy().to_string();
    let _zdotdir_env = EnvGuard::set(&lock, "ZDOTDIR", &zdotdir_str);

    let err = prompts::read_template("missing-template").unwrap_err();
    match err {
        PromptTemplateError::TemplateMissing { path } => {
            assert!(path.ends_with("missing-template.md"));
        }
        other => panic!("unexpected error: {other:?}"),
    }
}

#[test]
fn prompts_read_template_errors_when_template_unreadable() {
    let lock = GlobalStateLock::new();
    let dir = tempfile::TempDir::new().expect("tempdir");

    let zdotdir = dir.path().join("zdotdir");
    let prompts_dir = zdotdir.join("prompts");
    fs::create_dir_all(&prompts_dir).expect("prompts dir");
    let template = prompts_dir.join("unreadable.md");
    fs::write(&template, "hi").expect("write template");
    set_mode(&template, 0o000);

    let zdotdir_str = zdotdir.to_string_lossy().to_string();
    let _zdotdir_env = EnvGuard::set(&lock, "ZDOTDIR", &zdotdir_str);

    let err = prompts::read_template("unreadable").unwrap_err();
    match err {
        PromptTemplateError::ReadFailed { path } => assert_eq!(path, template),
        other => panic!("unexpected error: {other:?}"),
    }
}