zeroski 0.0.1

zero.ski CLI — @-protocol dispatch, streaming chat, and trace feed for the Zero runtime
//! `zeroski at <cmd> [text...]` — dispatch via the @ protocol.
//!
//! Server accepts two shapes on POST /@:
//!   { "cmd": "help", "args": {...} }    — structured
//!   { "text": "@help" }                 — human shorthand
//!
//! We default to the structured form (cleaner to parse server-side). If
//! `--json` is given, we use its value verbatim as `args`. Otherwise, if
//! there is trailing text, we pack it as `args.text` (most skills treat
//! that as the natural-language argument).

use anyhow::{anyhow, Context, Result};
use serde_json::{json, Value};

use crate::api::Client;
use crate::config::Config;

pub async fn run(
    cfg: &Config,
    cmd: String,
    text: Vec<String>,
    raw_json: Option<String>,
) -> Result<()> {
    let client = Client::new(cfg)?;

    let args: Value = if let Some(j) = raw_json {
        serde_json::from_str(&j).context("parsing --json")?
    } else if text.is_empty() {
        json!({})
    } else {
        json!({ "text": text.join(" ") })
    };

    let body = json!({ "cmd": cmd, "args": args });
    let resp: Value = client.post_json("/@", &body).await?;
    println!("{}", serde_json::to_string_pretty(&resp)?);

    if let Some(ok) = resp.get("success").and_then(Value::as_bool) {
        if !ok {
            return Err(anyhow!(
                "skill returned error: {}",
                resp.get("error").and_then(Value::as_str).unwrap_or("(unspecified)")
            ));
        }
    }
    Ok(())
}