Skip to main content

lowfat_plugin/
manifest.rs

1use serde::Deserialize;
2
3/// Parsed `lowfat.toml` (or `init.toml`) plugin manifest.
4#[derive(Debug, Deserialize)]
5pub struct PluginManifest {
6    pub plugin: PluginMeta,
7    pub runtime: RuntimeConfig,
8    pub hooks: Option<HooksConfig>,
9    pub pipeline: Option<PipelineConfig>,
10}
11
12#[derive(Debug, Deserialize)]
13pub struct PluginMeta {
14    pub name: String,
15    pub version: Option<String>,
16    pub description: Option<String>,
17    pub author: Option<String>,
18    pub category: Option<String>,
19    /// Which commands this plugin intercepts (e.g., ["git"])
20    pub commands: Vec<String>,
21    /// Optional: limit to specific subcommands
22    pub subcommands: Option<Vec<String>>,
23}
24
25#[derive(Debug, Deserialize)]
26pub struct RuntimeConfig {
27    #[serde(rename = "type")]
28    pub runtime_type: RuntimeType,
29    /// Entrypoint relative to plugin dir (e.g., "filter.sh")
30    pub entry: String,
31}
32
33#[derive(Debug, Deserialize, Clone, Copy, PartialEq, Eq)]
34#[serde(rename_all = "lowercase")]
35pub enum RuntimeType {
36    Shell,
37}
38
39#[derive(Debug, Deserialize)]
40pub struct HooksConfig {
41    pub on_install: Option<String>,
42    pub on_update: Option<String>,
43    pub on_remove: Option<String>,
44}
45
46#[derive(Debug, Deserialize)]
47pub struct PipelineConfig {
48    pub pre: Option<Vec<String>>,
49    pub post: Option<Vec<String>>,
50}
51
52impl PluginManifest {
53    pub fn parse(content: &str) -> anyhow::Result<Self> {
54        Ok(toml::from_str(content)?)
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn parse_minimal_manifest() {
64        let toml = r#"
65[plugin]
66name = "git-compact"
67commands = ["git"]
68
69[runtime]
70type = "shell"
71entry = "filter.sh"
72"#;
73        let manifest = PluginManifest::parse(toml).unwrap();
74        assert_eq!(manifest.plugin.name, "git-compact");
75        assert_eq!(manifest.plugin.commands, vec!["git"]);
76        assert_eq!(manifest.runtime.runtime_type, RuntimeType::Shell);
77        assert_eq!(manifest.runtime.entry, "filter.sh");
78    }
79
80    #[test]
81    fn parse_full_manifest() {
82        let toml = r#"
83[plugin]
84name = "git-compact"
85version = "1.2.0"
86description = "Compact git output for LLM contexts"
87author = "zdk"
88category = "git"
89commands = ["git"]
90subcommands = ["status", "diff", "log", "show"]
91
92[runtime]
93type = "shell"
94entry = "filter.sh"
95
96[hooks]
97on_install = "chmod +x filter.sh"
98
99[pipeline]
100pre = ["strip-ansi"]
101post = ["truncate"]
102"#;
103        let manifest = PluginManifest::parse(toml).unwrap();
104        assert_eq!(manifest.plugin.name, "git-compact");
105        assert_eq!(manifest.runtime.runtime_type, RuntimeType::Shell);
106        assert!(manifest.hooks.is_some());
107        assert!(manifest.pipeline.is_some());
108    }
109}