nils-codex-cli 0.7.3

CLI crate for nils-codex-cli in the nils-cli workspace.
Documentation
use std::io::{self, BufRead, Write};

pub mod commit;
pub mod exec;

pub fn prompt(prompt_args: &[String]) -> i32 {
    prompt_with_options(prompt_args, exec::ExecOptions::default())
}

pub fn prompt_with_options(prompt_args: &[String], exec_options: exec::ExecOptions) -> i32 {
    let stdin = io::stdin();
    let mut stdin = stdin.lock();
    let stdout = io::stdout();
    let mut stdout = stdout.lock();
    let stderr = io::stderr();
    let mut stderr = stderr.lock();
    prompt_with_io(
        prompt_args,
        exec_options,
        &mut stdin,
        &mut stdout,
        &mut stderr,
    )
}

pub fn prompt_with_io<R: BufRead, WOut: Write, WErr: Write>(
    prompt_args: &[String],
    exec_options: exec::ExecOptions,
    stdin: &mut R,
    stdout: &mut WOut,
    stderr: &mut WErr,
) -> i32 {
    let mut user_prompt = prompt_args.join(" ");

    if user_prompt.is_empty() {
        if write!(stdout, "Prompt: ").is_err() {
            return 1;
        }
        let _ = stdout.flush();

        user_prompt.clear();
        if stdin
            .read_line(&mut user_prompt)
            .ok()
            .filter(|n| *n > 0)
            .is_none()
        {
            return 1;
        }
        user_prompt = user_prompt.trim_end_matches(&['\n', '\r'][..]).to_string();
    }

    if user_prompt.is_empty() {
        let _ = writeln!(stderr, "codex-tools: missing prompt");
        return 1;
    }

    exec::exec_dangerous_with_options(&user_prompt, "codex-tools:prompt", stderr, exec_options)
}

pub fn advice(question_args: &[String]) -> i32 {
    advice_with_options(question_args, exec::ExecOptions::default())
}

pub fn advice_with_options(question_args: &[String], exec_options: exec::ExecOptions) -> i32 {
    let stdin = io::stdin();
    let mut stdin = stdin.lock();
    let stdout = io::stdout();
    let mut stdout = stdout.lock();
    let stderr = io::stderr();
    let mut stderr = stderr.lock();
    run_template_with_io(
        "actionable-advice",
        question_args,
        exec_options,
        &mut stdin,
        &mut stdout,
        &mut stderr,
    )
}

pub fn knowledge(concept_args: &[String]) -> i32 {
    knowledge_with_options(concept_args, exec::ExecOptions::default())
}

pub fn knowledge_with_options(concept_args: &[String], exec_options: exec::ExecOptions) -> i32 {
    let stdin = io::stdin();
    let mut stdin = stdin.lock();
    let stdout = io::stdout();
    let mut stdout = stdout.lock();
    let stderr = io::stderr();
    let mut stderr = stderr.lock();
    run_template_with_io(
        "actionable-knowledge",
        concept_args,
        exec_options,
        &mut stdin,
        &mut stdout,
        &mut stderr,
    )
}

fn run_template_with_io<R: BufRead, WOut: Write, WErr: Write>(
    template_name: &str,
    args: &[String],
    exec_options: exec::ExecOptions,
    stdin: &mut R,
    stdout: &mut WOut,
    stderr: &mut WErr,
) -> i32 {
    let mut user_query = args.join(" ");
    if user_query.trim().is_empty() {
        if write!(stdout, "Question: ").is_err() {
            return 1;
        }
        let _ = stdout.flush();

        user_query.clear();
        if stdin
            .read_line(&mut user_query)
            .ok()
            .filter(|n| *n > 0)
            .is_none()
        {
            return 1;
        }
        user_query = user_query.trim_end_matches(&['\n', '\r'][..]).to_string();
    }

    if user_query.trim().is_empty() {
        let _ = writeln!(stderr, "codex-tools: missing question");
        return 1;
    }

    let template_content = match crate::prompts::read_template(template_name) {
        Ok((_path, content)) => content,
        Err(crate::prompts::PromptTemplateError::TemplateMissing { path }) => {
            let _ = writeln!(
                stderr,
                "codex-tools: prompt template not found: {}",
                path.to_string_lossy()
            );
            return 1;
        }
        Err(crate::prompts::PromptTemplateError::ReadFailed { path }) => {
            let _ = writeln!(
                stderr,
                "codex-tools: failed to read prompt template: {}",
                path.to_string_lossy()
            );
            return 1;
        }
        Err(crate::prompts::PromptTemplateError::PromptsDirNotFound) => return 1,
    };

    let final_prompt = template_content.replace("$ARGUMENTS", &user_query);
    exec::exec_dangerous_with_options(
        &final_prompt,
        &format!("codex-tools:{template_name}"),
        stderr,
        exec_options,
    )
}