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