Skip to main content

skm_cli/commands/
select.rs

1//! Select command: interactive skill selection for debugging.
2
3use clap::Args;
4use std::path::PathBuf;
5
6use skm_core::SkillRegistry;
7use skm_select::{CascadeSelector, SelectionContext, TriggerStrategy};
8
9#[derive(Args)]
10pub struct SelectArgs {
11    /// Query to select skill for
12    query: String,
13
14    /// Skill directories
15    #[arg(short, long, default_value = ".")]
16    skills: Vec<PathBuf>,
17
18    /// Top K results to return
19    #[arg(short = 'k', long, default_value = "5")]
20    top_k: usize,
21
22    /// Show detailed breakdown
23    #[arg(long)]
24    verbose: bool,
25
26    /// Output format
27    #[arg(long, default_value = "text")]
28    format: String,
29}
30
31pub async fn select(args: SelectArgs) -> anyhow::Result<()> {
32    let registry = SkillRegistry::new(&args.skills).await?;
33
34    // Build trigger strategy
35    let trigger = TriggerStrategy::from_registry(&registry).await?;
36
37    // Build cascade
38    let selector = CascadeSelector::builder().with_triggers(trigger).build();
39
40    // Select
41    let ctx = SelectionContext::new();
42    let outcome = selector.select(&args.query, &registry, &ctx).await?;
43
44    match args.format.as_str() {
45        "json" => {
46            let results: Vec<serde_json::Value> = outcome
47                .selected
48                .iter()
49                .take(args.top_k)
50                .map(|r| {
51                    serde_json::json!({
52                        "skill": r.skill.as_str(),
53                        "score": r.score,
54                        "confidence": format!("{:?}", r.confidence),
55                        "strategy": r.strategy,
56                        "reasoning": r.reasoning
57                    })
58                })
59                .collect();
60
61            let output = serde_json::json!({
62                "query": args.query,
63                "results": results,
64                "latency_ms": outcome.total_latency.as_millis(),
65                "strategies_used": outcome.strategies_used,
66                "fallback_used": outcome.fallback_used
67            });
68
69            println!("{}", serde_json::to_string_pretty(&output)?);
70        }
71        _ => {
72            println!("Query: {}\n", args.query);
73
74            if outcome.selected.is_empty() {
75                println!("No matching skills found.");
76            } else {
77                println!("Results:");
78                for (i, r) in outcome.selected.iter().take(args.top_k).enumerate() {
79                    println!(
80                        "  {}. {} (score: {:.2}, confidence: {:?}, strategy: {})",
81                        i + 1,
82                        r.skill,
83                        r.score,
84                        r.confidence,
85                        r.strategy
86                    );
87                    if args.verbose {
88                        if let Some(ref reasoning) = r.reasoning {
89                            println!("     reasoning: {}", reasoning);
90                        }
91                    }
92                }
93            }
94
95            println!("\nLatency: {:?}", outcome.total_latency);
96            println!("Strategies: {:?}", outcome.strategies_used);
97        }
98    }
99
100    Ok(())
101}