pub fn can_prompt_user() -> bool {
prompt_output_target().is_some()
}
#[derive(Clone, Copy)]
enum PromptOutputTarget {
Stdout,
Stderr,
}
fn prompt_output_target() -> Option<PromptOutputTarget> {
if !crate::cli::handlers::boundary::is_terminal() {
return None;
}
if crate::cli::handlers::boundary::stdout_is_terminal() {
return Some(PromptOutputTarget::Stdout);
}
if crate::cli::handlers::boundary::stderr_is_terminal() {
return Some(PromptOutputTarget::Stderr);
}
None
}
fn with_prompt_writer<T>(
target: PromptOutputTarget,
f: impl FnOnce(&mut dyn std::io::Write) -> anyhow::Result<T>,
) -> anyhow::Result<T> {
match target {
PromptOutputTarget::Stdout => f(&mut crate::cli::handlers::boundary::stdout()),
PromptOutputTarget::Stderr => f(&mut crate::cli::handlers::boundary::stderr()),
}
}
pub fn prompt_overwrite_confirmation(prompt_path: &Path, colors: Colors) -> anyhow::Result<bool> {
let Some(target) = prompt_output_target() else {
return Ok(false);
};
with_prompt_writer(target, |w| {
writeln!(
w,
"{}PROMPT.md already exists:{} {}",
colors.yellow(),
colors.reset(),
prompt_path.display()
)?;
write!(w, "Do you want to overwrite it? [y/N]: ")?;
w.flush()?;
Ok(())
})?;
let input = crate::cli::handlers::boundary::read_line();
let response = input.unwrap_or_default().trim().to_lowercase();
Ok(response == "y" || response == "yes")
}
pub fn prompt_for_template(colors: Colors) -> Option<String> {
let target = prompt_output_target()?;
if with_prompt_writer(target, |w| {
let _ = writeln!(
w,
"PROMPT.md contains your task specification for the AI agents."
);
let _ = write!(w, "Would you like to create one from a Work Guide? [Y/n]: ");
w.flush()?;
Ok(())
})
.is_err()
{
return None;
}
let input = crate::cli::handlers::boundary::read_line();
let response = input.unwrap_or_default().trim().to_lowercase();
if response == "n" || response == "no" || response == "skip" {
return None;
}
let templates: Vec<(&str, &str)> = list_templates();
if with_prompt_writer(target, |w| {
let _ = writeln!(w);
let _ = writeln!(w, "Available Work Guides:");
templates
.iter()
.enumerate()
.for_each(|(i, (name, description))| {
let _ = writeln!(
w,
" {}{}{} {}{}{}",
colors.cyan(),
name,
colors.reset(),
colors.dim(),
description,
colors.reset()
);
if (i + 1) % 5 == 0 {
let _ = writeln!(w);
}
});
let _ = writeln!(w);
let _ = writeln!(w, "Common choices:");
let _ = writeln!(
w,
" {}quick{} - Quick/small changes (typos, minor fixes)",
colors.cyan(),
colors.reset()
);
let _ = writeln!(
w,
" {}bug-fix{} - Bug fix with investigation guidance",
colors.cyan(),
colors.reset()
);
let _ = writeln!(
w,
" {}feature-spec{} - Product specification",
colors.cyan(),
colors.reset()
);
let _ = writeln!(w);
let _ = write!(w, "Enter Work Guide name (or press Enter to use 'quick'): ");
w.flush()?;
Ok(())
})
.is_err()
{
return None;
}
let template_input = crate::cli::handlers::boundary::read_line();
let binding = template_input.unwrap_or_default();
let template_name = binding.trim();
if template_name.is_empty() {
return Some("quick".to_string());
}
if get_template(template_name).is_some() {
Some(template_name.to_string())
} else {
let _ = with_prompt_writer(target, |w| {
writeln!(
w,
"{}Unknown Work Guide: '{}'{}",
colors.red(),
template_name,
colors.reset()
)?;
writeln!(
w,
"Run 'ralph --list-work-guides' to see all available Work Guides."
)?;
Ok(())
});
None
}
}