systemprompt_cli/commands/core/plugins/
list.rs1use anyhow::{Context, Result};
7use clap::Args;
8use std::path::Path;
9
10use crate::CliConfig;
11use crate::shared::CommandOutput;
12
13use super::types::{PluginListOutput, PluginSummary};
14use systemprompt_loader::ServicesRootBootstrap;
15
16#[derive(Debug, Clone, Copy, Args)]
17pub struct ListArgs {
18 #[arg(long, help = "Show only enabled plugins")]
19 pub enabled: bool,
20
21 #[arg(long, help = "Show only disabled plugins", conflicts_with = "enabled")]
22 pub disabled: bool,
23}
24
25pub(super) fn execute(args: ListArgs, _config: &CliConfig) -> Result<CommandOutput> {
26 let plugins_path = get_plugins_path()?;
27 let plugins = scan_plugins(&plugins_path)?;
28
29 let filtered: Vec<PluginSummary> = plugins
30 .into_iter()
31 .filter(|p| {
32 if args.enabled {
33 p.enabled
34 } else if args.disabled {
35 !p.enabled
36 } else {
37 true
38 }
39 })
40 .collect();
41
42 let output = PluginListOutput { plugins: filtered };
43
44 Ok(CommandOutput::table_of(
45 vec![
46 "id",
47 "name",
48 "display_name",
49 "enabled",
50 "skill_count",
51 "agent_count",
52 ],
53 &output.plugins,
54 )
55 .with_title("Plugins"))
56}
57
58fn get_plugins_path() -> Result<std::path::PathBuf> {
59 let profile = systemprompt_config::ProfileBootstrap::get().context("Failed to get profile")?;
60 Ok(ServicesRootBootstrap::active_path_or(
61 &profile.paths.services,
62 "plugins",
63 ))
64}
65
66pub fn scan_plugins(plugins_path: &Path) -> Result<Vec<PluginSummary>> {
67 if !plugins_path.exists() {
68 return Ok(Vec::new());
69 }
70
71 let mut plugins = Vec::new();
72
73 for entry in std::fs::read_dir(plugins_path)? {
74 let entry = entry?;
75 let plugin_path = entry.path();
76
77 if !plugin_path.is_dir() {
78 continue;
79 }
80
81 let config_path = plugin_path.join("config.yaml");
82 if !config_path.exists() {
83 continue;
84 }
85
86 match parse_plugin_config(&config_path) {
87 Ok(plugin_file) => {
88 let plugin = &plugin_file.plugin;
89 let mut summary: PluginSummary = plugin.into();
90 summary.skill_count = estimate_component_count(&plugin.skills);
91 summary.agent_count = estimate_component_count(&plugin.agents);
92 plugins.push(summary);
93 },
94 Err(e) => {
95 tracing::warn!(
96 path = %config_path.display(),
97 error = %e,
98 "Failed to parse plugin config"
99 );
100 },
101 }
102 }
103
104 plugins.sort_by(|a, b| a.id.cmp(&b.id));
105 Ok(plugins)
106}
107
108fn parse_plugin_config(config_path: &Path) -> Result<systemprompt_manifest::PluginConfigFile> {
109 let content = std::fs::read_to_string(config_path)
110 .with_context(|| format!("Failed to read {}", config_path.display()))?;
111 let plugin_file: systemprompt_manifest::PluginConfigFile = serde_yaml::from_str(&content)
112 .with_context(|| format!("Failed to parse {}", config_path.display()))?;
113 Ok(plugin_file)
114}
115
116fn estimate_component_count(component: &systemprompt_models::plugin::PluginComponentRef) -> usize {
117 if component.source == systemprompt_models::plugin::ComponentSource::Explicit {
118 component.include.len()
119 } else {
120 0
121 }
122}