miyabi_cli/commands/
agent.rs

1//! Agent command - Run agents
2
3use crate::error::{CliError, Result};
4use colored::Colorize;
5use miyabi_agents::business::{
6    AIEntrepreneurAgent, AnalyticsAgent, CRMAgent, ContentCreationAgent, FunnelDesignAgent,
7    MarketResearchAgent, MarketingAgent, PersonaAgent, ProductConceptAgent, ProductDesignAgent,
8    SNSStrategyAgent, SalesAgent, SelfAnalysisAgent, YouTubeAgent,
9};
10use miyabi_agents::codegen::CodeGenAgent;
11use miyabi_agents::deployment::DeploymentAgent;
12use miyabi_agents::hooks::{AuditLogHook, EnvironmentCheckHook, HookedAgent, MetricsHook};
13use miyabi_agents::issue::IssueAgent;
14use miyabi_agents::pr::PRAgent;
15use miyabi_agents::review::ReviewAgent;
16use miyabi_agents::BaseAgent;
17use miyabi_agents::CoordinatorAgentWithLLM;
18use miyabi_core::git_utils::{find_git_root, get_current_branch};
19use miyabi_types::task::TaskType;
20use miyabi_types::{AgentConfig, AgentType, Task};
21use std::collections::HashMap;
22use std::path::PathBuf;
23
24pub struct AgentCommand {
25    pub agent_type: String,
26    pub issue: Option<u64>,
27}
28
29impl AgentCommand {
30    pub fn new(agent_type: String, issue: Option<u64>) -> Self {
31        Self { agent_type, issue }
32    }
33
34    pub async fn execute(&self) -> Result<()> {
35        println!(
36            "{}",
37            format!("🤖 Running {} agent...", self.agent_type)
38                .cyan()
39                .bold()
40        );
41
42        // Parse agent type
43        let agent_type = self.parse_agent_type()?;
44
45        // Load configuration
46        let config = self.load_config()?;
47
48        // Create and execute agent
49        match agent_type {
50            // Coding Agents
51            AgentType::CoordinatorAgent => {
52                self.run_coordinator_agent(config).await?;
53            }
54            AgentType::CodeGenAgent => {
55                self.run_codegen_agent(config).await?;
56            }
57            AgentType::ReviewAgent => {
58                self.run_review_agent(config).await?;
59            }
60            AgentType::IssueAgent => {
61                self.run_issue_agent(config).await?;
62            }
63            AgentType::PRAgent => {
64                self.run_pr_agent(config).await?;
65            }
66            AgentType::DeploymentAgent => {
67                self.run_deployment_agent(config).await?;
68            }
69
70            // Business Agents - Strategy & Planning
71            AgentType::AIEntrepreneurAgent => {
72                self.run_ai_entrepreneur_agent(config).await?;
73            }
74            AgentType::ProductConceptAgent => {
75                self.run_product_concept_agent(config).await?;
76            }
77            AgentType::ProductDesignAgent => {
78                self.run_product_design_agent(config).await?;
79            }
80            AgentType::FunnelDesignAgent => {
81                self.run_funnel_design_agent(config).await?;
82            }
83            AgentType::PersonaAgent => {
84                self.run_persona_agent(config).await?;
85            }
86            AgentType::SelfAnalysisAgent => {
87                self.run_self_analysis_agent(config).await?;
88            }
89
90            // Business Agents - Marketing & Content
91            AgentType::MarketResearchAgent => {
92                self.run_market_research_agent(config).await?;
93            }
94            AgentType::MarketingAgent => {
95                self.run_marketing_agent(config).await?;
96            }
97            AgentType::ContentCreationAgent => {
98                self.run_content_creation_agent(config).await?;
99            }
100            AgentType::SNSStrategyAgent => {
101                self.run_sns_strategy_agent(config).await?;
102            }
103            AgentType::YouTubeAgent => {
104                self.run_youtube_agent(config).await?;
105            }
106
107            // Business Agents - Sales & Customer Management
108            AgentType::SalesAgent => {
109                self.run_sales_agent(config).await?;
110            }
111            AgentType::CRMAgent => {
112                self.run_crm_agent(config).await?;
113            }
114            AgentType::AnalyticsAgent => {
115                self.run_analytics_agent(config).await?;
116            }
117
118            _ => {
119                println!(
120                    "{}",
121                    format!("Agent type {:?} not yet implemented", agent_type).yellow()
122                );
123            }
124        }
125
126        println!();
127        println!("{}", "✅ Agent completed successfully!".green().bold());
128
129        Ok(())
130    }
131
132    pub fn parse_agent_type(&self) -> Result<AgentType> {
133        match self.agent_type.to_lowercase().as_str() {
134            // Coding Agents
135            "coordinator" => Ok(AgentType::CoordinatorAgent),
136            "codegen" | "code-gen" => Ok(AgentType::CodeGenAgent),
137            "review" => Ok(AgentType::ReviewAgent),
138            "issue" => Ok(AgentType::IssueAgent),
139            "pr" => Ok(AgentType::PRAgent),
140            "deployment" | "deploy" => Ok(AgentType::DeploymentAgent),
141
142            // Business Agents - Strategy & Planning
143            "ai-entrepreneur" | "entrepreneur" => Ok(AgentType::AIEntrepreneurAgent),
144            "product-concept" | "concept" => Ok(AgentType::ProductConceptAgent),
145            "product-design" | "design" => Ok(AgentType::ProductDesignAgent),
146            "funnel-design" | "funnel" => Ok(AgentType::FunnelDesignAgent),
147            "persona" => Ok(AgentType::PersonaAgent),
148            "self-analysis" | "analysis" => Ok(AgentType::SelfAnalysisAgent),
149
150            // Business Agents - Marketing & Content
151            "market-research" | "research" => Ok(AgentType::MarketResearchAgent),
152            "marketing" => Ok(AgentType::MarketingAgent),
153            "content-creation" | "content" => Ok(AgentType::ContentCreationAgent),
154            "sns-strategy" | "sns" => Ok(AgentType::SNSStrategyAgent),
155            "youtube" => Ok(AgentType::YouTubeAgent),
156
157            // Business Agents - Sales & Customer Management
158            "sales" => Ok(AgentType::SalesAgent),
159            "crm" => Ok(AgentType::CRMAgent),
160            "analytics" => Ok(AgentType::AnalyticsAgent),
161
162            _ => Err(CliError::InvalidAgentType(self.agent_type.clone())),
163        }
164    }
165
166    fn load_config(&self) -> Result<AgentConfig> {
167        // Get GitHub token with auto-detection from multiple sources
168        let github_token = self.get_github_token()?;
169
170        // Get device identifier (optional)
171        let device_identifier = std::env::var("DEVICE_IDENTIFIER")
172            .unwrap_or_else(|_| hostname::get().unwrap().to_string_lossy().to_string());
173
174        // Parse repository owner and name from git remote
175        let (repo_owner, repo_name) = self.parse_git_remote()?;
176
177        // Load from .miyabi.yml or use defaults
178        Ok(AgentConfig {
179            device_identifier,
180            github_token,
181            repo_owner: Some(repo_owner),
182            repo_name: Some(repo_name),
183            use_task_tool: false,
184            use_worktree: true,
185            worktree_base_path: Some(".worktrees".to_string()),
186            log_directory: "./logs".to_string(),
187            report_directory: "./reports".to_string(),
188            tech_lead_github_username: None,
189            ciso_github_username: None,
190            po_github_username: None,
191            firebase_production_project: None,
192            firebase_staging_project: None,
193            production_url: None,
194            staging_url: None,
195        })
196    }
197
198    fn git_root(&self) -> Result<PathBuf> {
199        find_git_root(None).map_err(CliError::GitConfig)
200    }
201
202    /// Get GitHub token with auto-detection from multiple sources
203    ///
204    /// Tries the following sources in order:
205    /// 1. GITHUB_TOKEN environment variable
206    /// 2. gh CLI (`gh auth token`)
207    /// 3. Error with helpful instructions
208    ///
209    /// # Returns
210    /// * `Ok(String)` - GitHub token
211    /// * `Err(CliError)` - Token not found with helpful error message
212    fn get_github_token(&self) -> Result<String> {
213        // 1. Try environment variable first
214        if let Ok(token) = std::env::var("GITHUB_TOKEN") {
215            if !token.trim().is_empty() {
216                return Ok(token.trim().to_string());
217            }
218        }
219
220        // 2. Try gh CLI
221        if let Ok(output) = std::process::Command::new("gh")
222            .args(["auth", "token"])
223            .output()
224        {
225            if output.status.success() {
226                let token = String::from_utf8_lossy(&output.stdout).trim().to_string();
227                if !token.is_empty()
228                    && (token.starts_with("ghp_")
229                        || token.starts_with("gho_")
230                        || token.starts_with("ghu_")
231                        || token.starts_with("ghs_")
232                        || token.starts_with("ghr_"))
233                {
234                    return Ok(token);
235                }
236            }
237        }
238
239        // 3. Token not found - provide helpful error
240        Err(CliError::GitConfig(
241            "GitHub token not found. Please set up authentication:\n\n\
242             Option 1: Set environment variable\n\
243             export GITHUB_TOKEN=ghp_xxx\n\n\
244             Option 2: Authenticate with gh CLI\n\
245             gh auth login\n\n\
246             Option 3: Add to .env file (uncomment the GITHUB_TOKEN line)\n\
247             GITHUB_TOKEN=ghp_xxx"
248                .to_string(),
249        ))
250    }
251
252    /// Parse repository owner and name from git remote URL
253    ///
254    /// Supports formats:
255    /// - https://github.com/owner/repo
256    /// - https://github.com/owner/repo.git
257    /// - git@github.com:owner/repo.git
258    fn parse_git_remote(&self) -> Result<(String, String)> {
259        // Run git remote get-url origin
260        let output = std::process::Command::new("git")
261            .args(["remote", "get-url", "origin"])
262            .output()
263            .map_err(|e| CliError::GitConfig(format!("Failed to run git command: {}", e)))?;
264
265        if !output.status.success() {
266            return Err(CliError::GitConfig(
267                "Failed to get git remote URL. Not a git repository?".to_string(),
268            ));
269        }
270
271        let remote_url = String::from_utf8_lossy(&output.stdout).trim().to_string();
272
273        // Parse HTTPS format: https://github.com/owner/repo(.git)?
274        if remote_url.starts_with("http") && remote_url.contains("github.com/") {
275            let parts: Vec<&str> = remote_url
276                .split("github.com/")
277                .nth(1)
278                .ok_or_else(|| CliError::GitConfig("Invalid GitHub URL".to_string()))?
279                .trim_end_matches(".git")
280                .split('/')
281                .collect();
282
283            if parts.len() >= 2 {
284                return Ok((parts[0].to_string(), parts[1].to_string()));
285            }
286        }
287
288        // Parse SSH format: git@github.com:owner/repo.git
289        if remote_url.starts_with("git@github.com:") {
290            let repo_part = remote_url
291                .strip_prefix("git@github.com:")
292                .ok_or_else(|| CliError::GitConfig("Invalid SSH URL".to_string()))?
293                .trim_end_matches(".git");
294
295            let parts: Vec<&str> = repo_part.split('/').collect();
296            if parts.len() >= 2 {
297                return Ok((parts[0].to_string(), parts[1].to_string()));
298            }
299        }
300
301        Err(CliError::GitConfig(format!(
302            "Could not parse GitHub owner/repo from remote URL: {}",
303            remote_url
304        )))
305    }
306
307    /// Register standard lifecycle hooks for agents
308    ///
309    /// Registers three standard hooks:
310    /// - MetricsHook: Execution metrics collection
311    /// - EnvironmentCheckHook: GITHUB_TOKEN validation
312    /// - AuditLogHook: Execution logging to .ai/logs/{date}.md
313    ///
314    /// # Example
315    /// ```ignore
316    /// let mut agent = HookedAgent::new(CodeGenAgent::new(config.clone()));
317    /// self.register_standard_hooks(&mut agent, &config);
318    /// ```
319    fn register_standard_hooks<A: BaseAgent>(&self, agent: &mut HookedAgent<A>, config: &AgentConfig) {
320        agent.register_hook(MetricsHook::new());
321        agent.register_hook(EnvironmentCheckHook::new(["GITHUB_TOKEN"]));
322        agent.register_hook(AuditLogHook::new(config.log_directory.clone()));
323    }
324
325    async fn run_coordinator_agent(&self, config: AgentConfig) -> Result<()> {
326        let issue_number = self.issue.ok_or(CliError::MissingIssueNumber)?;
327
328        println!("  Issue: #{}", issue_number);
329        println!("  Type: CoordinatorAgent with LLM (Task decomposition & DAG)");
330        println!();
331
332        // Create agent with LLM integration and lifecycle hooks
333        let mut agent = HookedAgent::new(CoordinatorAgentWithLLM::new(config.clone()));
334        self.register_standard_hooks(&mut agent, &config);
335
336        // Create task for coordinator
337        let task = Task {
338            id: format!("coordinator-issue-{}", issue_number),
339            title: format!("Coordinate Issue #{}", issue_number),
340            description: format!("Decompose Issue #{} into executable tasks", issue_number),
341            task_type: miyabi_types::task::TaskType::Feature,
342            priority: 1,
343            severity: None,
344            impact: None,
345            assigned_agent: Some(AgentType::CoordinatorAgent),
346            dependencies: vec![],
347            estimated_duration: Some(5),
348            status: None,
349            start_time: None,
350            end_time: None,
351            metadata: Some(HashMap::from([(
352                "issue_number".to_string(),
353                serde_json::json!(issue_number),
354            )])),
355        };
356
357        // Execute agent
358        println!("{}", "  Executing...".dimmed());
359        let result = agent.execute(&task).await?;
360
361        // Display results
362        println!();
363        println!("  Results:");
364        println!("    Status: {:?}", result.status);
365
366        if let Some(metrics) = result.metrics {
367            println!("    Duration: {}ms", metrics.duration_ms);
368        }
369
370        if let Some(data) = result.data {
371            println!("    Data: {}", serde_json::to_string_pretty(&data)?);
372        }
373
374        Ok(())
375    }
376
377    async fn run_codegen_agent(&self, config: AgentConfig) -> Result<()> {
378        let issue_number = self.issue.ok_or(CliError::MissingIssueNumber)?;
379
380        println!("  Issue: #{}", issue_number);
381        println!("  Type: CodeGenAgent (Code generation)");
382        println!();
383
384        // Create agent with lifecycle hooks
385        let mut agent = HookedAgent::new(CodeGenAgent::new(config.clone()));
386        self.register_standard_hooks(&mut agent, &config);
387
388        // Create task for codegen
389        let task = Task {
390            id: format!("codegen-issue-{}", issue_number),
391            title: format!("Generate code for Issue #{}", issue_number),
392            description: format!("Implement solution for Issue #{}", issue_number),
393            task_type: miyabi_types::task::TaskType::Feature,
394            priority: 1,
395            severity: None,
396            impact: None,
397            assigned_agent: Some(AgentType::CodeGenAgent),
398            dependencies: vec![],
399            estimated_duration: Some(30),
400            status: None,
401            start_time: None,
402            end_time: None,
403            metadata: Some(HashMap::from([(
404                "issue_number".to_string(),
405                serde_json::json!(issue_number),
406            )])),
407        };
408
409        // Execute agent
410        println!("{}", "  Executing...".dimmed());
411        let result = agent.execute(&task).await?;
412
413        // Display results
414        println!();
415        println!("  Results:");
416        println!("    Status: {:?}", result.status);
417
418        if let Some(metrics) = result.metrics {
419            println!("    Duration: {}ms", metrics.duration_ms);
420            if let Some(lines_changed) = metrics.lines_changed {
421                println!("    Lines changed: {}", lines_changed);
422            }
423            if let Some(tests_added) = metrics.tests_added {
424                println!("    Tests added: {}", tests_added);
425            }
426        }
427
428        Ok(())
429    }
430
431    async fn run_review_agent(&self, config: AgentConfig) -> Result<()> {
432        let issue_number = self.issue.ok_or(CliError::MissingIssueNumber)?;
433
434        println!("  Issue: #{}", issue_number);
435        println!("  Type: ReviewAgent (Code quality review)");
436        println!();
437
438        let mut agent = HookedAgent::new(ReviewAgent::new(config.clone()));
439        self.register_standard_hooks(&mut agent, &config);
440
441        let task = Task {
442            id: format!("review-issue-{}", issue_number),
443            title: format!("Review implementation for Issue #{}", issue_number),
444            description: format!(
445                "Run lint, tests, security checks for Issue #{}",
446                issue_number
447            ),
448            task_type: TaskType::Refactor,
449            priority: 1,
450            severity: None,
451            impact: None,
452            assigned_agent: Some(AgentType::ReviewAgent),
453            dependencies: vec![],
454            estimated_duration: Some(15),
455            status: None,
456            start_time: None,
457            end_time: None,
458            metadata: Some(HashMap::from([(
459                "issue_number".to_string(),
460                serde_json::json!(issue_number),
461            )])),
462        };
463
464        println!("{}", "  Executing...".dimmed());
465        let result = agent.execute(&task).await?;
466
467        println!();
468        println!("  Results:");
469        println!("    Status: {:?}", result.status);
470
471        if let Some(ref metrics) = result.metrics {
472            println!("    Duration: {}ms", metrics.duration_ms);
473            if let Some(score) = metrics.quality_score {
474                println!("    Quality Score: {}", score);
475            }
476        }
477
478        if let Some(ref data) = result.data {
479            println!("    Data: {}", serde_json::to_string_pretty(data)?);
480        }
481
482        Ok(())
483    }
484
485    async fn run_issue_agent(&self, config: AgentConfig) -> Result<()> {
486        let issue_number = self.issue.ok_or(CliError::MissingIssueNumber)?;
487
488        println!("  Issue: #{}", issue_number);
489        println!("  Type: IssueAgent (Issue analysis & labeling)");
490        println!();
491
492        // Create agent with lifecycle hooks
493        let mut agent = HookedAgent::new(IssueAgent::new(config.clone()));
494        self.register_standard_hooks(&mut agent, &config);
495
496        // Create task for issue analysis
497        let task = Task {
498            id: format!("issue-analysis-{}", issue_number),
499            title: format!("Analyze Issue #{}", issue_number),
500            description: format!("Classify Issue #{} and apply labels", issue_number),
501            task_type: miyabi_types::task::TaskType::Feature,
502            priority: 1,
503            severity: None,
504            impact: None,
505            assigned_agent: Some(AgentType::IssueAgent),
506            dependencies: vec![],
507            estimated_duration: Some(5),
508            status: None,
509            start_time: None,
510            end_time: None,
511            metadata: Some(HashMap::from([(
512                "issue_number".to_string(),
513                serde_json::json!(issue_number),
514            )])),
515        };
516
517        // Execute agent
518        println!("{}", "  Executing...".dimmed());
519        let result = agent.execute(&task).await?;
520
521        // Display results
522        println!();
523        println!("  Results:");
524        println!("    Status: {:?}", result.status);
525
526        if let Some(metrics) = result.metrics {
527            println!("    Duration: {}ms", metrics.duration_ms);
528        }
529
530        if let Some(data) = result.data {
531            // Try to parse as IssueAnalysis
532            if let Ok(analysis) = serde_json::from_value::<miyabi_types::IssueAnalysis>(data) {
533                println!("  Analysis:");
534                println!("    Issue Type: {:?}", analysis.issue_type);
535                println!("    Severity: {:?}", analysis.severity);
536                println!("    Impact: {:?}", analysis.impact);
537                println!("    Assigned Agent: {:?}", analysis.assigned_agent);
538                println!(
539                    "    Estimated Duration: {} minutes",
540                    analysis.estimated_duration
541                );
542                println!("    Dependencies: {}", analysis.dependencies.join(", "));
543                println!("    Applied Labels: {}", analysis.labels.join(", "));
544            }
545        }
546
547        Ok(())
548    }
549
550    async fn run_pr_agent(&self, config: AgentConfig) -> Result<()> {
551        let issue_number = self.issue.ok_or(CliError::MissingIssueNumber)?;
552
553        println!("  Issue: #{}", issue_number);
554        println!("  Type: PRAgent (Pull Request creation)");
555        println!();
556
557        let repo_root = self.git_root()?;
558        let branch =
559            get_current_branch(&repo_root).map_err(|e| CliError::GitConfig(e.to_string()))?;
560        let base_branch =
561            std::env::var("MIYABI_BASE_BRANCH").unwrap_or_else(|_| "main".to_string());
562
563        let mut metadata = HashMap::new();
564        metadata.insert("issueNumber".to_string(), serde_json::json!(issue_number));
565        metadata.insert("branch".to_string(), serde_json::json!(branch.clone()));
566        metadata.insert(
567            "baseBranch".to_string(),
568            serde_json::json!(base_branch.clone()),
569        );
570
571        let mut agent = HookedAgent::new(PRAgent::new(config.clone()));
572        self.register_standard_hooks(&mut agent, &config);
573
574        let task = Task {
575            id: format!("pr-issue-{}", issue_number),
576            title: format!("Create PR for Issue #{}", issue_number),
577            description: format!("Generate pull request for Issue #{}", issue_number),
578            task_type: TaskType::Feature,
579            priority: 1,
580            severity: None,
581            impact: None,
582            assigned_agent: Some(AgentType::PRAgent),
583            dependencies: vec![],
584            estimated_duration: Some(5),
585            status: None,
586            start_time: None,
587            end_time: None,
588            metadata: Some(metadata),
589        };
590
591        println!("{}", "  Executing...".dimmed());
592        let result = agent.execute(&task).await?;
593
594        println!();
595        println!("  Results:");
596        println!("    Status: {:?}", result.status);
597
598        if let Some(ref data) = result.data {
599            println!("    Data: {}", serde_json::to_string_pretty(data)?);
600        }
601
602        println!("    Branch: {}", branch);
603        println!("    Base Branch: {}", base_branch);
604
605        Ok(())
606    }
607
608    async fn run_deployment_agent(&self, config: AgentConfig) -> Result<()> {
609        let issue_number = self.issue.ok_or(CliError::MissingIssueNumber)?;
610
611        println!("  Issue: #{}", issue_number);
612        println!("  Type: DeploymentAgent (Build/Test/Deploy)");
613        println!();
614
615        let environment =
616            std::env::var("MIYABI_DEPLOY_ENV").unwrap_or_else(|_| "staging".to_string());
617
618        let health_url = match environment.as_str() {
619            "production" => config
620                .production_url
621                .clone()
622                .or_else(|| std::env::var("MIYABI_PRODUCTION_HEALTH_URL").ok()),
623            _ => config
624                .staging_url
625                .clone()
626                .or_else(|| std::env::var("MIYABI_STAGING_HEALTH_URL").ok()),
627        };
628
629        let mut metadata = HashMap::new();
630        metadata.insert("issue_number".to_string(), serde_json::json!(issue_number));
631        metadata.insert(
632            "environment".to_string(),
633            serde_json::json!(environment.clone()),
634        );
635        if let Some(url) = health_url {
636            metadata.insert("health_url".to_string(), serde_json::json!(url));
637        }
638
639        let mut agent = HookedAgent::new(DeploymentAgent::new(config.clone()));
640        self.register_standard_hooks(&mut agent, &config);
641
642        let task = Task {
643            id: format!("deployment-issue-{}", issue_number),
644            title: format!("Deploy changes for Issue #{}", issue_number),
645            description: format!(
646                "Build, test, and deploy code associated with Issue #{}",
647                issue_number
648            ),
649            task_type: TaskType::Deployment,
650            priority: 1,
651            severity: None,
652            impact: None,
653            assigned_agent: Some(AgentType::DeploymentAgent),
654            dependencies: vec![],
655            estimated_duration: Some(30),
656            status: None,
657            start_time: None,
658            end_time: None,
659            metadata: Some(metadata),
660        };
661
662        println!("{}", "  Executing...".dimmed());
663        let result = agent.execute(&task).await?;
664
665        println!();
666        println!("  Results:");
667        println!("    Status: {:?}", result.status);
668
669        if let Some(ref metrics) = result.metrics {
670            println!("    Duration: {}ms", metrics.duration_ms);
671        }
672
673        if let Some(ref data) = result.data {
674            println!("    Data: {}", serde_json::to_string_pretty(data)?);
675        }
676
677        if let Some(ref escalation) = result.escalation {
678            println!(
679                "    Escalation: {:?} ({})",
680                escalation.target, escalation.reason
681            );
682        }
683
684        Ok(())
685    }
686
687    // Business Agent execution methods
688    async fn run_ai_entrepreneur_agent(&self, config: AgentConfig) -> Result<()> {
689        let issue_number = self.issue.ok_or(CliError::MissingIssueNumber)?;
690
691        println!("  Issue: #{}", issue_number);
692        println!("  Type: AIEntrepreneurAgent (8-phase business plan generation)");
693        println!();
694
695        let agent = AIEntrepreneurAgent::new(config);
696        let task = self.create_business_task(
697            issue_number,
698            "AI Entrepreneur Business Plan",
699            "Generate comprehensive 8-phase business plan",
700        );
701
702        println!("{}", "  Executing...".dimmed());
703        let result = agent.execute(&task).await?;
704        self.display_business_result(result)?;
705
706        Ok(())
707    }
708
709    async fn run_product_concept_agent(&self, config: AgentConfig) -> Result<()> {
710        let issue_number = self.issue.ok_or(CliError::MissingIssueNumber)?;
711
712        println!("  Issue: #{}", issue_number);
713        println!("  Type: ProductConceptAgent (MVP design & product strategy)");
714        println!();
715
716        let agent = ProductConceptAgent::new(config);
717        let task = self.create_business_task(
718            issue_number,
719            "Product Concept Design",
720            "Design MVP and product strategy",
721        );
722
723        println!("{}", "  Executing...".dimmed());
724        let result = agent.execute(&task).await?;
725        self.display_business_result(result)?;
726
727        Ok(())
728    }
729
730    async fn run_product_design_agent(&self, config: AgentConfig) -> Result<()> {
731        let issue_number = self.issue.ok_or(CliError::MissingIssueNumber)?;
732
733        println!("  Issue: #{}", issue_number);
734        println!("  Type: ProductDesignAgent (Comprehensive product design)");
735        println!();
736
737        let agent = ProductDesignAgent::new(config);
738        let task = self.create_business_task(
739            issue_number,
740            "Product Design Specification",
741            "Create comprehensive product design and technical specification",
742        );
743
744        println!("{}", "  Executing...".dimmed());
745        let result = agent.execute(&task).await?;
746        self.display_business_result(result)?;
747
748        Ok(())
749    }
750
751    async fn run_funnel_design_agent(&self, config: AgentConfig) -> Result<()> {
752        let issue_number = self.issue.ok_or(CliError::MissingIssueNumber)?;
753
754        println!("  Issue: #{}", issue_number);
755        println!("  Type: FunnelDesignAgent (AARRR metrics & conversion optimization)");
756        println!();
757
758        let agent = FunnelDesignAgent::new(config);
759        let task = self.create_business_task(
760            issue_number,
761            "Funnel Design Strategy",
762            "Design AARRR metrics and conversion optimization",
763        );
764
765        println!("{}", "  Executing...".dimmed());
766        let result = agent.execute(&task).await?;
767        self.display_business_result(result)?;
768
769        Ok(())
770    }
771
772    async fn run_persona_agent(&self, config: AgentConfig) -> Result<()> {
773        let issue_number = self.issue.ok_or(CliError::MissingIssueNumber)?;
774
775        println!("  Issue: #{}", issue_number);
776        println!("  Type: PersonaAgent (Customer persona & segment analysis)");
777        println!();
778
779        let agent = PersonaAgent::new(config);
780        let task = self.create_business_task(
781            issue_number,
782            "Customer Persona Analysis",
783            "Analyze customer personas and segments",
784        );
785
786        println!("{}", "  Executing...".dimmed());
787        let result = agent.execute(&task).await?;
788        self.display_business_result(result)?;
789
790        Ok(())
791    }
792
793    async fn run_self_analysis_agent(&self, config: AgentConfig) -> Result<()> {
794        let issue_number = self.issue.ok_or(CliError::MissingIssueNumber)?;
795
796        println!("  Issue: #{}", issue_number);
797        println!("  Type: SelfAnalysisAgent (Self-assessment & business strategy)");
798        println!();
799
800        let agent = SelfAnalysisAgent::new(config);
801        let task = self.create_business_task(
802            issue_number,
803            "Self Analysis Strategy",
804            "Perform self-assessment and business strategy formulation",
805        );
806
807        println!("{}", "  Executing...".dimmed());
808        let result = agent.execute(&task).await?;
809        self.display_business_result(result)?;
810
811        Ok(())
812    }
813
814    async fn run_market_research_agent(&self, config: AgentConfig) -> Result<()> {
815        let issue_number = self.issue.ok_or(CliError::MissingIssueNumber)?;
816
817        println!("  Issue: #{}", issue_number);
818        println!("  Type: MarketResearchAgent (Market analysis & competitive landscape)");
819        println!();
820
821        let agent = MarketResearchAgent::new(config);
822        let task = self.create_business_task(
823            issue_number,
824            "Market Research Analysis",
825            "Conduct market research and competitive analysis",
826        );
827
828        println!("{}", "  Executing...".dimmed());
829        let result = agent.execute(&task).await?;
830        self.display_business_result(result)?;
831
832        Ok(())
833    }
834
835    async fn run_marketing_agent(&self, config: AgentConfig) -> Result<()> {
836        let issue_number = self.issue.ok_or(CliError::MissingIssueNumber)?;
837
838        println!("  Issue: #{}", issue_number);
839        println!("  Type: MarketingAgent (Marketing strategy & campaign planning)");
840        println!();
841
842        let agent = MarketingAgent::new(config);
843        let task = self.create_business_task(
844            issue_number,
845            "Marketing Strategy Plan",
846            "Develop comprehensive marketing strategy and campaigns",
847        );
848
849        println!("{}", "  Executing...".dimmed());
850        let result = agent.execute(&task).await?;
851        self.display_business_result(result)?;
852
853        Ok(())
854    }
855
856    async fn run_content_creation_agent(&self, config: AgentConfig) -> Result<()> {
857        let issue_number = self.issue.ok_or(CliError::MissingIssueNumber)?;
858
859        println!("  Issue: #{}", issue_number);
860        println!("  Type: ContentCreationAgent (Content creation & blog article generation)");
861        println!();
862
863        let agent = ContentCreationAgent::new(config);
864        let task = self.create_business_task(
865            issue_number,
866            "Content Creation Strategy",
867            "Create content strategy and blog articles",
868        );
869
870        println!("{}", "  Executing...".dimmed());
871        let result = agent.execute(&task).await?;
872        self.display_business_result(result)?;
873
874        Ok(())
875    }
876
877    async fn run_sns_strategy_agent(&self, config: AgentConfig) -> Result<()> {
878        let issue_number = self.issue.ok_or(CliError::MissingIssueNumber)?;
879
880        println!("  Issue: #{}", issue_number);
881        println!("  Type: SNSStrategyAgent (Social media strategy & community management)");
882        println!();
883
884        let agent = SNSStrategyAgent::new(config);
885        let task = self.create_business_task(
886            issue_number,
887            "SNS Strategy Plan",
888            "Develop social media strategy and community management",
889        );
890
891        println!("{}", "  Executing...".dimmed());
892        let result = agent.execute(&task).await?;
893        self.display_business_result(result)?;
894
895        Ok(())
896    }
897
898    async fn run_youtube_agent(&self, config: AgentConfig) -> Result<()> {
899        let issue_number = self.issue.ok_or(CliError::MissingIssueNumber)?;
900
901        println!("  Issue: #{}", issue_number);
902        println!("  Type: YouTubeAgent (YouTube strategy & video content planning)");
903        println!();
904
905        let agent = YouTubeAgent::new(config);
906        let task = self.create_business_task(
907            issue_number,
908            "YouTube Strategy Plan",
909            "Develop YouTube strategy and video content planning",
910        );
911
912        println!("{}", "  Executing...".dimmed());
913        let result = agent.execute(&task).await?;
914        self.display_business_result(result)?;
915
916        Ok(())
917    }
918
919    async fn run_sales_agent(&self, config: AgentConfig) -> Result<()> {
920        let issue_number = self.issue.ok_or(CliError::MissingIssueNumber)?;
921
922        println!("  Issue: #{}", issue_number);
923        println!("  Type: SalesAgent (Sales strategy & process optimization)");
924        println!();
925
926        let agent = SalesAgent::new(config);
927        let task = self.create_business_task(
928            issue_number,
929            "Sales Strategy Plan",
930            "Develop sales strategy and process optimization",
931        );
932
933        println!("{}", "  Executing...".dimmed());
934        let result = agent.execute(&task).await?;
935        self.display_business_result(result)?;
936
937        Ok(())
938    }
939
940    async fn run_crm_agent(&self, config: AgentConfig) -> Result<()> {
941        let issue_number = self.issue.ok_or(CliError::MissingIssueNumber)?;
942
943        println!("  Issue: #{}", issue_number);
944        println!("  Type: CRMAgent (CRM strategy & customer relationship management)");
945        println!();
946
947        let agent = CRMAgent::new(config);
948        let task = self.create_business_task(
949            issue_number,
950            "CRM Strategy Plan",
951            "Develop CRM strategy and customer relationship management",
952        );
953
954        println!("{}", "  Executing...".dimmed());
955        let result = agent.execute(&task).await?;
956        self.display_business_result(result)?;
957
958        Ok(())
959    }
960
961    async fn run_analytics_agent(&self, config: AgentConfig) -> Result<()> {
962        let issue_number = self.issue.ok_or(CliError::MissingIssueNumber)?;
963
964        println!("  Issue: #{}", issue_number);
965        println!("  Type: AnalyticsAgent (Data analytics & business intelligence)");
966        println!();
967
968        let agent = AnalyticsAgent::new(config);
969        let task = self.create_business_task(
970            issue_number,
971            "Analytics Strategy Plan",
972            "Develop analytics strategy and business intelligence",
973        );
974
975        println!("{}", "  Executing...".dimmed());
976        let result = agent.execute(&task).await?;
977        self.display_business_result(result)?;
978
979        Ok(())
980    }
981
982    // Helper methods
983    fn create_business_task(&self, issue_number: u64, title: &str, description: &str) -> Task {
984        Task {
985            id: format!("business-issue-{}", issue_number),
986            title: title.to_string(),
987            description: description.to_string(),
988            task_type: miyabi_types::task::TaskType::Feature,
989            priority: 1,
990            severity: None,
991            impact: None,
992            assigned_agent: None,
993            dependencies: vec![],
994            estimated_duration: Some(60), // 1 hour for business tasks
995            status: None,
996            start_time: None,
997            end_time: None,
998            metadata: Some(HashMap::from([(
999                "issue_number".to_string(),
1000                serde_json::json!(issue_number),
1001            )])),
1002        }
1003    }
1004
1005    fn display_business_result(&self, result: miyabi_types::AgentResult) -> Result<()> {
1006        println!();
1007        println!("  Results:");
1008        println!("    Status: {:?}", result.status);
1009
1010        if let Some(metrics) = result.metrics {
1011            println!("    Duration: {}ms", metrics.duration_ms);
1012            if let Some(quality_score) = metrics.quality_score {
1013                println!("    Quality Score: {}/100", quality_score);
1014            }
1015        }
1016
1017        if let Some(data) = result.data {
1018            println!(
1019                "    Summary: {}",
1020                data.get("summary").unwrap_or(&serde_json::Value::String(
1021                    "No summary available".to_string()
1022                ))
1023            );
1024        }
1025
1026        Ok(())
1027    }
1028}
1029
1030#[cfg(test)]
1031mod tests {
1032    use super::*;
1033
1034    #[test]
1035    fn test_parse_agent_type() {
1036        let cmd = AgentCommand::new("coordinator".to_string(), None);
1037        assert!(matches!(
1038            cmd.parse_agent_type().unwrap(),
1039            AgentType::CoordinatorAgent
1040        ));
1041
1042        let cmd = AgentCommand::new("codegen".to_string(), None);
1043        assert!(matches!(
1044            cmd.parse_agent_type().unwrap(),
1045            AgentType::CodeGenAgent
1046        ));
1047
1048        let cmd = AgentCommand::new("code-gen".to_string(), None);
1049        assert!(matches!(
1050            cmd.parse_agent_type().unwrap(),
1051            AgentType::CodeGenAgent
1052        ));
1053
1054        let cmd = AgentCommand::new("invalid".to_string(), None);
1055        assert!(cmd.parse_agent_type().is_err());
1056
1057        // Test Business Agent types
1058        let cmd = AgentCommand::new("ai-entrepreneur".to_string(), None);
1059        assert!(matches!(
1060            cmd.parse_agent_type().unwrap(),
1061            AgentType::AIEntrepreneurAgent
1062        ));
1063
1064        let cmd = AgentCommand::new("entrepreneur".to_string(), None);
1065        assert!(matches!(
1066            cmd.parse_agent_type().unwrap(),
1067            AgentType::AIEntrepreneurAgent
1068        ));
1069
1070        let cmd = AgentCommand::new("marketing".to_string(), None);
1071        assert!(matches!(
1072            cmd.parse_agent_type().unwrap(),
1073            AgentType::MarketingAgent
1074        ));
1075
1076        let cmd = AgentCommand::new("analytics".to_string(), None);
1077        assert!(matches!(
1078            cmd.parse_agent_type().unwrap(),
1079            AgentType::AnalyticsAgent
1080        ));
1081    }
1082
1083    #[test]
1084    fn test_agent_command_creation() {
1085        let cmd = AgentCommand::new("coordinator".to_string(), Some(123));
1086        assert_eq!(cmd.agent_type, "coordinator");
1087        assert_eq!(cmd.issue, Some(123));
1088
1089        let cmd = AgentCommand::new("codegen".to_string(), None);
1090        assert_eq!(cmd.agent_type, "codegen");
1091        assert_eq!(cmd.issue, None);
1092    }
1093}