lean_ctx/cli/
plugin_cmd.rs1use crate::core::plugins::{
2 executor::HookPoint,
3 registry::{default_plugin_dir, PluginRegistry},
4 PluginManager,
5};
6
7pub fn cmd_plugin(args: &[String]) {
8 let action = args.first().map_or("help", String::as_str);
9
10 match action {
11 "list" | "ls" => cmd_list(),
12 "enable" => {
13 let Some(name) = args.get(1).map(String::as_str).filter(|s| !s.is_empty()) else {
14 eprintln!("Usage: lean-ctx plugin enable <name>");
15 std::process::exit(1);
16 };
17 cmd_enable(name);
18 }
19 "disable" => {
20 let Some(name) = args.get(1).map(String::as_str).filter(|s| !s.is_empty()) else {
21 eprintln!("Usage: lean-ctx plugin disable <name>");
22 std::process::exit(1);
23 };
24 cmd_disable(name);
25 }
26 "info" => {
27 let Some(name) = args.get(1).map(String::as_str).filter(|s| !s.is_empty()) else {
28 eprintln!("Usage: lean-ctx plugin info <name>");
29 std::process::exit(1);
30 };
31 cmd_info(name);
32 }
33 "init" => {
34 let Some(name) = args.get(1).map(String::as_str).filter(|s| !s.is_empty()) else {
35 eprintln!("Usage: lean-ctx plugin init <name>");
36 std::process::exit(1);
37 };
38 cmd_init(name);
39 }
40 "hooks" => cmd_hooks(),
41 "help" | "--help" | "-h" => print_help(),
42 _ => {
43 eprintln!("Unknown plugin action: {action}");
44 print_help();
45 std::process::exit(1);
46 }
47 }
48}
49
50fn cmd_list() {
51 let mut registry = PluginRegistry::from_default_dir();
52 let errors = registry.discover();
53 for err in &errors {
54 eprintln!("Warning: {}: {}", err.path.display(), err.error);
55 }
56
57 let plugins = registry.list();
58 if plugins.is_empty() {
59 println!("No plugins installed.");
60 println!("\nPlugin directory: {}", default_plugin_dir().display());
61 println!("Use 'lean-ctx plugin init <name>' to create a plugin template.");
62 return;
63 }
64
65 println!("Installed plugins:\n");
66 for plugin in &plugins {
67 let status = if plugin.enabled { "✓" } else { "✗" };
68 let hooks_count = plugin.manifest.hooks.len();
69 println!(
70 " [{status}] {} v{} ({hooks_count} hook{})",
71 plugin.manifest.plugin.name,
72 plugin.manifest.plugin.version,
73 if hooks_count == 1 { "" } else { "s" }
74 );
75 if !plugin.manifest.plugin.description.is_empty() {
76 println!(" {}", plugin.manifest.plugin.description);
77 }
78 }
79 println!("\nPlugin directory: {}", default_plugin_dir().display());
80}
81
82fn cmd_enable(name: &str) {
83 PluginManager::init();
84 match PluginManager::with_registry_mut(|reg| reg.enable(name)) {
85 Some(Ok(())) => println!("Enabled plugin: {name}"),
86 Some(Err(e)) => {
87 eprintln!("Error: {e}");
88 std::process::exit(1);
89 }
90 None => {
91 eprintln!("Error: plugin registry not initialized");
92 std::process::exit(1);
93 }
94 }
95}
96
97fn cmd_disable(name: &str) {
98 PluginManager::init();
99 match PluginManager::with_registry_mut(|reg| reg.disable(name)) {
100 Some(Ok(())) => println!("Disabled plugin: {name}"),
101 Some(Err(e)) => {
102 eprintln!("Error: {e}");
103 std::process::exit(1);
104 }
105 None => {
106 eprintln!("Error: plugin registry not initialized");
107 std::process::exit(1);
108 }
109 }
110}
111
112fn cmd_info(name: &str) {
113 let mut registry = PluginRegistry::from_default_dir();
114 registry.discover();
115
116 if let Some(plugin) = registry.get(name) {
117 println!("Plugin: {}", plugin.manifest.plugin.name);
118 println!("Version: {}", plugin.manifest.plugin.version);
119 if !plugin.manifest.plugin.description.is_empty() {
120 println!("Description: {}", plugin.manifest.plugin.description);
121 }
122 if !plugin.manifest.plugin.author.is_empty() {
123 println!("Author: {}", plugin.manifest.plugin.author);
124 }
125 println!("Enabled: {}", plugin.enabled);
126 println!("Path: {}", plugin.path.display());
127 if !plugin.manifest.hooks.is_empty() {
128 println!("\nHooks:");
129 for (hook_name, entry) in &plugin.manifest.hooks {
130 println!(
131 " {hook_name}: {} (timeout: {}ms)",
132 entry.command, entry.timeout_ms
133 );
134 }
135 }
136 } else {
137 eprintln!("Plugin not found: {name}");
138 std::process::exit(1);
139 }
140}
141
142fn cmd_init(name: &str) {
143 let dir = default_plugin_dir();
144 match crate::core::plugins::init_plugin_template(name, &dir) {
145 Ok(()) => {
146 println!("Created plugin template: {}", dir.join(name).display());
147 println!("\nNext steps:");
148 println!(" 1. Edit {}/plugin.toml", dir.join(name).display());
149 println!(" 2. Implement your plugin binary");
150 println!(" 3. Run 'lean-ctx plugin list' to verify");
151 }
152 Err(e) => {
153 eprintln!("Error creating plugin template: {e}");
154 std::process::exit(1);
155 }
156 }
157}
158
159fn cmd_hooks() {
160 println!("Available hook points:\n");
161 for name in HookPoint::all_hook_names() {
162 let desc = match *name {
163 "on_session_start" => "Called when a new lean-ctx session begins",
164 "on_session_end" => "Called when a session ends",
165 "pre_read" => "Called before a file is read (receives {path} in stdin)",
166 "post_compress" => {
167 "Called after compression (receives {path, original_tokens, compressed_tokens})"
168 }
169 "on_knowledge_update" => "Called when knowledge is updated (receives {fact_id})",
170 _ => "",
171 };
172 println!(" {name}");
173 println!(" {desc}\n");
174 }
175}
176
177fn print_help() {
178 eprintln!(
179 "lean-ctx plugin — Plugin management\n\
180 \n\
181 USAGE:\n \
182 lean-ctx plugin <action> [args]\n\
183 \n\
184 ACTIONS:\n \
185 list List installed plugins\n \
186 enable <name> Enable a plugin\n \
187 disable <name> Disable a plugin\n \
188 info <name> Show plugin details\n \
189 init <name> Create a plugin template\n \
190 hooks List available hook points\n \
191 help Show this help"
192 );
193}