systemprompt-cli 0.1.18

systemprompt.io OS - CLI for agent orchestration, AI operations, and system management
Documentation
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ArtifactType {
    Table,
    List,
    PresentationCard,
    Text,
    CopyPasteText,
    Chart,
    Form,
    Dashboard,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ChartType {
    Bar,
    Line,
    Pie,
    Area,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct RenderingHints {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub columns: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub chart_type: Option<ChartType>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub theme: Option<String>,
    #[serde(flatten)]
    pub extra: HashMap<String, serde_json::Value>,
}

#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct CommandResult<T> {
    pub data: T,
    pub artifact_type: ArtifactType,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub hints: Option<RenderingHints>,
    #[serde(skip)]
    skip_render: bool,
}

impl<T> CommandResult<T> {
    const fn new(data: T, artifact_type: ArtifactType) -> Self {
        Self {
            data,
            artifact_type,
            title: None,
            hints: None,
            skip_render: false,
        }
    }

    pub const fn should_skip_render(&self) -> bool {
        self.skip_render
    }

    pub const fn with_skip_render(mut self) -> Self {
        self.skip_render = true;
        self
    }

    pub const fn table(data: T) -> Self {
        Self::new(data, ArtifactType::Table)
    }

    pub const fn list(data: T) -> Self {
        Self::new(data, ArtifactType::List)
    }

    pub const fn card(data: T) -> Self {
        Self::new(data, ArtifactType::PresentationCard)
    }

    pub const fn text(data: T) -> Self {
        Self::new(data, ArtifactType::Text)
    }

    pub const fn copy_paste(data: T) -> Self {
        Self::new(data, ArtifactType::CopyPasteText)
    }

    pub fn chart(data: T, chart_type: ChartType) -> Self {
        let mut result = Self::new(data, ArtifactType::Chart);
        result.hints = Some(RenderingHints {
            chart_type: Some(chart_type),
            ..Default::default()
        });
        result
    }

    pub const fn form(data: T) -> Self {
        Self::new(data, ArtifactType::Form)
    }

    pub const fn dashboard(data: T) -> Self {
        Self::new(data, ArtifactType::Dashboard)
    }

    pub fn with_title(mut self, title: impl Into<String>) -> Self {
        self.title = Some(title.into());
        self
    }

    pub fn with_hints(mut self, hints: RenderingHints) -> Self {
        self.hints = Some(hints);
        self
    }

    pub fn with_columns(mut self, columns: Vec<String>) -> Self {
        let mut hints = self.hints.unwrap_or_else(RenderingHints::default);
        hints.columns = Some(columns);
        self.hints = Some(hints);
        self
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct TextOutput {
    pub message: String,
}

impl TextOutput {
    pub fn new(message: impl Into<String>) -> Self {
        Self {
            message: message.into(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct SuccessOutput {
    pub message: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub details: Option<Vec<String>>,
}

impl SuccessOutput {
    pub fn new(message: impl Into<String>) -> Self {
        Self {
            message: message.into(),
            details: None,
        }
    }

    pub fn with_details(mut self, details: Vec<String>) -> Self {
        self.details = Some(details);
        self
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct KeyValueOutput {
    pub items: Vec<KeyValueItem>,
}

#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct KeyValueItem {
    pub key: String,
    pub value: String,
}

impl KeyValueOutput {
    pub const fn new() -> Self {
        Self { items: Vec::new() }
    }

    pub fn add(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.items.push(KeyValueItem {
            key: key.into(),
            value: value.into(),
        });
        self
    }
}

impl Default for KeyValueOutput {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct TableOutput<T> {
    pub rows: Vec<T>,
}

impl<T> TableOutput<T> {
    pub const fn new(rows: Vec<T>) -> Self {
        Self { rows }
    }
}

impl<T> Default for TableOutput<T> {
    fn default() -> Self {
        Self { rows: Vec::new() }
    }
}

use crate::cli_settings::{OutputFormat, get_global_config};
use systemprompt_logging::CliService;

pub fn render_result<T: Serialize>(result: &CommandResult<T>) {
    if result.should_skip_render() {
        return;
    }

    let config = get_global_config();

    match config.output_format() {
        OutputFormat::Json => {
            CliService::json(result);
        },
        OutputFormat::Yaml => {
            CliService::yaml(result);
        },
        OutputFormat::Table => {
            if let Some(title) = &result.title {
                CliService::section(title);
            }
            CliService::json(&result.data);
        },
    }
}