Skip to main content

systemprompt_cli/commands/plugins/
list.rs

1use clap::Args;
2use systemprompt_extension::Extension;
3use systemprompt_loader::ExtensionLoader;
4
5use super::discover_registry;
6use super::types::{CapabilitySummary, ExtensionListOutput, ExtensionSource, ExtensionSummary};
7use crate::CliConfig;
8use crate::shared::CommandOutput;
9
10#[derive(Debug, Clone, Args)]
11pub struct ListArgs {
12    #[arg(long, help = "Filter by extension ID (substring match)")]
13    pub filter: Option<String>,
14
15    #[arg(long, value_parser = ["jobs", "templates", "schemas", "routes", "tools", "roles", "llm", "storage"])]
16    pub capability: Option<String>,
17
18    #[arg(long, value_parser = ["compiled", "manifest", "cli", "mcp", "all"], default_value = "all", help = "Filter by extension type")]
19    pub r#type: String,
20}
21
22pub fn execute(args: &ListArgs, _config: &CliConfig) -> CommandOutput {
23    let mut extensions: Vec<ExtensionSummary> = Vec::new();
24
25    if matches!(args.r#type.as_str(), "all" | "compiled") {
26        extensions.extend(collect_compiled(args));
27    }
28
29    if matches!(args.r#type.as_str(), "all" | "manifest" | "cli" | "mcp") {
30        extensions.extend(collect_manifest(args));
31    }
32
33    extensions.sort_by_key(|e| e.priority);
34
35    let total = extensions.len();
36
37    let output = ExtensionListOutput { extensions, total };
38
39    CommandOutput::table_of(
40        vec![
41            "id",
42            "name",
43            "version",
44            "priority",
45            "source",
46            "capabilities",
47        ],
48        &output.extensions,
49    )
50    .with_title("Extensions")
51}
52
53fn collect_compiled(args: &ListArgs) -> Vec<ExtensionSummary> {
54    let registry = discover_registry();
55    registry
56        .extensions()
57        .iter()
58        .filter(|ext| matches_compiled_filters(ext.as_ref(), args))
59        .map(|ext| compiled_summary(ext.as_ref()))
60        .collect()
61}
62
63fn matches_compiled_filters(ext: &dyn Extension, args: &ListArgs) -> bool {
64    if let Some(ref filter) = args.filter
65        && !ext.id().to_lowercase().contains(&filter.to_lowercase())
66        && !ext.name().to_lowercase().contains(&filter.to_lowercase())
67    {
68        return false;
69    }
70
71    if let Some(ref cap) = args.capability {
72        match cap.as_str() {
73            "jobs" => return ext.has_jobs(),
74            "templates" => return ext.has_template_providers(),
75            "schemas" => return ext.has_schemas(),
76            "routes" => return false,
77            "tools" => return ext.has_tool_providers(),
78            "roles" => return ext.has_roles(),
79            "llm" => return ext.has_llm_providers(),
80            "storage" => return ext.has_storage_paths(),
81            _ => {},
82        }
83    }
84
85    true
86}
87
88fn compiled_summary(ext: &dyn Extension) -> ExtensionSummary {
89    let capabilities = CapabilitySummary {
90        jobs: ext.jobs().len(),
91        templates: ext.template_providers().len(),
92        schemas: ext.schemas().len(),
93        routes: 0,
94        tools: ext.tool_providers().len(),
95        roles: ext.roles().len(),
96        llm_providers: ext.llm_providers().len(),
97        storage_paths: ext.required_storage_paths().len(),
98    };
99
100    ExtensionSummary {
101        id: systemprompt_identifiers::PluginId::new(ext.id()),
102        name: ext.name().to_owned(),
103        version: ext.version().to_owned(),
104        priority: ext.priority(),
105        source: ExtensionSource::Compiled,
106        enabled: true,
107        capabilities,
108    }
109}
110
111fn collect_manifest(args: &ListArgs) -> Vec<ExtensionSummary> {
112    let project_root = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::new());
113    let mut summaries = Vec::new();
114
115    for ext in ExtensionLoader::discover(&project_root) {
116        let type_matches = match args.r#type.as_str() {
117            "cli" => ext.is_cli(),
118            "mcp" => ext.is_mcp(),
119            _ => true,
120        };
121
122        if !type_matches {
123            continue;
124        }
125
126        if let Some(ref filter) = args.filter
127            && !ext
128                .manifest
129                .extension
130                .name
131                .to_lowercase()
132                .contains(&filter.to_lowercase())
133        {
134            continue;
135        }
136
137        summaries.push(ExtensionSummary {
138            id: systemprompt_identifiers::PluginId::new(ext.manifest.extension.name.clone()),
139            name: ext.manifest.extension.name.clone(),
140            version: "manifest".to_owned(),
141            priority: 100,
142            source: ExtensionSource::Manifest,
143            enabled: ext.is_enabled(),
144            capabilities: CapabilitySummary::default(),
145        });
146    }
147
148    summaries
149}