Skip to main content

lean_ctx/tools/
ctx_plugins.rs

1use crate::core::plugins::{executor::HookPoint, registry::PluginRegistry, PluginManager};
2
3pub fn handle(action: &str, name: Option<&str>) -> String {
4    match action {
5        "list" => handle_list(),
6        "enable" => handle_enable(name),
7        "disable" => handle_disable(name),
8        "info" => handle_info(name),
9        "hooks" => handle_hooks(),
10        _ => format!("Unknown action: {action}. Valid: list, enable, disable, info, hooks"),
11    }
12}
13
14fn handle_list() -> String {
15    let mut registry = PluginRegistry::from_default_dir();
16    let errors = registry.discover();
17
18    let mut out = String::new();
19    if !errors.is_empty() {
20        for err in &errors {
21            out.push_str(&format!("⚠ {}: {}\n", err.path.display(), err.error));
22        }
23        out.push('\n');
24    }
25
26    let plugins = registry.list();
27    if plugins.is_empty() {
28        out.push_str("No plugins installed.\n");
29        out.push_str(&format!(
30            "Plugin directory: {}\n",
31            registry.plugin_dir().display()
32        ));
33        return out;
34    }
35
36    out.push_str(&format!("{} plugin(s):\n\n", plugins.len()));
37    for plugin in &plugins {
38        let status = if plugin.enabled {
39            "enabled"
40        } else {
41            "disabled"
42        };
43        let hooks_count = plugin.manifest.hooks.len();
44        out.push_str(&format!(
45            "• {} v{} [{status}] ({hooks_count} hook{})\n",
46            plugin.manifest.plugin.name,
47            plugin.manifest.plugin.version,
48            if hooks_count == 1 { "" } else { "s" }
49        ));
50        if !plugin.manifest.plugin.description.is_empty() {
51            out.push_str(&format!("  {}\n", plugin.manifest.plugin.description));
52        }
53    }
54    out
55}
56
57fn handle_enable(name: Option<&str>) -> String {
58    let Some(name) = name else {
59        return "Error: 'name' parameter required for enable action".to_string();
60    };
61    PluginManager::init();
62    match PluginManager::with_registry_mut(|reg| reg.enable(name)) {
63        Some(Ok(())) => format!("Enabled plugin: {name}"),
64        Some(Err(e)) => format!("Error: {e}"),
65        None => "Error: plugin registry not initialized".to_string(),
66    }
67}
68
69fn handle_disable(name: Option<&str>) -> String {
70    let Some(name) = name else {
71        return "Error: 'name' parameter required for disable action".to_string();
72    };
73    PluginManager::init();
74    match PluginManager::with_registry_mut(|reg| reg.disable(name)) {
75        Some(Ok(())) => format!("Disabled plugin: {name}"),
76        Some(Err(e)) => format!("Error: {e}"),
77        None => "Error: plugin registry not initialized".to_string(),
78    }
79}
80
81fn handle_info(name: Option<&str>) -> String {
82    let Some(name) = name else {
83        return "Error: 'name' parameter required for info action".to_string();
84    };
85    let mut registry = PluginRegistry::from_default_dir();
86    registry.discover();
87
88    match registry.get(name) {
89        Some(plugin) => {
90            let mut out = String::new();
91            out.push_str(&format!("Plugin: {}\n", plugin.manifest.plugin.name));
92            out.push_str(&format!("Version: {}\n", plugin.manifest.plugin.version));
93            if !plugin.manifest.plugin.description.is_empty() {
94                out.push_str(&format!(
95                    "Description: {}\n",
96                    plugin.manifest.plugin.description
97                ));
98            }
99            if !plugin.manifest.plugin.author.is_empty() {
100                out.push_str(&format!("Author: {}\n", plugin.manifest.plugin.author));
101            }
102            out.push_str(&format!("Enabled: {}\n", plugin.enabled));
103            out.push_str(&format!("Path: {}\n", plugin.path.display()));
104            if !plugin.manifest.hooks.is_empty() {
105                out.push_str("\nHooks:\n");
106                for (hook_name, entry) in &plugin.manifest.hooks {
107                    out.push_str(&format!(
108                        "  {hook_name}: {} (timeout: {}ms)\n",
109                        entry.command, entry.timeout_ms
110                    ));
111                }
112            }
113            out
114        }
115        None => format!("Plugin not found: {name}"),
116    }
117}
118
119fn handle_hooks() -> String {
120    let mut out = String::from("Available hook points:\n\n");
121    for name in HookPoint::all_hook_names() {
122        let desc = match *name {
123            "on_session_start" => "Called when a new session begins",
124            "on_session_end" => "Called when a session ends",
125            "pre_read" => "Called before a file is read (stdin: {\"hook\":\"pre_read\",\"path\":\"...\"})",
126            "post_compress" => "Called after compression (stdin: {\"hook\":\"post_compress\",\"path\":\"...\",\"original_tokens\":N,\"compressed_tokens\":N})",
127            "on_knowledge_update" => "Called when knowledge is updated (stdin: {\"hook\":\"on_knowledge_update\",\"fact_id\":\"...\"})",
128            _ => "",
129        };
130        out.push_str(&format!("• {name}\n  {desc}\n\n"));
131    }
132    out
133}