github.rs (11848B)
1 use crate::chatops::{ChatCommand, ChatOpError, ChatOpResult, CommandContext}; 2 use std::process::Command; 3 4 /// GitHub repository information 5 pub struct GitHubCommand; 6 7 impl ChatCommand for GitHubCommand { 8 fn name(&self) -> &'static str { 9 "github" 10 } 11 fn description(&self) -> &'static str { 12 "Get GitHub repository information" 13 } 14 fn usage(&self) -> &'static str { 15 "/github <user>/<repo> [issues|latest|file <path>]" 16 } 17 fn aliases(&self) -> Vec<&'static str> { 18 vec!["gh"] 19 } 20 21 fn execute( 22 &self, 23 args: Vec<String>, 24 _context: &CommandContext, 25 ) -> Result<ChatOpResult, ChatOpError> { 26 if args.is_empty() { 27 return Err(ChatOpError::MissingArguments( 28 "Please specify a repository (user/repo)".to_string(), 29 )); 30 } 31 32 let repo = &args[0]; 33 if !repo.contains('/') { 34 return Err(ChatOpError::InvalidSyntax( 35 "Repository must be in format 'user/repo'".to_string(), 36 )); 37 } 38 39 let action = args.get(1).map(|s| s.as_str()).unwrap_or("info"); 40 41 match action { 42 "issues" => Ok(ChatOpResult::Message(format!( 43 "🐛 GitHub Issues for {}: https://github.com/{}/issues", 44 repo, repo 45 ))), 46 "latest" => Ok(ChatOpResult::Message(format!( 47 "🏷️ Latest Release for {}: https://github.com/{}/releases/latest", 48 repo, repo 49 ))), 50 "file" => { 51 if args.len() < 3 { 52 return Err(ChatOpError::MissingArguments( 53 "Please specify file path".to_string(), 54 )); 55 } 56 let file_path = &args[2]; 57 Ok(ChatOpResult::Message(format!( 58 "📄 File {}: https://github.com/{}/blob/main/{}", 59 file_path, repo, file_path 60 ))) 61 } 62 _ => { 63 // Basic repo info 64 let info = vec![ 65 format!("📦 **GitHub Repository: {}**", repo), 66 format!("🔗 URL: https://github.com/{}", repo), 67 format!("📊 Issues: https://github.com/{}/issues", repo), 68 format!("🏷️ Releases: https://github.com/{}/releases", repo), 69 format!("📋 README: https://github.com/{}#readme", repo), 70 ]; 71 Ok(ChatOpResult::Block(info)) 72 } 73 } 74 } 75 } 76 77 /// GitHub Gist creation 78 pub struct GistCommand; 79 80 impl ChatCommand for GistCommand { 81 fn name(&self) -> &'static str { 82 "gist" 83 } 84 fn description(&self) -> &'static str { 85 "Create a GitHub Gist (requires gh CLI)" 86 } 87 fn usage(&self) -> &'static str { 88 "/gist <code>" 89 } 90 91 fn execute( 92 &self, 93 args: Vec<String>, 94 _context: &CommandContext, 95 ) -> Result<ChatOpResult, ChatOpError> { 96 if args.is_empty() { 97 return Err(ChatOpError::MissingArguments( 98 "Please specify code to create a gist".to_string(), 99 )); 100 } 101 102 let code = args.join(" "); 103 104 // Try using GitHub CLI if available 105 match Command::new("gh") 106 .args(&["gist", "create", "-"]) 107 .stdin(std::process::Stdio::piped()) 108 .stdout(std::process::Stdio::piped()) 109 .spawn() 110 { 111 Ok(mut child) => { 112 use std::io::Write; 113 if let Some(stdin) = child.stdin.as_mut() { 114 let _ = stdin.write_all(code.as_bytes()); 115 } 116 117 match child.wait_with_output() { 118 Ok(output) => { 119 if output.status.success() { 120 let gist_url = 121 String::from_utf8_lossy(&output.stdout).trim().to_string(); 122 Ok(ChatOpResult::Message(format!( 123 "📝 Gist created: {}", 124 gist_url 125 ))) 126 } else { 127 Ok(ChatOpResult::Message("📝 Failed to create gist. Make sure you're logged in with `gh auth login`".to_string())) 128 } 129 } 130 Err(_) => Ok(ChatOpResult::Message( 131 "📝 Failed to create gist".to_string(), 132 )), 133 } 134 } 135 Err(_) => { 136 // Fallback - just show the manual gist creation URL 137 Ok(ChatOpResult::Message(format!( 138 "📝 Create gist manually at: https://gist.github.com/" 139 ))) 140 } 141 } 142 } 143 } 144 145 /// Rust crates.io information 146 pub struct CratesCommand; 147 148 impl ChatCommand for CratesCommand { 149 fn name(&self) -> &'static str { 150 "crates" 151 } 152 fn description(&self) -> &'static str { 153 "Get Rust crate information from crates.io" 154 } 155 fn usage(&self) -> &'static str { 156 "/crates <crate_name>" 157 } 158 159 fn execute( 160 &self, 161 args: Vec<String>, 162 _context: &CommandContext, 163 ) -> Result<ChatOpResult, ChatOpError> { 164 if args.is_empty() { 165 return Err(ChatOpError::MissingArguments( 166 "Please specify a crate name".to_string(), 167 )); 168 } 169 170 let crate_name = &args[0]; 171 172 // Try to fetch from crates.io API 173 match Command::new("curl") 174 .args(&[ 175 "-s", 176 &format!("https://crates.io/api/v1/crates/{}", crate_name), 177 ]) 178 .output() 179 { 180 Ok(output) => { 181 if output.status.success() { 182 let result = String::from_utf8_lossy(&output.stdout); 183 if result.contains("\"crate\"") { 184 // Parse basic info (in real implementation, use serde_json) 185 let info = vec![ 186 format!("📦 **Rust Crate: {}**", crate_name), 187 format!("🔗 crates.io: https://crates.io/crates/{}", crate_name), 188 format!("📚 docs.rs: https://docs.rs/{}", crate_name), 189 format!("📋 Add to Cargo.toml: {} = \"latest\"", crate_name), 190 ]; 191 Ok(ChatOpResult::Block(info)) 192 } else { 193 Ok(ChatOpResult::Message(format!( 194 "📦 Crate '{}' not found on crates.io", 195 crate_name 196 ))) 197 } 198 } else { 199 Ok(ChatOpResult::Message(format!( 200 "📦 Failed to fetch info for crate '{}'", 201 crate_name 202 ))) 203 } 204 } 205 Err(_) => Ok(ChatOpResult::Message(format!( 206 "📦 Check crate manually: https://crates.io/crates/{}", 207 crate_name 208 ))), 209 } 210 } 211 } 212 213 /// NPM package information 214 pub struct NpmCommand; 215 216 impl ChatCommand for NpmCommand { 217 fn name(&self) -> &'static str { 218 "npm" 219 } 220 fn description(&self) -> &'static str { 221 "Get NPM package information" 222 } 223 fn usage(&self) -> &'static str { 224 "/npm <package_name>" 225 } 226 227 fn execute( 228 &self, 229 args: Vec<String>, 230 _context: &CommandContext, 231 ) -> Result<ChatOpResult, ChatOpError> { 232 if args.is_empty() { 233 return Err(ChatOpError::MissingArguments( 234 "Please specify a package name".to_string(), 235 )); 236 } 237 238 let package_name = &args[0]; 239 240 // Try using npm view command 241 match Command::new("npm") 242 .args(&["view", package_name, "--json"]) 243 .output() 244 { 245 Ok(output) => { 246 if output.status.success() { 247 let result = String::from_utf8_lossy(&output.stdout); 248 if result.contains("\"name\"") { 249 let info = vec![ 250 format!("📦 **NPM Package: {}**", package_name), 251 format!( 252 "🔗 npmjs.com: https://www.npmjs.com/package/{}", 253 package_name 254 ), 255 format!("📋 Install: npm install {}", package_name), 256 format!("📋 Or: yarn add {}", package_name), 257 ]; 258 Ok(ChatOpResult::Block(info)) 259 } else { 260 Ok(ChatOpResult::Message(format!( 261 "📦 Package '{}' not found on NPM", 262 package_name 263 ))) 264 } 265 } else { 266 Ok(ChatOpResult::Message(format!( 267 "📦 Failed to fetch info for package '{}'", 268 package_name 269 ))) 270 } 271 } 272 Err(_) => Ok(ChatOpResult::Message(format!( 273 "📦 Check package manually: https://www.npmjs.com/package/{}", 274 package_name 275 ))), 276 } 277 } 278 } 279 280 /// Python PyPI package information 281 pub struct PipCommand; 282 283 impl ChatCommand for PipCommand { 284 fn name(&self) -> &'static str { 285 "pip" 286 } 287 fn description(&self) -> &'static str { 288 "Get Python package information from PyPI" 289 } 290 fn usage(&self) -> &'static str { 291 "/pip <package_name>" 292 } 293 fn aliases(&self) -> Vec<&'static str> { 294 vec!["pypi"] 295 } 296 297 fn execute( 298 &self, 299 args: Vec<String>, 300 _context: &CommandContext, 301 ) -> Result<ChatOpResult, ChatOpError> { 302 if args.is_empty() { 303 return Err(ChatOpError::MissingArguments( 304 "Please specify a package name".to_string(), 305 )); 306 } 307 308 let package_name = &args[0]; 309 310 // Try to fetch from PyPI API 311 match Command::new("curl") 312 .args(&[ 313 "-s", 314 &format!("https://pypi.org/pypi/{}/json", package_name), 315 ]) 316 .output() 317 { 318 Ok(output) => { 319 if output.status.success() { 320 let result = String::from_utf8_lossy(&output.stdout); 321 if result.contains("\"info\"") && !result.contains("\"message\": \"Not Found\"") 322 { 323 let info = vec![ 324 format!("🐍 **Python Package: {}**", package_name), 325 format!("🔗 PyPI: https://pypi.org/project/{}/", package_name), 326 format!("📋 Install: pip install {}", package_name), 327 format!("📋 Or: python -m pip install {}", package_name), 328 ]; 329 Ok(ChatOpResult::Block(info)) 330 } else { 331 Ok(ChatOpResult::Message(format!( 332 "🐍 Package '{}' not found on PyPI", 333 package_name 334 ))) 335 } 336 } else { 337 Ok(ChatOpResult::Message(format!( 338 "🐍 Failed to fetch info for package '{}'", 339 package_name 340 ))) 341 } 342 } 343 Err(_) => Ok(ChatOpResult::Message(format!( 344 "🐍 Check package manually: https://pypi.org/project/{}/", 345 package_name 346 ))), 347 } 348 } 349 }