artifactize 0.14.2

The AI-native collaboration layer for one-of-a-kind teammates and their agents: split a project into Artifacts with their own evals, and reuse every review while it still holds.
//! Readiness, maintenance and account command workflows.

use super::{Context, cancellation_listener, print_json};
use serde_json::json;
use std::{
    io::{self, Write},
    path::PathBuf,
};

pub(super) async fn tools(
    context: Context,
    options: artifactize_core::diagnostics::ToolCheckOptions,
) -> Result<u8, String> {
    let (cancellation, listener) = cancellation_listener()?;
    let result = artifactize_core::diagnostics::check_tools(
        &context.repo.unwrap_or_else(|| PathBuf::from(".")),
        context.state_dir.as_deref(),
        &options,
        cancellation,
    )
    .await;
    listener.abort();
    let report = result?;
    print_json(&report)?;
    Ok(u8::from(!report.ok))
}

pub(super) async fn login(context: Context) -> Result<u8, String> {
    artifactize_core::auth::codex::login(context.state_dir.as_deref(), context.repo.as_deref())
        .await?;
    if context.json {
        print_json(&json!({ "provider": "codex", "signed_in": true }))?;
    } else {
        writeln!(io::stdout().lock(), "Signed in with Codex.").map_err(|e| e.to_string())?;
    }
    Ok(0)
}

pub(super) async fn logout(context: Context) -> Result<u8, String> {
    let revoked = artifactize_core::auth::codex::logout(
        context.state_dir.as_deref(),
        context.repo.as_deref(),
    )
    .await?;
    if !revoked {
        writeln!(
            io::stderr().lock(),
            "Local tokens removed; their revocation was not confirmed."
        )
        .map_err(|e| e.to_string())?;
    }
    if context.json {
        print_json(&json!({ "provider": "codex", "signed_in": false, "revoked": revoked }))?;
    } else {
        writeln!(io::stdout().lock(), "Signed out of Codex.").map_err(|e| e.to_string())?;
    }
    Ok(0)
}

pub(super) async fn doctor(context: Context) -> Result<u8, String> {
    let report = artifactize_core::diagnostics::doctor(
        context.state_dir.as_deref(),
        context.repo.as_deref(),
    )
    .await?;
    if context.json {
        print_json(&report)?;
    } else {
        let mut out = io::stdout().lock();
        writeln!(
            out,
            "State directory: {}",
            artifactize_core::platform::path_text(&report.state_dir)
        )
        .map_err(|e| e.to_string())?;
        for check in &report.checks {
            writeln!(out, "{} {}: {}", check.status, check.name, check.message)
                .map_err(|e| e.to_string())?;
        }
    }
    Ok(u8::from(!report.ok))
}

pub(super) async fn prune(
    context: Context,
    older_than: Option<std::time::Duration>,
    dry_run: bool,
) -> Result<u8, String> {
    let state = match context.state_dir {
        Some(state) => state,
        None => artifactize_core::store::state_home().map_err(|e| e.to_string())?,
    };
    let report = artifactize_core::store::prune::prune(
        &state,
        context.repo.as_deref(),
        older_than,
        dry_run,
    )?;
    if context.json {
        print_json(&report)?;
    } else {
        let mut out = io::stdout().lock();
        for path in &report.removed {
            writeln!(
                out,
                "Removed {}",
                artifactize_core::platform::path_text(path)
            )
            .map_err(|e| e.to_string())?;
        }
        for path in &report.would_remove {
            writeln!(
                out,
                "Would remove {}",
                artifactize_core::platform::path_text(path)
            )
            .map_err(|e| e.to_string())?;
        }
        for id in &report.skipped_runs {
            writeln!(out, "Skipped Run {id}").map_err(|e| e.to_string())?;
        }
        for id in &report.removed_sessions {
            writeln!(out, "Removed Agent session {id}").map_err(|e| e.to_string())?;
        }
        for id in &report.would_remove_sessions {
            writeln!(out, "Would remove Agent session {id}").map_err(|e| e.to_string())?;
        }
        writeln!(out, "Database audit and repository files were preserved.")
            .map_err(|e| e.to_string())?;
    }
    Ok(0)
}

pub(super) async fn models(context: Context, provider: super::ModelProvider) -> Result<u8, String> {
    let listing = artifactize_core::llm::models::list(
        provider.backend(),
        context.state_dir.as_deref(),
        context.repo.as_deref(),
    )
    .await?;
    if context.json {
        print_json(&listing)?;
    } else {
        let mut out = io::stdout().lock();
        for model in listing.models {
            writeln!(out, "{}\t{}", model.slug, model.display_name).map_err(|e| e.to_string())?;
        }
    }
    Ok(0)
}