use std::path::PathBuf;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("`claude` CLI not found on PATH; install it with `npm install -g @anthropic-ai/claude-code`")]
ClaudeNotFound,
#[error("`claude` version {found} is below the required floor {floor}; upgrade with `npm install -g @anthropic-ai/claude-code`")]
ClaudeTooOld {
found: String,
floor: &'static str,
},
#[error("`copilot` CLI not found on PATH; install it with `npm install -g @github/copilot`")]
CopilotNotFound,
#[error("copilot >= 1.0.71 required for plugin management (found {found}); run `copilot update`")]
CopilotTooOld {
found: String,
},
#[error(
"`{var}` is set to an empty string; unset it or point it at a real directory. \
An empty override resolves against the current directory, so the write would \
land where the CLI never reads it."
)]
EmptyConfigDirOverride {
var: &'static str,
},
#[error("`{bin} {args}` failed with exit {code}:\n{stderr}")]
#[non_exhaustive]
Cli {
bin: &'static str,
args: String,
code: i32,
stderr: String,
},
#[error("could not parse {what} as JSON: {source}")]
Json {
what: String,
#[source]
source: serde_json::Error,
},
#[error("{context}: {source}")]
Io {
context: String,
#[source]
source: std::io::Error,
},
#[error("invalid plugin tree: {0}")]
Tree(String),
#[error("could not parse config {path}: {detail}")]
Config {
path: String,
detail: String,
},
#[error("{agent}: {detail}")]
Backend {
agent: String,
detail: String,
},
#[error("post-op verification failed: {0}")]
Verify(String),
#[error("failed to acquire the shared lock at {path}: {source}")]
Lock {
path: PathBuf,
#[source]
source: std::io::Error,
},
}
pub(crate) trait IoContext<T> {
fn io_ctx(self, f: impl FnOnce() -> String) -> Result<T>;
}
impl<T> IoContext<T> for std::io::Result<T> {
fn io_ctx(self, f: impl FnOnce() -> String) -> Result<T> {
self.map_err(|source| Error::Io { context: f(), source })
}
}