use std::path::PathBuf;
use std::process::ExitCode;
use anyhow::{anyhow, Context, Result};
use crate::adopt::plan::AdoptMode;
use crate::cli::{AdoptModeArg, InitArgs};
const EXAMPLE_TOML: &str = r#"# klasp.toml — generated by `klasp init`
# Docs: https://github.com/klasp-dev/klasp
# Verify this install: run `klasp doctor`
version = 1
[gate]
# Agent surfaces that klasp intercepts. v0.3 ships three: claude_code, codex, aider.
# `klasp install --agent all` walks this list. Comment out any you don't use.
agents = ["claude_code", "codex", "aider"]
# Gate policy: "any_fail" blocks the agent if any check fails.
policy = "any_fail"
# [[checks]]
# name = "lint"
# triggers = [{ on = ["commit"] }]
# timeout_secs = 60
# [checks.source]
# type = "shell"
# command = "ruff check ." # or: cargo clippy --all-targets -- -D warnings
# [[checks]]
# name = "test"
# triggers = [{ on = ["commit"] }]
# [checks.source]
# type = "shell"
# command = "pytest -q" # or: cargo test --workspace
"#;
pub fn run(args: &InitArgs) -> ExitCode {
if args.adopt || args.mode != AdoptModeArg::Inspect {
run_adopt(args)
} else {
match try_run(args) {
Ok(path) => {
println!("wrote {}", path.display());
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("klasp init: {e:#}");
ExitCode::FAILURE
}
}
}
}
fn run_adopt(args: &InitArgs) -> ExitCode {
let repo_root =
match crate::cmd::install::resolve_repo_root(None).context("resolving repo root") {
Ok(r) => r,
Err(e) => {
eprintln!("klasp init: {e:#}");
return ExitCode::FAILURE;
}
};
let plan = match crate::adopt::detect::detect_all(&repo_root) {
Ok(p) => p,
Err(e) => {
eprintln!("klasp init: detecting gates: {e}");
return ExitCode::FAILURE;
}
};
let mode: AdoptMode = args.mode.into();
match mode {
AdoptMode::Inspect => {
print!("{}", crate::adopt::render::render_plan(&plan));
ExitCode::SUCCESS
}
AdoptMode::Mirror => {
print!("{}", crate::adopt::render::render_plan(&plan));
let home = crate::fs_util::home_dir();
let (detected_agents, fell_back) =
crate::adopt::detect_agents::detect_installed_agents(home.as_deref());
let agents_arg = narrowed_agents_arg(&detected_agents, fell_back);
match crate::adopt::writer::write_klasp_toml(&repo_root, &plan, args.force, agents_arg)
{
Ok(path) => {
println!("wrote klasp.toml at {}", path.display());
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("klasp init: {e}");
ExitCode::FAILURE
}
}
}
AdoptMode::Chain => {
let msg = crate::adopt::mode::chain_unsupported_message(&plan);
eprint!("{msg}");
ExitCode::from(2)
}
}
}
fn narrowed_agents_arg(detected: &[String], fell_back: bool) -> Option<&[String]> {
if fell_back {
None
} else {
Some(detected)
}
}
fn try_run(args: &InitArgs) -> Result<PathBuf> {
let repo_root = crate::cmd::install::resolve_repo_root(None).context("resolving repo root")?;
let target = repo_root.join("klasp.toml");
if target.exists() && !args.force {
return Err(anyhow!(
"klasp.toml already exists at {}; pass --force to overwrite",
target.display()
));
}
crate::fs_util::atomic_write_text(&target, EXAMPLE_TOML)
.with_context(|| format!("writing klasp.toml to {}", target.display()))?;
Ok(target)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn narrowed_agents_arg_fallback_returns_none() {
let fallback = vec![
"claude_code".to_string(),
"codex".to_string(),
"aider".to_string(),
];
assert!(narrowed_agents_arg(&fallback, true).is_none());
}
#[test]
fn narrowed_agents_arg_detected_returns_some() {
let agents = vec!["claude_code".to_string()];
assert_eq!(narrowed_agents_arg(&agents, false), Some(agents.as_slice()));
}
#[test]
fn narrowed_agents_arg_user_with_all_three_is_some() {
let agents = vec![
"claude_code".to_string(),
"codex".to_string(),
"aider".to_string(),
];
assert_eq!(narrowed_agents_arg(&agents, false), Some(agents.as_slice()));
}
}