tftio-clanker 0.2.0

Launch AI harnesses with runtime-configured context, domains, models, and prompts
Documentation
#![cfg_attr(
    not(test),
    deny(clippy::unwrap_used, clippy::panic, clippy::indexing_slicing)
)]
//! Binary entrypoint for clanker command and multi-call symlink modes.

use std::ffi::OsString;
use std::path::PathBuf;
use std::process::Command;

use clanker::error::ClankerError;
use clanker::{RuntimeContext, command_exit_code, invocation_name, run_symlink_mode};
use tftio_cli_common::{JsonOutput, ProcessEnv, run_with_display_error_handler};

fn main() {
    let argv: Vec<OsString> = std::env::args_os().collect();
    let invocation = argv
        .first()
        .map_or_else(|| "clanker".to_string(), |value| invocation_name(value));
    let process_env = process_env();
    let runtime = match runtime_context() {
        Ok(runtime) => runtime,
        Err(error) => {
            eprintln!("error: {error}");
            std::process::exit(1);
        }
    };

    let exit_code = if invocation.ends_with("-launch") {
        let harness_arguments = argv.into_iter().skip(1).collect();
        run_with_display_error_handler("launch", JsonOutput::Text, || {
            run_symlink_mode(&invocation, harness_arguments, &runtime)
        })
    } else {
        command_exit_code(argv, &process_env, &runtime)
    };
    std::process::exit(exit_code);
}

#[allow(
    clippy::disallowed_methods,
    reason = "process environment is read once at the binary edge"
)]
fn process_env() -> ProcessEnv {
    ProcessEnv {
        agent: tftio_cli_common::AgentModeContext::from_tokens(
            std::env::var(tftio_cli_common::AGENT_TOKEN_ENV).ok(),
            std::env::var(tftio_cli_common::AGENT_TOKEN_EXPECTED_ENV).ok(),
        ),
        home: std::env::var_os("HOME").map(PathBuf::from),
    }
}

#[allow(
    clippy::disallowed_methods,
    reason = "launch environment and working directory are read once at the binary edge"
)]
fn runtime_context() -> Result<RuntimeContext, ClankerError> {
    let home = std::env::var_os("HOME").map(PathBuf::from);
    let current_dir = std::env::current_dir().map_err(ClankerError::CurrentDirectory)?;
    let hostname = short_hostname().map_err(|error| error.to_string());
    let inherited_environment = std::env::vars_os().collect();
    Ok(RuntimeContext {
        home,
        current_dir,
        hostname,
        config_environment: std::env::var_os("CLANKER_CONFIG").map(PathBuf::from),
        context_override: environment_text("CONTEXT_OVERRIDE")?,
        context_environment: environment_text("CONTEXT")?,
        domain_override: environment_text("CLANKER_DOMAIN_OVERRIDE")?,
        domain_environment: environment_text("CLANKER_DOMAIN")?,
        model_environment: environment_text("CLANKER_MODEL")?,
        inherited_path: std::env::var_os("PATH"),
        inherited_environment,
    })
}

#[allow(
    clippy::disallowed_methods,
    reason = "environment value is parsed at the binary edge"
)]
fn environment_text(name: &'static str) -> Result<Option<String>, ClankerError> {
    std::env::var_os(name)
        .map(|value| {
            value
                .into_string()
                .map_err(|_| ClankerError::NonUtf8Environment(name))
        })
        .transpose()
}

fn short_hostname() -> Result<String, ClankerError> {
    let output = Command::new("hostname")
        .arg("-s")
        .output()
        .map_err(|error| ClankerError::Hostname(error.to_string()))?;
    if !output.status.success() {
        return Err(ClankerError::Hostname(format!(
            "`hostname -s` exited with {}",
            output.status
        )));
    }
    let hostname = String::from_utf8(output.stdout)
        .map_err(|error| ClankerError::Hostname(error.to_string()))?;
    let hostname = hostname.trim();
    if hostname.is_empty() {
        Err(ClankerError::Hostname(
            "`hostname -s` returned an empty value".to_string(),
        ))
    } else {
        Ok(hostname.to_string())
    }
}