systemprompt_cli/commands/plugins/
list.rs1use 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}