hematite-cli 0.13.0

Senior SysAdmin, Network Admin, Data Analyst, and Software Engineer living in your terminal. A high-precision local AI agent harness for LM Studio, Ollama, and other local OpenAI-compatible runtimes that runs 100% on your own silicon. Reads repos, edits files, runs builds, inspects full network state and workstation telemetry, and runs real Python/JS for data analysis.
Documentation
use serde_json::Value as JsonValue;
use toml::Value as TomlValue;

pub async fn execute(args: &serde_json::Value) -> Result<String, String> {
    let action = args
        .get("action")
        .and_then(|v| v.as_str())
        .unwrap_or("validate");

    match action {
        "validate" => validate(args),
        "format" => format_toml(args),
        "get" => get_path(args),
        "keys" => list_keys(args),
        "to-json" => to_json(args),
        "from-json" => from_json(args),
        other => Err(format!(
            "toml_tools: unknown action '{other}'. Valid: validate, format, get, keys, to-json, from-json"
        )),
    }
}

fn resolve_input(args: &serde_json::Value) -> Result<String, String> {
    if let Some(inline) = args.get("toml").and_then(|v| v.as_str()) {
        return Ok(inline.to_string());
    }
    if let Some(file) = args.get("file").and_then(|v| v.as_str()) {
        let root = if let Some(r) = args.get("_root").and_then(|v| v.as_str()) {
            std::path::PathBuf::from(r)
        } else {
            crate::tools::file_ops::workspace_root()
        };
        let path = root.join(file);
        return std::fs::read_to_string(&path)
            .map_err(|e| format!("toml_tools: cannot read '{file}': {e}"));
    }
    Err("toml_tools: provide 'toml' (inline string) or 'file' (path)".into())
}

fn parse(src: &str) -> Result<TomlValue, String> {
    toml::from_str(src).map_err(|e| format!("toml_tools: invalid TOML: {e}"))
}

fn validate(args: &serde_json::Value) -> Result<String, String> {
    let src = resolve_input(args)?;
    let val = parse(&src)?;

    let kind = toml_kind(&val);
    let key_count = if let TomlValue::Table(t) = &val {
        t.len()
    } else {
        0
    };
    let item_count = if let TomlValue::Array(a) = &val {
        a.len()
    } else {
        0
    };

    let mut out = format!("TOML VALID\n{}\n", "".repeat(50));
    out.push_str(&format!("Root type : {kind}\n"));
    if key_count > 0 {
        out.push_str(&format!("Top-level keys: {key_count}\n"));
        if let TomlValue::Table(t) = &val {
            for (k, v) in t.iter().take(10) {
                out.push_str(&format!("  - {} ({})\n", k, toml_kind(v)));
            }
            if key_count > 10 {
                out.push_str(&format!("  ... and {} more\n", key_count - 10));
            }
        }
    }
    if item_count > 0 {
        out.push_str(&format!("Array length: {item_count}\n"));
    }
    Ok(out)
}

fn format_toml(args: &serde_json::Value) -> Result<String, String> {
    let src = resolve_input(args)?;
    let val = parse(&src)?;
    let formatted =
        toml::to_string_pretty(&val).map_err(|e| format!("toml_tools: serialize error: {e}"))?;
    Ok(format!("TOML FORMAT\n{}\n{formatted}", "".repeat(50)))
}

fn get_path(args: &serde_json::Value) -> Result<String, String> {
    let src = resolve_input(args)?;
    let path = args.get("path").and_then(|v| v.as_str()).ok_or(
        "toml_tools get: 'path' is required (e.g. 'package.name' or 'dependencies.serde')",
    )?;

    let val = parse(&src)?;
    let result =
        navigate_toml(&val, path).ok_or_else(|| format!("toml_tools: path '{path}' not found"))?;

    let repr = toml::to_string(result).unwrap_or_else(|_| format!("{result:?}"));
    Ok(format!("TOML GET: {path}\n{}\n{repr}", "".repeat(50)))
}

fn navigate_toml<'a>(val: &'a TomlValue, path: &str) -> Option<&'a TomlValue> {
    let mut cur = val;
    for part in path.split('.') {
        if let Some(bracket) = part.find('[') {
            let key = &part[..bracket];
            let idx_str = part[bracket + 1..].trim_end_matches(']');
            let idx: usize = idx_str.parse().ok()?;
            if !key.is_empty() {
                cur = cur.get(key)?;
            }
            cur = cur.as_array()?.get(idx)?;
        } else {
            cur = cur.get(part)?;
        }
    }
    Some(cur)
}

fn list_keys(args: &serde_json::Value) -> Result<String, String> {
    let src = resolve_input(args)?;
    let path = args.get("path").and_then(|v| v.as_str());
    let val = parse(&src)?;

    let target = if let Some(p) = path {
        navigate_toml(&val, p)
            .ok_or_else(|| format!("toml_tools: path '{p}' not found"))?
            .clone()
    } else {
        val
    };

    let mut out = format!(
        "TOML KEYS{}\n{}\n",
        path.map(|p| format!(": {p}")).unwrap_or_default(),
        "".repeat(50)
    );

    match &target {
        TomlValue::Table(t) => {
            for (k, v) in t {
                out.push_str(&format!("  {} ({})\n", k, toml_kind(v)));
            }
            out.push_str(&format!("\nTotal: {} key(s)\n", t.len()));
        }
        TomlValue::Array(a) => {
            out.push_str(&format!("Array with {} element(s):\n", a.len()));
            for (i, v) in a.iter().enumerate().take(20) {
                out.push_str(&format!("  [{}] {}\n", i, toml_kind(v)));
            }
            if a.len() > 20 {
                out.push_str(&format!("  ... and {} more\n", a.len() - 20));
            }
        }
        other => {
            out.push_str(&format!("Scalar: {}\n", toml_kind(other)));
        }
    }
    Ok(out)
}

fn to_json(args: &serde_json::Value) -> Result<String, String> {
    let src = resolve_input(args)?;
    let toml_val: TomlValue = parse(&src)?;
    let json_val: JsonValue = serde_json::to_value(&toml_val)
        .map_err(|e| format!("toml_tools to-json: conversion error: {e}"))?;
    let pretty = serde_json::to_string_pretty(&json_val)
        .map_err(|e| format!("toml_tools to-json: serialize error: {e}"))?;
    Ok(format!("TOML → JSON\n{}\n{pretty}", "".repeat(50)))
}

fn from_json(args: &serde_json::Value) -> Result<String, String> {
    let json_src = args
        .get("json")
        .and_then(|v| v.as_str())
        .ok_or("toml_tools from-json: provide 'json' arg with inline JSON string")?;

    let json_val: JsonValue = serde_json::from_str(json_src)
        .map_err(|e| format!("toml_tools from-json: invalid JSON: {e}"))?;
    let toml_val: TomlValue = serde_json::from_value(json_val)
        .map_err(|e| format!("toml_tools from-json: conversion error: {e}"))?;
    let toml_str = toml::to_string_pretty(&toml_val)
        .map_err(|e| format!("toml_tools from-json: serialize error: {e}"))?;

    Ok(format!("JSON → TOML\n{}\n{toml_str}", "".repeat(50)))
}

fn toml_kind(v: &TomlValue) -> &'static str {
    match v {
        TomlValue::String(_) => "string",
        TomlValue::Integer(_) => "integer",
        TomlValue::Float(_) => "float",
        TomlValue::Boolean(_) => "bool",
        TomlValue::Datetime(_) => "datetime",
        TomlValue::Array(_) => "array",
        TomlValue::Table(_) => "table",
    }
}