1use anyhow::{Result, Context};
2use colored::*;
3use chrono;
4use std::fs;
5use std::path::PathBuf;
6use dirs;
7use crate::captain::license;
8use crate::captain::wtf;
9use crate::captain::version;
10use crate::display;
11use crate::captain::captain_log;
12pub fn run_cargo_with_wrapper(args: &[&str]) -> Result<()> {
13 if !args.is_empty() {
14 let license_manager = crate::captain::license::LicenseManager::new();
15 if let Err(e) = license_manager?.enforce_license(&format!("cargo-{}", args[0])) {
16 eprintln!("❌ License enforcement failed: {}", e);
17 crate::captain::wtf::display_api_failure_art();
18 std::process::exit(1);
19 }
20 }
21 if let Err(e) = version::pre_operation_hook(None) {
22 eprintln!("⚠️ Version auto-increment failed: {}", e);
23 }
24 display::run_cargo_with_display(args);
25 if let Ok(mut log) = captain_log::CaptainLog::new() {
26 let build_result = captain_log::BuildResult {
27 success: true,
28 error_count: 0,
29 warning_count: 0,
30 duration_seconds: 0.0,
31 };
32 if let Err(e) = log
33 .log_command(&format!("cargo {}", args.join(" ")), build_result)
34 {
35 eprintln!("⚠️ Captain's Log recording failed: {}", e);
36 }
37 println!("\n📝 {}", "Captain's Log: Session recorded".dimmed());
38 }
39 if let Err(e) = version::post_operation_hook(None, true) {
40 eprintln!("⚠️ Version post-operation hook failed: {}", e);
41 }
42 Ok(())
43}
44pub fn run_tracked_command(command: &str, session_id: &str) -> Result<()> {
45 use std::process::Command;
46 use std::io::{BufRead, BufReader};
47 let parts: Vec<&str> = command.split_whitespace().collect();
48 if parts.is_empty() {
49 return Err(anyhow::anyhow!("Empty command"));
50 }
51 let mut log = captain_log::CaptainLog::new()?;
52 let parser = captain_log::CargoOutputParser::new();
53 let mut cmd = Command::new(parts[0]);
54 cmd.args(&parts[1..]);
55 if parts[0] == "cargo" {
56 let command_supports_json = parts.get(1).map_or(false, |cmd| {
58 matches!(*cmd, "build" | "check" | "test" | "doc" | "clippy" | "fmt")
59 });
60 if command_supports_json {
61 cmd.arg("--message-format=json");
62 }
63
64 if parts.get(1) == Some(&"publish") {
66 }
69 }
70 let start_time = std::time::Instant::now();
71 let mut child = cmd
72 .stdout(std::process::Stdio::piped())
73 .stderr(std::process::Stdio::piped())
74 .spawn()?;
75 if let Some(stdout) = child.stdout.take() {
76 let reader = BufReader::new(stdout);
77 for line in reader.lines() {
78 let line = line?;
79 println!("{}", line);
80 if let Some(msg) = parser.parse_message(&line)? {
81 if let Some(diagnostic) = msg.message {
82 let entry = parser
83 .create_log_entry_from_diagnostic(&diagnostic, session_id);
84 log.log(&entry.message, entry.tags)?;
85 }
86 }
87 }
88 }
89 if let Some(stderr) = child.stderr.take() {
90 let reader = BufReader::new(stderr);
91 for line in reader.lines() {
92 let line = line?;
93 eprintln!("{}", line);
94 if let Some(msg) = parser.parse_message(&line)? {
95 if let Some(diagnostic) = msg.message {
96 let entry = parser
97 .create_log_entry_from_diagnostic(&diagnostic, session_id);
98 log.log(&entry.message, entry.tags)?;
99 }
100 }
101 }
102 }
103 let status = child.wait()?;
104 let duration = start_time.elapsed();
105 let build_result = captain_log::BuildResult {
106 success: status.success(),
107 error_count: 0,
108 warning_count: 0,
109 duration_seconds: duration.as_secs_f64(),
110 };
111 log.log_command(command, build_result)?;
112 println!("\n🔍 Analysis:");
113 let entries = log.get_recent(1000);
114 let entries_owned: Vec<captain_log::LogEntry> = entries
115 .into_iter()
116 .cloned()
117 .collect();
118 let detector = captain_log::PatternDetector::new(entries_owned);
119 let recurring = detector.find_recurring_errors();
120 if !recurring.is_empty() {
121 println!("\n⚠️ Recurring Issues:");
122 for (error_key, count, _) in recurring.into_iter().take(5) {
123 println!(" {} ({})", error_key.cyan(), count);
124 }
125 }
126 let regressions = detector.detect_build_time_regression();
127 if !regressions.is_empty() {
128 println!("\n📈 Build Time Regressions:");
129 for (command, old_time, new_time) in regressions {
130 println!(
131 " {}: {:.2}s → {:.2}s ({:.1}%)", command.cyan(), old_time, new_time,
132 ((new_time - old_time) / old_time) * 100.0
133 );
134 }
135 }
136 Ok(())
137}
138pub fn get_recent_errors(count: usize) -> Result<Vec<String>> {
139 let home_dir = dirs::home_dir()
140 .ok_or_else(|| anyhow::anyhow!("Could not find home directory"))?;
141 let shipwreck = home_dir.join(".shipwreck");
142 let error_file = shipwreck.join("errors").join("latest.txt");
143 if error_file.exists() {
144 let content = std::fs::read_to_string(&error_file)?;
145 let errors: Vec<String> = content
146 .lines()
147 .filter(|line| !line.trim().is_empty())
148 .take(count)
149 .map(|s| s.to_string())
150 .collect();
151 if errors.is_empty() {
152 Ok(vec!["No recent errors found in the error logs.".to_string()])
153 } else {
154 Ok(errors)
155 }
156 } else {
157 Ok(
158 vec![
159 "No error log file found. Try running some cargo commands first."
160 .to_string()
161 ],
162 )
163 }
164}
165pub fn show_loading_messages() {
166 let messages = [
167 "⚓ Hoisting the sails... preparing to set sail for knowledge!",
168 "🌊 Riding the waves... surfing through the digital ocean!",
169 "🧭 Checking the compass... navigating to the answer!",
170 "🚢 Batten down the hatches... stormy seas of computation ahead!",
171 "🦜 Arr, matey! Consulting the ancient tomes of wisdom!",
172 "⚡ Charging the canons... ready to fire the knowledge salvo!",
173 "🧜♀️ Singing sea shanties... luring the answers from the deep!",
174 "🗺️ Reading the treasure map... X marks the spot of knowledge!",
175 "🦈 Dodging digital sharks... swimming towards the answer!",
176 "🌟 Aligning the stars... consulting the celestial database!",
177 ];
178 let mut index = 0;
179 let start_time = std::time::Instant::now();
180 while start_time.elapsed().as_secs() < 30 {
181 println!("⏳ {}", messages[index]);
182 std::thread::sleep(std::time::Duration::from_secs(3));
183 index = (index + 1) % messages.len();
184 }
185}
186pub fn parse_bool(s: &str) -> Result<bool, std::num::ParseIntError> {
187 match s.to_lowercase().as_str() {
188 "true" | "1" | "yes" | "on" => Ok(true),
189 "false" | "0" | "no" | "off" => Ok(false),
190 _ => Ok(s.parse::<u8>()? != 0),
191 }
192}
193pub fn is_cm_command(cmd: &str) -> bool {
194 matches!(
195 cmd, "anchor" | "journey" | "log" | "tide" | "map" | "mutiny" | "config" |
196 "version" | "view" | "optimize" | "test" | "history" | "init" | "install" |
197 "activate" | "register" | "idea" | "wtf" | "checklist" | "add" | "done" | "clear"
198 | "show" | "list" | "user" | "debug" | "help" | "--help" | "-h" | "tool" |
199 "tools" | "strip" | "scat"
200 )
201}
202pub fn handle_license_check(command: &str) -> Result<()> {
203 let license_manager = crate::captain::license::LicenseManager::new();
204 license_manager?.enforce_license(command)
205}
206pub fn check_command_license(command: &str) -> Result<()> {
207 let license_manager = crate::captain::license::LicenseManager::new();
208 match license_manager?.enforce_license(command) {
209 Ok(_) => Ok(()),
210 Err(e) => Err(e),
211 }
212}