eoka-cli 0.3.7

Browser automation CLI for AI agents and pentesting
use crate::protocol::Response;

pub fn print_response(response: &Response, json_mode: bool) {
    if json_mode {
        println!(
            "{}",
            serde_json::to_string_pretty(response).unwrap_or_default()
        );
        return;
    }

    if !response.ok {
        eprint!("{}", format_error(response));
        return;
    }

    if let Some(ref data) = response.data {
        match data {
            serde_json::Value::String(s) => println!("{}", s),
            serde_json::Value::Object(obj) if obj.get("text").is_some() => {
                if let Some(text) = obj.get("text").and_then(|value| value.as_str()) {
                    println!("{}", text);
                }
            }
            other => println!(
                "{}",
                serde_json::to_string_pretty(other).unwrap_or_default()
            ),
        }
    }
}

fn format_error(response: &Response) -> String {
    use std::fmt::Write;
    let detail = response.error_detail.as_ref();
    let context = detail.and_then(|detail| detail.context.as_deref());
    let mut text = format!(
        "Error [{}]: {}\n",
        detail.map_or("eoka_error", |d| &d.code),
        response.error.as_deref().unwrap_or("Command failed")
    );
    if let Some(context) = context {
        if let Some(action) = context.action_status {
            text.push_str(match action {
                eoka_protocol::ActionStatus::NotAttempted => "Action was not attempted.\n",
                eoka_protocol::ActionStatus::MayHaveExecuted => {
                    "Action may have executed; it was not replayed.\n"
                }
            });
        }
        if let Some(page) = &context.page {
            text.push_str(&eoka_server::page_context::format(page));
        }
    }
    let data = response.data.as_ref();
    let id = data
        .and_then(|data| data["id"].as_str())
        .or_else(|| context.and_then(|context| context.download_id.as_deref()));
    let session = response
        .meta
        .as_ref()
        .and_then(|meta| meta.session.as_deref())
        .unwrap_or("default");
    let quote = |value: &str| format!("'{}'", value.replace('\'', "'\"'\"'"));
    if let Some(id) = id {
        let _ = writeln!(text, "Download job: {id}");
        if let Some(data) = data {
            for key in ["state", "output", "published_path", "staging_path"] {
                if let Some(value) = data[key].as_str() {
                    let _ = writeln!(text, "{key}: {value}");
                }
            }
        } else {
            text.push_str("Current job state unavailable.\n");
        }
        let _ = writeln!(
            text,
            "Next: eoka --session {} download status {}",
            quote(session),
            quote(id)
        );
    } else if context.is_some_and(|context| context.page.is_some()) {
        let _ = writeln!(
            text,
            "Next: eoka --session {} --agent observe",
            quote(session)
        );
    }
    if let Some(hint) = detail.and_then(|detail| detail.hint.as_deref()) {
        let _ = writeln!(text, "Hint: {hint}");
    }
    text
}