use std::env;
use std::path::{Path, PathBuf};
use tracing::warn;
use crate::{Error, ErrorKind};
pub(crate) fn copilot_binary_with_extract_dir(
extract_dir: Option<&Path>,
) -> Result<PathBuf, Error> {
if let Ok(value) = env::var("COPILOT_CLI_PATH") {
let candidate = PathBuf::from(&value);
if candidate.is_file() {
return Ok(candidate);
}
warn!(
path = %candidate.display(),
"COPILOT_CLI_PATH is set but does not point to a file; falling back"
);
}
#[cfg(feature = "bundled-cli")]
{
let bundled = match extract_dir {
Some(dir) => crate::embeddedcli::install_at(dir),
None => crate::embeddedcli::path(),
};
if let Some(path) = bundled {
return Ok(path);
}
}
#[cfg(not(feature = "bundled-cli"))]
{
let _ = extract_dir;
if let Some(path) = extracted_cli_path() {
return Ok(path);
}
}
Err(ErrorKind::BinaryNotFound {
name: "copilot".into(),
hint: Some(
"the Copilot CLI is not bundled in this build of github-copilot-sdk and \
COPILOT_CLI_PATH is not set. Either keep the default `bundled-cli` cargo \
feature enabled, set COPILOT_CLI_PATH, or supply an explicit path via \
`CliProgram::Path(...)` on `ClientOptions::program`."
.into(),
),
}
.into())
}
#[cfg(all(not(feature = "bundled-cli"), has_extracted_cli))]
fn extracted_cli_path() -> Option<PathBuf> {
let version = env!("COPILOT_SDK_CLI_VERSION");
let binary = if cfg!(windows) {
"copilot.exe"
} else {
"copilot"
};
let dir = match env::var_os("COPILOT_CLI_EXTRACT_DIR") {
Some(custom) => PathBuf::from(custom),
None => dirs::cache_dir()
.unwrap_or_else(env::temp_dir)
.join("github-copilot-sdk")
.join("cli")
.join(sanitize_version(version)),
};
let path = dir.join(binary);
if path.is_file() {
return Some(path);
}
warn!(
path = %path.display(),
"expected build-time-extracted CLI is missing; rebuild the crate or set COPILOT_CLI_PATH"
);
None
}
#[cfg(all(not(feature = "bundled-cli"), not(has_extracted_cli)))]
fn extracted_cli_path() -> Option<PathBuf> {
None
}
#[cfg(all(not(feature = "bundled-cli"), has_extracted_cli))]
fn sanitize_version(version: &str) -> String {
version
.chars()
.map(|c| match c {
'a'..='z' | 'A'..='Z' | '0'..='9' | '.' | '-' | '_' => c,
_ => '_',
})
.collect()
}