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