Skip to main content

systemprompt_cli/commands/plugins/
show.rs

1//! `plugins show` command rendering one extension's jobs, templates, and
2//! schemas.
3//!
4//! Copyright (c) systemprompt.io — Business Source License 1.1.
5//! See <https://systemprompt.io> for licensing details.
6
7use anyhow::{Result, anyhow};
8use clap::Args;
9use systemprompt_extension::{Extension, ExtensionRegistry};
10use systemprompt_loader::ExtensionLoader;
11
12use super::types::{
13    ExtensionDetailOutput, ExtensionSource, JobInfo, LlmProviderInfo, RoleInfo, SchemaInfo,
14    TemplateInfo, ToolInfo,
15};
16use crate::CliConfig;
17use crate::shared::CommandOutput;
18
19#[derive(Debug, Clone, Args)]
20pub struct ShowArgs {
21    #[arg(help = "Extension ID to show")]
22    pub id: String,
23}
24
25pub fn execute(args: &ShowArgs, _config: &CliConfig) -> Result<CommandOutput> {
26    let registry = ExtensionRegistry::discover()?;
27    let needle = args.id.to_lowercase();
28
29    let ext = registry.get(&args.id).or_else(|| {
30        registry
31            .extensions()
32            .iter()
33            .find(|e| e.id().to_lowercase() == needle || e.name().to_lowercase() == needle)
34    });
35
36    let Some(ext) = ext else {
37        return show_manifest(&args.id)
38            .ok_or_else(|| anyhow!("Extension '{}' not found", args.id))?;
39    };
40
41    let output = build_detail_output(ext.as_ref());
42
43    Ok(CommandOutput::card_value(
44        format!("Extension: {}", args.id),
45        &output,
46    ))
47}
48
49fn build_detail_output(ext: &dyn Extension) -> ExtensionDetailOutput {
50    ExtensionDetailOutput {
51        id: systemprompt_identifiers::PluginId::new(ext.id()),
52        name: ext.name().to_owned(),
53        version: ext.version().to_owned(),
54        priority: ext.priority(),
55        source: ExtensionSource::Compiled,
56        dependencies: ext.dependencies().iter().map(|s| (*s).to_owned()).collect(),
57        config_prefix: ext.config_prefix().map(String::from),
58        jobs: job_infos(ext),
59        templates: template_infos(ext),
60        schemas: schema_infos(ext),
61        routes: vec![],
62        tools: ext
63            .tool_providers()
64            .iter()
65            .map(|_provider| ToolInfo {
66                name: "tool_provider".to_owned(),
67            })
68            .collect(),
69        roles: role_infos(ext),
70        llm_providers: ext
71            .llm_providers()
72            .iter()
73            .map(|_provider| LlmProviderInfo {
74                name: "llm_provider".to_owned(),
75            })
76            .collect(),
77        storage_paths: ext
78            .required_storage_paths()
79            .iter()
80            .map(|s| (*s).to_owned())
81            .collect(),
82    }
83}
84
85fn job_infos(ext: &dyn Extension) -> Vec<JobInfo> {
86    ext.jobs()
87        .iter()
88        .map(|job| JobInfo {
89            name: job.name().to_owned(),
90            schedule: job.schedule().to_owned(),
91            enabled: job.enabled(),
92        })
93        .collect()
94}
95
96fn template_infos(ext: &dyn Extension) -> Vec<TemplateInfo> {
97    ext.template_providers()
98        .iter()
99        .flat_map(|provider| {
100            provider
101                .templates()
102                .iter()
103                .map(|t| TemplateInfo {
104                    name: t.name.clone(),
105                    description: t.content_types.join(", "),
106                })
107                .collect::<Vec<_>>()
108        })
109        .collect()
110}
111
112fn schema_infos(ext: &dyn Extension) -> Vec<SchemaInfo> {
113    ext.schemas()
114        .iter()
115        .map(|schema| SchemaInfo {
116            table: schema.table.clone(),
117            source: "inline".to_owned(),
118            required_columns: schema.required_columns.clone(),
119        })
120        .collect()
121}
122
123fn role_infos(ext: &dyn Extension) -> Vec<RoleInfo> {
124    ext.roles()
125        .iter()
126        .map(|role| RoleInfo {
127            name: role.name.clone(),
128            display_name: role.display_name.clone(),
129            description: role.description.clone(),
130            permissions: role.permissions.clone(),
131        })
132        .collect()
133}
134
135fn show_manifest(id: &str) -> Option<Result<CommandOutput>> {
136    let project_root = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::new());
137    let needle = id.to_lowercase();
138    let ext = ExtensionLoader::discover(&project_root)
139        .into_iter()
140        .find(|e| e.manifest.extension.name.to_lowercase() == needle)?;
141
142    let name = ext.manifest.extension.name;
143    let output = ExtensionDetailOutput {
144        id: systemprompt_identifiers::PluginId::new(name.clone()),
145        name: name.clone(),
146        version: "manifest".to_owned(),
147        priority: 100,
148        source: ExtensionSource::Manifest,
149        dependencies: vec![],
150        config_prefix: None,
151        jobs: vec![],
152        templates: vec![],
153        schemas: vec![],
154        routes: vec![],
155        tools: vec![],
156        roles: vec![],
157        llm_providers: vec![],
158        storage_paths: vec![],
159    };
160
161    Some(Ok(CommandOutput::card_value(
162        format!("Extension: {}", name),
163        &output,
164    )))
165}