use std::path::PathBuf;
use serde::Serialize;
use crate::cli::InitArgs;
use crate::output::format_json;
use crate::{resolve_db_path, CliResult};
#[derive(Serialize)]
struct InitOutput {
directory: DirectoryResult,
database: Option<DatabaseResult>,
#[serde(rename = "claudeCode")]
claude_code: Option<ClaudeCodeResult>,
}
#[derive(Serialize)]
struct DirectoryResult {
created: bool,
path: String,
}
#[derive(Serialize)]
struct DatabaseResult {
created: bool,
path: String,
existed: bool,
}
#[derive(Serialize)]
struct ClaudeCodeResult {
configured: bool,
#[serde(rename = "pluginPath")]
plugin_path: String,
message: String,
}
pub fn run(args: InitArgs) -> CliResult {
let db_path = resolve_db_path(args.db.as_deref())?;
let kindling_dir = db_path
.parent()
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from("."));
let dir_existed = kindling_dir.exists();
if !dir_existed {
std::fs::create_dir_all(&kindling_dir)?;
}
let directory = DirectoryResult {
created: !dir_existed,
path: kindling_dir.to_string_lossy().into_owned(),
};
let database = if args.skip_db {
None
} else {
let db_existed = db_path.exists();
let _service = kindling_service::KindlingService::open(&db_path)?;
Some(DatabaseResult {
created: !db_existed,
path: db_path.to_string_lossy().into_owned(),
existed: db_existed,
})
};
let claude_code = if args.claude_code {
Some(configure_claude_code())
} else {
None
};
let output = InitOutput {
directory,
database,
claude_code,
};
if args.json {
println!("{}", format_json(&output, true)?);
} else {
print_human(&output, args.claude_code);
}
Ok(())
}
fn configure_claude_code() -> ClaudeCodeResult {
let claude_dir = home_dir().map(|h| h.join(".claude"));
match claude_dir {
Some(dir) if dir.exists() => ClaudeCodeResult {
configured: false,
plugin_path: String::new(),
message: "Claude Code detected, but the Rust plugin cutover is not yet \
available (tracked by PORT-015). No plugin was installed."
.to_string(),
},
_ => ClaudeCodeResult {
configured: false,
plugin_path: String::new(),
message: "Claude Code not detected (~/.claude/ does not exist)".to_string(),
},
}
}
fn home_dir() -> Option<PathBuf> {
std::env::var_os("HOME")
.filter(|v| !v.is_empty())
.or_else(|| std::env::var_os("USERPROFILE").filter(|v| !v.is_empty()))
.map(PathBuf::from)
}
fn print_human(output: &InitOutput, claude_code_requested: bool) {
println!("\nKindling Setup");
println!("==============\n");
if output.directory.created {
println!("Created directory {}", output.directory.path);
} else {
println!("Directory exists {}", output.directory.path);
}
if let Some(db) = &output.database {
if db.created {
println!("Created database {}", db.path);
} else if db.existed {
println!("Database exists {}", db.path);
}
}
if claude_code_requested {
if let Some(cc) = &output.claude_code {
println!("\nClaude Code Integration");
println!("-----------------------");
println!("{}", cc.message);
}
}
println!("\nKindling is ready!\n");
println!("Next steps:");
println!(" kindling status - Check database status");
println!(" kindling search - Search your memory");
println!(" kindling serve - Start the daemon");
println!();
}