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