supercode-cli 0.4.12

supercode — a lightweight, fully-customizable AI coding agent CLI in Rust. Any model via OpenRouter; natively continues Claude Code and Codex sessions.
//! Build script: stamp a short git SHA (+ dirty flag) and a build date into
//! compile-time env vars so `supercode --version` can report actionable
//! build metadata (UX-16). Never fails the build — if `git` isn't
//! available (e.g. building from a crates.io source tarball, outside any
//! git checkout), the corresponding `SC_GIT_SHA`/`SC_BUILD_DATE` env var is
//! simply left unset and `main.rs` falls back to the bare crate version via
//! `option_env!`.

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

const GOOSE_RUNTIME_CONTRACT: &str =
    "scripts/client-protocol-corpus/contracts/goose-runtime-closure-acd3c135.sha256";

fn main() {
    println!("cargo:rerun-if-env-changed=SOURCE_DATE_EPOCH");
    println!("cargo:rustc-check-cfg=cfg(supercode_workspace_protocol_contracts)");

    let workspace = PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("../..");
    let goose_runtime_contract = workspace.join(GOOSE_RUNTIME_CONTRACT);
    if goose_runtime_contract.is_file() {
        println!("cargo:rustc-cfg=supercode_workspace_protocol_contracts");
        println!(
            "cargo:rerun-if-changed={}",
            goose_runtime_contract.display()
        );
    }

    // Best-effort: re-run this script when the checked-out commit or index
    // changes, so a fresh `cargo build` after `git commit`/`git checkout`
    // picks up the new SHA. If git isn't present or this isn't a checkout,
    // these simply resolve to nothing and we skip the rerun hints.
    for path in ["HEAD", "index"] {
        if let Some(git_path) = git_output(&["rev-parse", "--git-path", path]) {
            println!("cargo:rerun-if-changed={git_path}");
        }
    }

    if let Some(sha) = git_output(&["rev-parse", "--short=8", "HEAD"]) {
        let dirty = git_output(&["status", "--porcelain", "--untracked-files=no"])
            .map(|s| !s.is_empty())
            .unwrap_or(false);
        let sha = if dirty { format!("{sha}-dirty") } else { sha };
        println!("cargo:rustc-env=SC_GIT_SHA={sha}");
    }
    // else: not a git checkout (e.g. a release tarball / crates.io build) —
    // leave SC_GIT_SHA unset; main.rs degrades gracefully via option_env!.

    println!("cargo:rustc-env=SC_BUILD_DATE={}", build_date());
}

/// Run `git <args>` with cwd at this crate's manifest dir, returning
/// trimmed stdout on success, or `None` on any failure (git missing, not a
/// repo, command error, non-UTF8 output, etc). Never panics.
fn git_output(args: &[&str]) -> Option<String> {
    let out = Command::new("git")
        .args(args)
        .current_dir(env!("CARGO_MANIFEST_DIR"))
        .output()
        .ok()?;
    if !out.status.success() {
        return None;
    }
    let s = String::from_utf8(out.stdout).ok()?;
    let s = s.trim();
    if s.is_empty() {
        None
    } else {
        Some(s.to_string())
    }
}

/// UTC build date as `YYYY-MM-DD`, honoring `SOURCE_DATE_EPOCH` for
/// reproducible builds. No date/time dependency: converts a Unix
/// timestamp to a civil (Gregorian) date with Howard Hinnant's
/// `civil_from_days` algorithm.
fn build_date() -> String {
    let epoch_secs = std::env::var("SOURCE_DATE_EPOCH")
        .ok()
        .and_then(|s| s.parse::<u64>().ok())
        .or_else(|| {
            std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .ok()
                .map(|d| d.as_secs())
        })
        .unwrap_or(0);
    epoch_to_ymd(epoch_secs)
}

fn epoch_to_ymd(epoch_secs: u64) -> String {
    let days = (epoch_secs / 86_400) as i64;
    // civil_from_days, http://howardhinnant.github.io/date_algorithms.html
    let z = days + 719_468;
    let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
    let doe = (z - era * 146_097) as u64; // [0, 146096]
    let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365; // [0, 399]
    let y = yoe as i64 + era * 400;
    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365]
    let mp = (5 * doy + 2) / 153; // [0, 11]
    let d = doy - (153 * mp + 2) / 5 + 1; // [1, 31]
    let m = if mp < 10 { mp + 3 } else { mp - 9 }; // [1, 12]
    let y = if m <= 2 { y + 1 } else { y };
    format!("{y:04}-{m:02}-{d:02}")
}