miyabi_cli/commands/
install.rs1use crate::error::{CliError, Result};
4use colored::Colorize;
5use std::fs;
6use std::path::Path;
7
8pub struct InstallCommand {
9 pub dry_run: bool,
10}
11
12impl InstallCommand {
13 pub fn new(dry_run: bool) -> Self {
14 Self { dry_run }
15 }
16
17 pub async fn execute(&self) -> Result<()> {
18 println!(
19 "{}",
20 "📦 Installing Miyabi to existing project...".cyan().bold()
21 );
22
23 if self.dry_run {
24 println!("{}", " (Dry run - no changes will be made)".yellow());
25 }
26
27 self.verify_git_repository()?;
29
30 if self.is_miyabi_installed() {
32 println!(
33 "{}",
34 "⚠️ Miyabi is already installed in this project".yellow()
35 );
36 return Ok(());
37 }
38
39 self.create_directory_structure()?;
41
42 self.create_configuration_files()?;
44
45 self.update_gitignore()?;
47
48 println!();
49 println!("{}", "✅ Miyabi installed successfully!".green().bold());
50 println!();
51 println!("Next steps:");
52 println!(" export GITHUB_TOKEN=ghp_xxx");
53 println!(" miyabi status");
54
55 Ok(())
56 }
57
58 fn verify_git_repository(&self) -> Result<()> {
59 use std::process::Command;
60
61 let output = Command::new("git")
62 .args(["rev-parse", "--git-dir"])
63 .output()?;
64
65 if !output.status.success() {
66 return Err(CliError::NotGitRepository);
67 }
68
69 println!(" ✓ Git repository detected");
70 Ok(())
71 }
72
73 fn is_miyabi_installed(&self) -> bool {
74 Path::new(".miyabi.yml").exists()
75 }
76
77 fn create_directory_structure(&self) -> Result<()> {
78 let dirs = vec![
79 ".github/workflows",
80 ".claude/agents/specs",
81 ".claude/agents/prompts",
82 ".claude/commands",
83 "docs",
84 "logs",
85 "reports",
86 ];
87
88 for dir in dirs {
89 if self.dry_run {
90 println!(" [DRY RUN] Would create: {}", dir);
91 } else {
92 fs::create_dir_all(dir)?;
93 println!(" Created: {}", dir);
94 }
95 }
96
97 Ok(())
98 }
99
100 fn create_configuration_files(&self) -> Result<()> {
101 let project_name = self.get_project_name()?;
103
104 let miyabi_config = format!(
105 r#"# Miyabi Configuration
106project_name: {}
107version: "0.1.0"
108
109# GitHub settings (use environment variables for sensitive data)
110# github_token: ${{{{ GITHUB_TOKEN }}}}
111
112# Agent settings
113agents:
114 enabled: true
115 use_worktree: true
116 worktree_base_path: ".worktrees"
117
118# Logging
119logging:
120 level: info
121 directory: "./logs"
122
123# Reporting
124reporting:
125 directory: "./reports"
126"#,
127 project_name
128 );
129
130 if self.dry_run {
131 println!(" [DRY RUN] Would create: .miyabi.yml");
132 } else {
133 fs::write(".miyabi.yml", miyabi_config)?;
134 println!(" Created: .miyabi.yml");
135 }
136
137 Ok(())
138 }
139
140 fn update_gitignore(&self) -> Result<()> {
141 let gitignore_entries = r#"
142# Miyabi
143.miyabi.yml
144.worktrees/
145logs/
146reports/
147*.log
148"#;
149
150 if self.dry_run {
151 println!(" [DRY RUN] Would update: .gitignore");
152 } else {
153 let existing_gitignore = fs::read_to_string(".gitignore").unwrap_or_default();
155
156 if !existing_gitignore.contains("# Miyabi") {
158 let updated_gitignore = format!("{}{}", existing_gitignore, gitignore_entries);
159 fs::write(".gitignore", updated_gitignore)?;
160 println!(" Updated: .gitignore");
161 } else {
162 println!(" Skipped: .gitignore (already has Miyabi entries)");
163 }
164 }
165
166 Ok(())
167 }
168
169 fn get_project_name(&self) -> Result<String> {
170 use std::process::Command;
171
172 let output = Command::new("git")
174 .args(["remote", "get-url", "origin"])
175 .output();
176
177 if let Ok(output) = output {
178 if output.status.success() {
179 let url = String::from_utf8_lossy(&output.stdout);
180 if let Some(name) = url.split('/').next_back() {
181 return Ok(name.trim().trim_end_matches(".git").to_string());
182 }
183 }
184 }
185
186 let current_dir = std::env::current_dir()?;
188 let name = current_dir
189 .file_name()
190 .and_then(|n| n.to_str())
191 .unwrap_or("miyabi-project");
192
193 Ok(name.to_string())
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 #[test]
202 fn test_install_command_creation() {
203 let cmd = InstallCommand::new(false);
204 assert!(!cmd.dry_run);
205
206 let cmd = InstallCommand::new(true);
207 assert!(cmd.dry_run);
208 }
209}