zeroski 0.0.1

zero.ski CLI — @-protocol dispatch, streaming chat, and trace feed for the Zero runtime
//! zeroski — CLI for the Zero runtime (zero.ski).
//!
//! Five commands in v0.0.1:
//!   chat    — stream a completion from /api/chat
//!   at      — dispatch via the @ protocol (/@)
//!   me      — identity of the caller (/api/me)
//!   traces  — recent trace history (/traces/agent/:id)
//!   config  — print effective config
//!
//! Global flags: --base, --key. Env fallback: ZEROSKI_BASE, ZEROSKI_KEY.

mod api;
mod commands;
mod config;

use anyhow::Result;
use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(
    name = "zeroski",
    version,
    about = "CLI for the Zero runtime (zero.ski) — @-protocol, streaming chat, trace feed",
    long_about = None,
)]
struct Cli {
    /// Base URL. Defaults to https://zero.ski. Env: ZEROSKI_BASE.
    #[arg(long, global = true)]
    base: Option<String>,

    /// Bearer API key. Env: ZEROSKI_KEY. Public endpoints work without it.
    #[arg(long, global = true)]
    key: Option<String>,

    #[command(subcommand)]
    cmd: Cmd,
}

#[derive(Subcommand)]
enum Cmd {
    /// Stream a chat completion from /api/chat.
    Chat {
        /// Prompt text. If omitted, read from stdin.
        #[arg(trailing_var_arg = true)]
        text: Vec<String>,
        /// Model alias: fast | code | think. Defaults to fast.
        #[arg(long)]
        model: Option<String>,
    },
    /// Dispatch a skill via the @ protocol.
    At {
        /// Skill name to invoke (e.g. "help", "chat", "mro").
        cmd: String,
        /// Trailing text passed as `args.text`.
        #[arg(trailing_var_arg = true)]
        text: Vec<String>,
        /// Raw JSON args. Overrides trailing text if both are given.
        #[arg(long)]
        json: Option<String>,
    },
    /// Print the caller's identity (/api/me).
    Me,
    /// Show recent traces for the caller (or --agent).
    Traces {
        /// Agent id (32-hex). Defaults to the caller's own.
        #[arg(long)]
        agent: Option<String>,
        /// Max rows to return. Server caps apply.
        #[arg(long, default_value_t = 20)]
        limit: u32,
    },
    /// Print the effective configuration (base, key presence, file path).
    Config,
}

#[tokio::main]
async fn main() -> Result<()> {
    let cli = Cli::parse();
    let cfg = config::Config::resolve(cli.base, cli.key)?;

    match cli.cmd {
        Cmd::Chat { text, model } => commands::chat::run(&cfg, text, model).await,
        Cmd::At { cmd, text, json } => commands::at::run(&cfg, cmd, text, json).await,
        Cmd::Me => commands::me::run(&cfg).await,
        Cmd::Traces { agent, limit } => commands::traces::run(&cfg, agent, limit).await,
        Cmd::Config => commands::config_cmd::run(&cfg),
    }
}