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", "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 "storage" => return ext.has_storage_paths(),
85 _ => {},
86 }
87 }
88
89 true
90}
91
92fn compiled_summary(ext: &dyn Extension) -> ExtensionSummary {
93 let capabilities = CapabilitySummary {
94 jobs: ext.jobs().len(),
95 templates: ext.template_providers().len(),
96 schemas: ext.schemas().len(),
97 routes: 0,
98 tools: ext.tool_providers().len(),
99 roles: ext.roles().len(),
100 storage_paths: ext.required_storage_paths().len(),
101 };
102
103 ExtensionSummary {
104 id: systemprompt_identifiers::PluginId::new(ext.id()),
105 name: ext.name().to_owned(),
106 version: ext.version().to_owned(),
107 priority: ext.priority(),
108 source: ExtensionSource::Compiled,
109 enabled: true,
110 capabilities,
111 }
112}
113
114fn collect_manifest(args: &ListArgs) -> Vec<ExtensionSummary> {
115 let project_root = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::new());
116 let mut summaries = Vec::new();
117
118 for ext in ExtensionLoader::discover(&project_root) {
119 let type_matches = match args.r#type.as_str() {
120 "cli" => ext.is_cli(),
121 "mcp" => ext.is_mcp(),
122 _ => true,
123 };
124
125 if !type_matches {
126 continue;
127 }
128
129 if let Some(ref filter) = args.filter
130 && !ext
131 .manifest
132 .extension
133 .name
134 .to_lowercase()
135 .contains(&filter.to_lowercase())
136 {
137 continue;
138 }
139
140 summaries.push(ExtensionSummary {
141 id: systemprompt_identifiers::PluginId::new(ext.manifest.extension.name.clone()),
142 name: ext.manifest.extension.name.clone(),
143 version: "manifest".to_owned(),
144 priority: 100,
145 source: ExtensionSource::Manifest,
146 enabled: ext.is_enabled(),
147 capabilities: CapabilitySummary::default(),
148 });
149 }
150
151 summaries
152}