use std::io::{BufRead, IsTerminal, Write};
use std::path::Path;
use crate::config::{self, LorumConfig};
use crate::error::LorumError;
pub fn run_interactive_init(local: bool, yes: bool) -> Result<(), LorumError> {
run_interactive_init_inner(local, yes, std::io::stdin().is_terminal())
}
pub(crate) fn run_interactive_init_inner(
local: bool,
yes: bool,
is_tty: bool,
) -> Result<(), LorumError> {
if !is_tty && !yes {
return Err(LorumError::Other {
message: "interactive init requires a TTY, use --yes for non-interactive mode".into(),
});
}
let local = if !local && git_repo_detected() {
if yes {
true
} else {
let stdin = std::io::stdin();
let mut stdout = std::io::stdout();
print!(
"Git repository detected. Create a project-level config (.lorum/config.yaml)? [Y/n] "
);
stdout.flush().map_err(|e| LorumError::Io { source: e })?;
let mut line = String::new();
stdin
.lock()
.read_line(&mut line)
.map_err(|e| LorumError::Io { source: e })?;
let trimmed = line.trim().to_lowercase();
trimmed != "n" && trimmed != "no"
}
} else {
local
};
let path = if local {
std::env::current_dir()?.join(".lorum").join("config.yaml")
} else {
config::global_config_path()?
};
if path.exists() {
return Err(LorumError::Other {
message: format!("config already exists: {}", path.display()),
});
}
let detected = super::detect_installed_tools();
if !detected.is_empty() {
println!("Detected installed tools:");
for tool in &detected {
println!(" - {tool}");
}
println!();
}
let mut cfg = LorumConfig::default();
let selected: Vec<String> = if yes {
detected.clone()
} else if !detected.is_empty() {
select_tools_interactive(&detected)?
} else {
Vec::new()
};
for tool in &selected {
if let Err(e) = import_tool(tool, &mut cfg) {
eprintln!("warning: failed to import from {}: {e}", tool);
}
}
config::save_config(&path, &cfg)?;
let server_count = cfg.mcp.servers.len();
println!("created config at: {}", path.display());
if server_count > 0 {
println!(" imported {server_count} MCP server(s)");
}
if local {
let parent = path.parent().ok_or_else(|| LorumError::Other {
message: "cannot determine parent directory for .gitignore".into(),
})?;
let gitignore_path = parent.join(".gitignore");
std::fs::write(&gitignore_path, "skills/\n").map_err(|e| LorumError::Io { source: e })?;
println!("created .gitignore at: {}", gitignore_path.display());
}
Ok(())
}
pub(crate) fn git_repo_detected() -> bool {
let cwd = match std::env::current_dir() {
Ok(d) => d,
Err(_) => return false,
};
is_git_repo(&cwd)
}
pub(crate) fn is_git_repo(dir: &Path) -> bool {
let mut current = Some(dir);
while let Some(d) = current {
if d.join(".git").exists() {
return true;
}
current = d.parent();
}
false
}
fn select_tools_interactive(detected: &[String]) -> Result<Vec<String>, LorumError> {
let stdin = std::io::stdin();
let mut stdout = std::io::stdout();
println!("Which tools should lorum manage? (comma-separated indices, or \"all\")");
for (i, tool) in detected.iter().enumerate() {
println!(" {}) {tool}", i + 1);
}
print!("Selection [all]: ");
stdout.flush().map_err(|e| LorumError::Io { source: e })?;
let mut line = String::new();
stdin
.lock()
.read_line(&mut line)
.map_err(|e| LorumError::Io { source: e })?;
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.eq_ignore_ascii_case("all") {
return Ok(detected.to_vec());
}
let mut selected = Vec::new();
for part in trimmed.split(',') {
let part = part.trim();
if let Ok(idx) = part.parse::<usize>() {
if idx >= 1 && idx <= detected.len() {
selected.push(detected[idx - 1].clone());
}
}
}
Ok(selected)
}
fn import_tool(tool: &str, cfg: &mut LorumConfig) -> Result<(), LorumError> {
let adapter = crate::adapters::find_adapter(tool)
.ok_or_else(|| LorumError::AdapterNotFound { name: tool.into() })?;
let mcp = adapter.read_mcp()?;
for (name, server) in &mcp.servers {
cfg.mcp.servers.insert(name.clone(), server.clone());
}
Ok(())
}