ccd-cli 1.0.0-beta.4

Bootstrap and validate Continuous Context Development repositories
// `ccd machine …` is introduced by RFC 0006 M1 as the flat-layout CLI
// surface that the pod-layer collapse will converge on. During the
// transition `ccd machine list` reads both `~/.ccd/machines/` and the
// legacy `~/.ccd/pods/{pod}/` layout.

use std::process::ExitCode;

use anyhow::Result;
use serde::Serialize;

use crate::output::CommandReport;
use crate::paths::state::default_ccd_root;
use crate::state::machine_registry;
use crate::state::machine_registry::MachineTrustClass;

#[derive(Serialize)]
pub struct MachineListEntry {
    pub id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub display_name: Option<String>,
    pub trust_class: MachineTrustClass,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub profiles: Vec<String>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub capabilities: Vec<String>,
    pub source: &'static str,
    pub manifest_path: String,
}

#[derive(Serialize)]
pub struct MachineListReport {
    command: &'static str,
    ok: bool,
    pub machines: Vec<MachineListEntry>,
}

impl CommandReport for MachineListReport {
    fn exit_code(&self) -> ExitCode {
        if self.ok {
            ExitCode::SUCCESS
        } else {
            ExitCode::FAILURE
        }
    }

    fn render_text(&self) {
        if self.machines.is_empty() {
            println!("No machines registered.");
            return;
        }
        println!("{:<24} {:<10} {:<10}", "Machine ID", "Trust", "Source");
        for entry in &self.machines {
            let trust = match entry.trust_class {
                MachineTrustClass::Owned => "owned",
                MachineTrustClass::Limited => "limited",
                MachineTrustClass::Observer => "observer",
            };
            println!("{:<24} {:<10} {:<10}", entry.id, trust, entry.source);
        }
    }
}

pub fn list() -> Result<MachineListReport> {
    let ccd_root = default_ccd_root()?;
    let records = machine_registry::load_all_machines(&ccd_root)?;

    let machines: Vec<MachineListEntry> = records
        .into_iter()
        .map(|record| MachineListEntry {
            id: record.id,
            display_name: record.display_name,
            trust_class: record.trust_class,
            profiles: record.profiles,
            capabilities: record.capabilities,
            source: record.source.as_str(),
            manifest_path: record.manifest_path,
        })
        .collect();

    Ok(MachineListReport {
        command: "machine-list",
        ok: true,
        machines,
    })
}