greentic-flow-builder 0.1.0

AI-powered Adaptive Card flow builder with visual graph editor and demo runner
Documentation
pub mod cli;
pub mod discovery;
pub mod registry;
pub mod render;
pub mod template;
pub mod theme;
pub mod ui;
pub mod validate;

use std::fs;
use std::io::{self, Read};
use std::path::Path;

use anyhow::{Context, bail};
use cli::{Cli, Commands};

pub async fn run(cli: Cli) -> anyhow::Result<()> {
    match cli.command {
        Commands::Render(args) => cmd_render(args),
        Commands::List(args) => cmd_list(args),
        Commands::Schema(args) => cmd_schema(args),
        Commands::Validate(args) => cmd_validate(args),
        Commands::Ui(args) => ui::launch(args).await,
    }
}

fn read_data(
    data_path: Option<&Path>,
    data_json: Option<&str>,
) -> anyhow::Result<serde_json::Value> {
    match (data_path, data_json) {
        (Some(path), _) => {
            let text = if path.as_os_str() == "-" {
                let mut buf = String::new();
                io::stdin()
                    .read_to_string(&mut buf)
                    .context("reading stdin")?;
                buf
            } else {
                fs::read_to_string(path).with_context(|| format!("reading {}", path.display()))?
            };
            serde_json::from_str(&text).context("parsing data JSON")
        }
        (_, Some(json_str)) => serde_json::from_str(json_str).context("parsing inline data JSON"),
        _ => bail!("provide --data <FILE|-> or --data-json <JSON>"),
    }
}

fn cmd_render(args: cli::RenderArgs) -> anyhow::Result<()> {
    let registry = registry::TemplateRegistry::load(&args.template_pack)?;
    let preset_name = args
        .preset
        .as_deref()
        .or(args.template.as_deref())
        .context("provide --preset or --template")?;
    let data = read_data(args.data.as_deref(), args.data_json.as_deref())?;

    if args.strict
        && let Some(preset) = registry.get_preset(preset_name)
    {
        validate::validate_data_against_schema(&data, &preset.schema)?;
    }

    let response = render::render_preset(&registry, preset_name, args.theme.as_deref(), &data)?;
    let output = if args.compact {
        serde_json::to_string(&response.card)?
    } else {
        serde_json::to_string_pretty(&response.card)?
    };

    match args.output {
        Some(path) => {
            fs::write(&path, &output).with_context(|| format!("writing {}", path.display()))?
        }
        None => println!("{}", output),
    }
    Ok(())
}

fn cmd_list(args: cli::ListArgs) -> anyhow::Result<()> {
    let registry = registry::TemplateRegistry::load(&args.template_pack)?;
    let presets = registry.presets.values();

    if args.json {
        let items: Vec<serde_json::Value> = presets
            .filter(|p| args.category.as_deref().is_none_or(|c| p.category == c))
            .map(|p| {
                serde_json::json!({
                    "name": p.name,
                    "title": p.title,
                    "description": p.description,
                    "category": p.category,
                    "tags": p.tags,
                })
            })
            .collect();
        println!("{}", serde_json::to_string_pretty(&items)?);
    } else {
        for category in &registry.categories {
            if let Some(filter) = &args.category
                && &category.name != filter
            {
                continue;
            }
            println!("\n{} ({})", category.display_name, category.name);
            for preset_name in &category.presets {
                if let Some(p) = registry.get_preset(preset_name) {
                    println!("  {:<25} {}", p.name, p.description);
                }
            }
        }
    }
    Ok(())
}

fn cmd_schema(args: cli::SchemaArgs) -> anyhow::Result<()> {
    let registry = registry::TemplateRegistry::load(&args.template_pack)?;
    let preset = registry
        .get_preset(&args.template)
        .with_context(|| format!("preset '{}' not found", args.template))?;
    println!("{}", serde_json::to_string_pretty(&preset.schema)?);
    Ok(())
}

fn cmd_validate(args: cli::ValidateArgs) -> anyhow::Result<()> {
    let registry = registry::TemplateRegistry::load(&args.template_pack)?;
    let preset = registry
        .get_preset(&args.template)
        .with_context(|| format!("preset '{}' not found", args.template))?;
    let data = read_data(args.data.as_deref(), args.data_json.as_deref())?;
    validate::validate_data_against_schema(&data, &preset.schema)?;
    println!("Valid.");
    Ok(())
}