use std::fs;
use std::io::{self, Write};
use std::path::Path;
const AGENTS_MINIMAL: &str = include_str!("../assets/templates/minimal/AGENTS.md");
const AGENTS_RUST_ONLY: &str = include_str!("../assets/templates/rust-only/AGENTS.md");
const AGENTS_FULL: &str = include_str!("../assets/templates/full/AGENTS.md");
const AGENTS_MODULAR: &str = include_str!("../assets/templates/modular/AGENTS.md");
const CLIPPY_RELAXED: &str = include_str!("../assets/strictness/relaxed.toml");
const CLIPPY_STANDARD: &str = include_str!("../assets/strictness/standard.toml");
const CLIPPY_STRICT: &str = include_str!("../assets/strictness/strict.toml");
pub fn cmd_init(
template: &str,
strictness: &str,
location: &str,
yes: bool,
) -> anyhow::Result<()> {
let agents = resolve_agents(template)?;
let clippy = resolve_clippy(strictness)?;
let target_path = match location {
"auto" => {
if Path::new(".kimi/AGENTS.md").exists() {
".kimi/AGENTS.md"
} else {
"AGENTS.md"
}
}
".kimi" => {
fs::create_dir_all(".kimi")?;
".kimi/AGENTS.md"
}
"root" => "AGENTS.md",
_ => anyhow::bail!("Unknown location: '{}'. Available: auto, root, .kimi", location),
};
if Path::new(target_path).exists() && !yes {
print!("{} already exists. Overwrite? [y/N] ", target_path);
io::stdout().flush()?;
let mut buf = String::new();
io::stdin().read_line(&mut buf)?;
if !buf.trim().eq_ignore_ascii_case("y") {
println!("Aborted.");
return Ok(());
}
}
fs::write(target_path, agents)?;
println!("✓ Created {} (template: {})", target_path, template);
if template == "modular" {
if let Some(parts_dir) = Path::new(target_path).parent() {
let parts_dir = parts_dir.join("parts");
fs::create_dir_all(&parts_dir)?;
if let Ok(parts) = split_agents_into_parts(agents) {
for (name, content) in parts {
let part_path = parts_dir.join(format!("{}.md", name));
fs::write(&part_path, content)?;
println!(" ✓ Created part {}", part_path.display());
}
}
}
}
if Path::new("Cargo.toml").exists() {
fs::create_dir_all(".cargo")?;
fs::write(".cargo/config.toml", clippy)?;
println!("✓ Created .cargo/config.toml (strictness: {})", strictness);
} else {
println!("⚠ No Cargo.toml found — skipping .cargo/config.toml");
}
if Path::new("Cargo.toml").exists() {
println!(
"\nNext: git add {} .cargo/config.toml && git commit -m 'Add kimi-dotfiles guidelines'",
target_path
);
} else {
println!(
"\nNext: git add {} && git commit -m 'Add kimi-dotfiles guidelines'",
target_path
);
}
Ok(())
}
fn split_agents_into_parts(agents: &str) -> anyhow::Result<Vec<(String, String)>> {
let mut parts = Vec::new();
let mut current_name = String::from("00-preamble");
let mut current_content = String::new();
for line in agents.lines() {
if let Some(name) = line.strip_prefix("<!-- PART: ") {
if let Some(name) = name.strip_suffix(" -->") {
if !current_content.trim().is_empty() {
parts.push((current_name.clone(), current_content.trim().to_string() + "\n"));
}
current_name = name.to_string();
current_content.clear();
continue;
}
}
current_content.push_str(line);
current_content.push('\n');
}
if !current_content.trim().is_empty() {
parts.push((current_name, current_content.trim().to_string() + "\n"));
}
Ok(parts)
}
fn resolve_agents(name: &str) -> anyhow::Result<&'static str> {
match name {
"minimal" => Ok(AGENTS_MINIMAL),
"rust-only" => Ok(AGENTS_RUST_ONLY),
"full" => Ok(AGENTS_FULL),
"modular" => Ok(AGENTS_MODULAR),
_ => anyhow::bail!(
"Unknown template: '{}'. Available: minimal, rust-only, full, modular",
name
),
}
}
fn resolve_clippy(level: &str) -> anyhow::Result<&'static str> {
match level {
"relaxed" => Ok(CLIPPY_RELAXED),
"standard" => Ok(CLIPPY_STANDARD),
"strict" => Ok(CLIPPY_STRICT),
_ => anyhow::bail!(
"Unknown strictness: '{}'. Available: relaxed, standard, strict",
level
),
}
}