hf2q 0.1.10

Pure Rust CLI for converting HuggingFace models to hardware-optimized formats and serving them over an OpenAI-compatible API on Apple Silicon
mod fs;
mod host;
mod policy;
mod schema;

use std::io::{BufRead, IsTerminal, Write};
use std::path::{Path, PathBuf};

use thiserror::Error;

use crate::cli::SetupArgs;

use self::host::{HostObservation, HostProbe, LiveHostProbe};
use self::policy::{resolve_preferences, PreferenceResolution};
pub(crate) use self::schema::{OperatorConfigV2, ServeDefaultsV2};

#[derive(Debug, Clone)]
pub(crate) struct ConfigPurgePlan {
    pub(crate) root: PathBuf,
    pub(crate) paths: [PathBuf; 3],
}

#[derive(Debug, Error)]
pub(crate) enum SetupError {
    #[error("setup input: {0}")]
    Input(String),
    #[error("setup host inventory: {0}")]
    Host(String),
    #[error("setup configuration: {0}")]
    InvalidConfig(String),
    #[error("setup filesystem: {0}")]
    Filesystem(String),
    #[error("another hf2q setup is already running")]
    Busy,
    #[error("config.toml may have committed but durability could not be proven: {0}")]
    DurabilityUnknown(String),
    #[error("required setup state is missing")]
    Missing,
    #[error("setup I/O: {0}")]
    Io(#[from] std::io::Error),
}

impl SetupError {
    pub(super) fn is_input(&self) -> bool {
        matches!(
            self,
            Self::Input(_) | Self::Host(_) | Self::InvalidConfig(_)
        )
    }
}

pub(super) fn run(args: SetupArgs, explicit_root: Option<&Path>) -> Result<(), SetupError> {
    let stdin = std::io::stdin();
    let stdout = std::io::stdout();
    let interactive = stdin.is_terminal() && stdout.is_terminal();
    let mut input = stdin.lock();
    let mut output = stdout.lock();
    execute(
        args,
        explicit_root,
        &LiveHostProbe,
        interactive,
        &mut input,
        &mut output,
    )
}

pub(crate) fn load_operator_config(
    explicit_root: Option<&Path>,
) -> Result<Option<OperatorConfigV2>, SetupError> {
    let root = resolve_root(explicit_root)?;
    fs::load_config_if_present(&root)
}

pub(crate) fn prepare_config_purge(
    explicit_root: Option<&Path>,
) -> Result<ConfigPurgePlan, SetupError> {
    let root = resolve_root(explicit_root)?;
    fs::validate_purge_target(&root)?;
    Ok(ConfigPurgePlan {
        paths: [
            root.join("config.toml"),
            root.join(".config.toml.partial"),
            root.join(".config.toml.lock"),
        ],
        root,
    })
}

pub(crate) fn execute_config_purge(plan: &ConfigPurgePlan) -> Result<Vec<PathBuf>, SetupError> {
    fs::validate_purge_target(&plan.root)?;
    let removed = fs::purge_config(&plan.root)?;
    Ok(removed
        .into_iter()
        .map(|name| plan.root.join(name))
        .collect())
}

fn execute<P: HostProbe, R: BufRead, W: Write>(
    args: SetupArgs,
    explicit_root: Option<&Path>,
    probe: &P,
    interactive: bool,
    input: &mut R,
    output: &mut W,
) -> Result<(), SetupError> {
    let root = resolve_root(explicit_root)?;
    let observed = fs::observe_existing_config(&root)?;
    let current = observed.config();
    let HostObservation {
        hardware,
        macos_version,
        configured_shell,
        performance_levels,
        logical_cores,
        open_file_soft_limit,
        volume_total_bytes,
        volume_available_bytes,
    } = probe.observe(&root)?;
    hardware.validate()?;
    host::validate_performance_levels(&performance_levels, logical_cores)?;
    let performance_summary = performance_levels
        .iter()
        .map(|level| format!("{} {}", level.logical_cores, level.name))
        .collect::<Vec<_>>()
        .join(" + ");

    writeln!(
        output,
        "Detected {} on {}: {} / {} with {} bytes unified memory and a {}-byte Metal recommended working set (macOS {}, {} logical cores, configured shell {}, RLIMIT_NOFILE {}).",
        hardware.target,
        root.display(),
        hardware.chip_model,
        hardware.metal_device_name,
        hardware.unified_memory_bytes,
        hardware.metal_recommended_working_set_bytes,
        macos_version,
        performance_summary,
        configured_shell.as_str(),
        open_file_soft_limit,
    )?;
    writeln!(
        output,
        "Containing volume: {} bytes total, {} bytes available.",
        volume_total_bytes, volume_available_bytes,
    )?;
    writeln!(
        output,
        "Recommended defaults follow the tested Qwen3.8 coding-client journey; every value remains overrideable per command."
    )?;

    let config = match resolve_preferences(&args, current, interactive, input, output)? {
        PreferenceResolution::Cancelled => {
            writeln!(output, "Setup cancelled; no configuration was changed.")?;
            return Ok(());
        }
        PreferenceResolution::Selected(config) => config,
    };
    let bytes = config.to_canonical_bytes()?;
    let changed = fs::persist(&root, &config, &bytes, &observed)?;
    writeln!(
        output,
        "{} {}",
        if changed { "Configured" } else { "Verified" },
        root.join("config.toml").display()
    )?;
    writeln!(output, "Default convert quant: {}", config.convert.quant)?;
    writeln!(
        output,
        "Default serve endpoint: {}:{}",
        config.serve.host, config.serve.port
    )?;
    writeln!(
        output,
        "Default serve scheduler: {} (max slots {}).",
        config.serve.scheduler.as_str(),
        config.serve.max_slots
    )?;
    if let (Some(penalty), Some(budget)) = (
        config.serve.repetition_penalty,
        config.serve.thinking_token_budget,
    ) {
        writeln!(
            output,
            "Agentic serving profile: repetition penalty {penalty}, thinking budget {budget} tokens."
        )?;
    }
    if config.serve.host == "0.0.0.0" {
        writeln!(
            output,
            "LAN serving requires --auth-token or HF2Q_AUTH_TOKEN before hf2q will bind."
        )?;
    }
    writeln!(
        output,
        "No model was downloaded, converted, loaded, or served."
    )?;
    Ok(())
}

fn resolve_root(explicit: Option<&Path>) -> Result<PathBuf, SetupError> {
    let root = match explicit {
        Some(path) => path.to_owned(),
        None => {
            let home = std::env::var_os("HOME").ok_or_else(|| {
                SetupError::Input("HOME is unset; pass --state-root ABSOLUTE_PATH".to_owned())
            })?;
            if home.is_empty() {
                return Err(SetupError::Input(
                    "HOME is empty; pass --state-root ABSOLUTE_PATH".to_owned(),
                ));
            }
            PathBuf::from(home).join(".hf2q")
        }
    };
    if !root.is_absolute() {
        return Err(SetupError::Input(
            "hf2q state root must be absolute".to_owned(),
        ));
    }
    Ok(root)
}

#[cfg(test)]
mod tests;

#[cfg(test)]
mod defaults_contract_tests;