cruise 0.1.45

YAML-driven coding agent workflow orchestrator
Documentation
use std::io::{IsTerminal, Read};

use console::style;

use crate::cli::DraftArgs;
use crate::config::{WorkflowConfig, validate_config};
use crate::error::{CruiseError, Result};
use crate::multiline_input::{InputResult, prompt_multiline};
use crate::session::{SessionManager, SessionPhase, SessionState};

pub fn run(args: DraftArgs) -> Result<()> {
    let (yaml, source) = crate::resolver::resolve_config(args.config.as_deref())?;
    eprintln!("{}", style(source.display_string()).dim());

    let noninteractive = !std::io::stdin().is_terminal();
    let input = read_draft_input(args.input, noninteractive)?;

    let config = WorkflowConfig::from_yaml(&yaml)
        .map_err(|e| CruiseError::ConfigParseError(e.to_string()))?;
    validate_config(&config)?;

    let manager = SessionManager::new(crate::paths::data_dir()?);
    let session_id = SessionManager::new_session_id();
    let base_dir = std::env::current_dir()?;
    let mut session = SessionState::new(
        session_id.clone(),
        base_dir,
        source.display_string(),
        input.trim().to_string(),
    );
    session.config_path = source.path().cloned();
    session.phase = SessionPhase::Draft;
    manager.create(&session)?;

    if session.config_path.is_none() {
        let session_dir = manager.sessions_dir().join(&session_id);
        if let Err(e) = std::fs::write(session_dir.join("config.yaml"), &yaml) {
            let _ = manager.delete(&session_id);
            return Err(CruiseError::IoError(e));
        }
    }

    eprintln!(
        "\n{} Session {} saved as draft.",
        style("").green().bold(),
        session.id
    );
    eprintln!(
        "  Generate plan later with: {}",
        style("cruise list").cyan()
    );
    Ok(())
}

fn read_draft_input(input: Option<String>, noninteractive: bool) -> Result<String> {
    if let Some(text) = input {
        let trimmed = text.trim().to_string();
        if trimmed.is_empty() {
            return Err(CruiseError::Other("input cannot be empty".to_string()));
        }
        return Ok(trimmed);
    }

    if noninteractive {
        let mut s = String::new();
        std::io::stdin()
            .read_to_string(&mut s)
            .map_err(CruiseError::IoError)?;
        let trimmed = s.trim().to_string();
        if !trimmed.is_empty() {
            return Ok(trimmed);
        }
        return Err(CruiseError::Other(
            "no input provided: stdin is not a terminal and no input argument was given"
                .to_string(),
        ));
    }

    match prompt_multiline("What would you like to implement?")? {
        InputResult::Submitted(text) => {
            if text.trim().is_empty() {
                return Err(CruiseError::Other("input cannot be empty".to_string()));
            }
            Ok(text)
        }
        InputResult::Cancelled => Err(CruiseError::StepPaused),
    }
}