lifeloop-cli 0.2.0

Provider-neutral lifecycle abstraction and normalizer for AI harnesses
Documentation
//! `lifeloop manifest list|show|inspect` — neutral manifest registry
//! inspection.
//!
//! `list` dumps the full registry as a JSON array of
//! `{ adapter_id, adapter_version, conformance, display_name }` rows.
//! `show <id>` dumps the full `AdapterManifest` JSON for that adapter.
//! `inspect <id>@<version>` is a stricter `show`: the registered
//! version must match the requested version, exactly as
//! `BuiltinAdapterRegistry::resolve` does.

use lifeloop::{AdapterManifest, lookup_manifest, manifest_registry};
use serde::Serialize;

use super::{CliError, print_json};

#[derive(Serialize)]
struct ManifestSummary<'a> {
    adapter_id: &'a str,
    adapter_version: &'a str,
    display_name: &'a str,
    conformance: lifeloop::ConformanceLevel,
}

pub fn run<I: Iterator<Item = String>>(mut args: I) -> Result<(), CliError> {
    let action = args.next().ok_or_else(|| {
        CliError::Usage("manifest requires a subcommand: list | show | inspect".to_string())
    })?;
    match action.as_str() {
        "list" => run_list(args),
        "show" => run_show(args),
        "inspect" => run_inspect(args),
        other => Err(CliError::Usage(format!(
            "manifest: unknown subcommand `{other}` (expected: list | show | inspect)"
        ))),
    }
}

fn run_list<I: Iterator<Item = String>>(mut args: I) -> Result<(), CliError> {
    if args.next().is_some() {
        return Err(CliError::Usage(
            "manifest list: unexpected extra argument".into(),
        ));
    }
    let registry = manifest_registry();
    let summaries: Vec<ManifestSummary<'_>> = registry
        .iter()
        .map(|entry| ManifestSummary {
            adapter_id: &entry.manifest.adapter_id,
            adapter_version: &entry.manifest.adapter_version,
            display_name: &entry.manifest.display_name,
            conformance: entry.conformance,
        })
        .collect();
    print_json(&summaries)
}

fn run_show<I: Iterator<Item = String>>(mut args: I) -> Result<(), CliError> {
    let id = args
        .next()
        .ok_or_else(|| CliError::Usage("manifest show requires an <adapter_id>".into()))?;
    if args.next().is_some() {
        return Err(CliError::Usage(
            "manifest show: unexpected extra argument".into(),
        ));
    }
    let entry = lookup_manifest(&id)
        .ok_or_else(|| CliError::Validation(format!("manifest show: unknown adapter_id `{id}`")))?;
    print_json::<AdapterManifest>(&entry.manifest)
}

fn run_inspect<I: Iterator<Item = String>>(mut args: I) -> Result<(), CliError> {
    let spec = args.next().ok_or_else(|| {
        CliError::Usage("manifest inspect requires <adapter_id>@<version>".into())
    })?;
    if args.next().is_some() {
        return Err(CliError::Usage(
            "manifest inspect: unexpected extra argument".into(),
        ));
    }
    let (id, version) = spec.split_once('@').ok_or_else(|| {
        CliError::Usage(format!(
            "manifest inspect: expected <adapter_id>@<version>, got `{spec}`"
        ))
    })?;
    let entry = lookup_manifest(id).ok_or_else(|| {
        CliError::Validation(format!("manifest inspect: unknown adapter_id `{id}`"))
    })?;
    if entry.manifest.adapter_version != version {
        return Err(CliError::Validation(format!(
            "manifest inspect: adapter `{id}` is registered at version `{}`, not `{version}`",
            entry.manifest.adapter_version
        )));
    }
    print_json::<AdapterManifest>(&entry.manifest)
}