cellos-ctl 0.5.1

cellctl — kubectl-style CLI for CellOS execution cells and formations. Thin HTTP client over cellos-server with apply/get/describe/logs/events/webui.
Documentation
//! `cellctl version` — print client version, then attempt the server version.
//!
//! The client version is printed unconditionally on its own line so an operator
//! always sees what they have locally even when the server is unreachable.
//! After that, we attempt the server query and:
//!
//! - on success, print `server: cellos-server <ver> (<url>)` and exit 0;
//! - on transport OR HTTP error, print `error: unable to connect to the server: <reason>`
//!   to stderr and return a [`CtlError::api`] so the process exits non-zero.
//!
//! This matches `kubectl version`, which also exits non-zero when it cannot
//! reach the API server. See SMOKE-TEST report Finding #2.

use serde_json::Value;

use crate::client::CellosClient;
use crate::exit::{CtlError, CtlResult};

pub async fn run(client: &CellosClient) -> CtlResult<()> {
    println!("client: cellctl {}", env!("CARGO_PKG_VERSION"));

    match client.get_json::<Value>("/v1/version").await {
        Ok(v) => {
            let svr = v
                .get("version")
                .and_then(|x| x.as_str())
                .unwrap_or("unknown");
            println!("server: cellos-server {svr} ({})", client.base_url());
            Ok(())
        }
        Err(e) => {
            // Propagate as an API error so the process exits with code 2 —
            // scripts and CI gates that branch on $? get an honest signal
            // instead of a false-success. `CtlError::exit` formats this as
            // `cellctl: api: unable to connect to the server: <reason>` on
            // stderr, matching the existing error-output contract.
            Err(CtlError::api(format!(
                "unable to connect to the server: {e}"
            )))
        }
    }
}