lean_ctx/setup/
helpers.rs1#[allow(clippy::wildcard_imports)]
5use super::*;
6
7pub fn install_skill_files(home: &std::path::Path) -> Vec<(String, bool)> {
8 crate::rules_inject::install_all_skills(home)
9}
10
11pub(crate) fn install_kiro_steering(home: &std::path::Path) {
12 let cwd = std::env::current_dir().unwrap_or_else(|_| home.to_path_buf());
13 let steering_dir = cwd.join(".kiro").join("steering");
14 let steering_file = steering_dir.join("lean-ctx.md");
15
16 if steering_file.exists()
17 && std::fs::read_to_string(&steering_file)
18 .unwrap_or_default()
19 .contains("lean-ctx")
20 {
21 println!(" Kiro steering file already exists at .kiro/steering/lean-ctx.md");
22 return;
23 }
24
25 let _ = std::fs::create_dir_all(&steering_dir);
26 let _ = std::fs::write(&steering_file, crate::hooks::KIRO_STEERING_TEMPLATE);
27 println!(" \x1b[32m✓\x1b[0m Created .kiro/steering/lean-ctx.md (Kiro will now prefer lean-ctx tools)");
28}
29
30pub(crate) fn configure_plan_mode_settings(newly_configured: &[&str], already_configured: &[&str]) {
31 use crate::terminal_ui;
32
33 let all_configured: Vec<&str> = newly_configured
34 .iter()
35 .chain(already_configured.iter())
36 .copied()
37 .collect();
38
39 let has_vscode = all_configured.contains(&"VS Code");
40 let has_claude = all_configured.contains(&"Claude Code");
41
42 if !has_vscode && !has_claude {
43 return;
44 }
45
46 if has_vscode {
47 match crate::core::editor_registry::plan_mode::write_vscode_plan_settings() {
48 Ok(r) if r.action == WriteAction::Already => {
49 terminal_ui::print_status_ok(
50 "VS Code \x1b[2mplan mode already configured\x1b[0m",
51 );
52 }
53 Ok(_) => {
54 terminal_ui::print_status_new(
55 "VS Code \x1b[2mplan mode tools configured\x1b[0m",
56 );
57 }
58 Err(e) => {
59 terminal_ui::print_status_warn(&format!("VS Code plan mode: {e}"));
60 }
61 }
62 }
63
64 if has_claude {
65 match crate::core::editor_registry::plan_mode::write_claude_code_plan_permissions() {
66 Ok(r) if r.action == WriteAction::Already => {
67 terminal_ui::print_status_ok(
68 "Claude Code \x1b[2mplan mode permissions present\x1b[0m",
69 );
70 }
71 Ok(_) => {
72 terminal_ui::print_status_new(
73 "Claude Code \x1b[2mplan mode permissions added\x1b[0m",
74 );
75 }
76 Err(e) => {
77 terminal_ui::print_status_warn(&format!("Claude Code plan mode: {e}"));
78 }
79 }
80 }
81}
82
83pub(crate) fn shorten_path(path: &str, home: &str) -> String {
84 if let Some(stripped) = path.strip_prefix(home) {
85 format!("~{stripped}")
86 } else {
87 path.to_string()
88 }
89}
90
91fn upsert_toml_key(content: &mut String, key: &str, value: &str) {
92 let pattern = format!("{key} = ");
93 if let Some(start) = content.find(&pattern) {
94 let line_end = content[start..]
95 .find('\n')
96 .map_or(content.len(), |p| start + p);
97 content.replace_range(start..line_end, &format!("{key} = \"{value}\""));
98 } else {
99 if !content.is_empty() && !content.ends_with('\n') {
100 content.push('\n');
101 }
102 content.push_str(&format!("{key} = \"{value}\"\n"));
103 }
104}
105
106fn remove_toml_key(content: &mut String, key: &str) {
107 let pattern = format!("{key} = ");
108 if let Some(start) = content.find(&pattern) {
109 let line_end = content[start..]
110 .find('\n')
111 .map_or(content.len(), |p| start + p + 1);
112 content.replace_range(start..line_end, "");
113 }
114}
115
116pub(crate) fn configure_tool_profile() {
117 use crate::terminal_ui;
118 use std::io::Write;
119
120 let cfg = crate::core::config::Config::load();
121 let current = cfg.tool_profile_effective();
122
123 if !matches!(current, crate::core::tool_profiles::ToolProfile::Power)
124 && cfg.tool_profile.is_some()
125 {
126 terminal_ui::print_status_ok(&format!(
127 "Tool profile: {} ({} tools)",
128 current.as_str(),
129 current.tool_count()
130 ));
131 return;
132 }
133
134 let dim = "\x1b[2m";
135 let bold = "\x1b[1m";
136 let cyan = "\x1b[36m";
137 let rst = "\x1b[0m";
138
139 let registry_count = crate::server::registry::tool_count();
140
141 println!(" {dim}Control how many MCP tools your AI agent sees.{rst}");
142 println!(" {dim}Fewer tools = less context overhead, faster agent responses.{rst}");
143 println!();
144 println!(
145 " {cyan}minimal{rst} — 6 tools {dim}(ctx_read, ctx_shell, shell, ctx_search, ctx_tree, ctx_session){rst}"
146 );
147 println!(" {cyan}standard{rst} — 22 tools {dim}(balanced set for most workflows){rst}");
148 println!(
149 " {cyan}power{rst} — {registry_count} tools {dim}(everything, for power users){rst}"
150 );
151 println!();
152 print!(" Tool profile? {bold}[minimal/standard/power]{rst} {dim}(default: standard){rst} ");
153 std::io::stdout().flush().ok();
154
155 let mut profile_input = String::new();
156 let profile_name = if std::io::stdin().read_line(&mut profile_input).is_ok() {
157 let trimmed = profile_input.trim().to_lowercase();
158 match trimmed.as_str() {
159 "minimal" | "min" => "minimal",
160 "power" | "full" | "all" => "power",
161 _ => "standard",
162 }
163 } else {
164 "standard"
165 };
166
167 match crate::core::tool_profiles::set_profile_in_config(profile_name) {
168 Ok(()) => {
169 let profile = crate::core::tool_profiles::ToolProfile::parse(profile_name)
170 .unwrap_or(crate::core::tool_profiles::ToolProfile::Standard);
171 let count = match &profile {
172 crate::core::tool_profiles::ToolProfile::Power => registry_count,
173 other => other.tool_count(),
174 };
175 terminal_ui::print_status_ok(&format!("Tool profile: {profile_name} ({count} tools)"));
176 }
177 Err(e) => {
178 terminal_ui::print_status_warn(&format!("Could not save tool profile: {e}"));
179 }
180 }
181}
182
183pub(crate) fn configure_premium_features(home: &std::path::Path) {
184 use crate::terminal_ui;
185 use std::io::Write;
186
187 let config_dir = crate::core::data_dir::lean_ctx_data_dir()
188 .unwrap_or_else(|_| home.join(".config/lean-ctx"));
189 let _ = std::fs::create_dir_all(&config_dir);
190 let config_path = config_dir.join("config.toml");
191 let mut config_content = std::fs::read_to_string(&config_path).unwrap_or_default();
192
193 let dim = "\x1b[2m";
194 let bold = "\x1b[1m";
195 let cyan = "\x1b[36m";
196 let rst = "\x1b[0m";
197
198 println!("\n {bold}Compression Level{rst} {dim}(controls all token optimization layers){rst}");
200 println!(" {dim}Applies to tool output, agent prompts, and protocol mode.{rst}");
201 println!();
202 println!(" {cyan}off{rst} — No compression (full verbose output)");
203 println!(" {cyan}lite{rst} — Light: concise output, basic terse filtering {dim}(~25% savings){rst}");
204 println!(" {cyan}standard{rst} — Dense output + compact protocol + pattern-aware {dim}(~45% savings){rst}");
205 println!(" {cyan}max{rst} — Expert mode: TDD protocol, all layers active {dim}(~65% savings){rst}");
206 println!();
207 print!(" Compression level? {bold}[off/lite/standard/max]{rst} {dim}(default: off){rst} ");
208 std::io::stdout().flush().ok();
209
210 let mut level_input = String::new();
211 let level = if std::io::stdin().read_line(&mut level_input).is_ok() {
212 match level_input.trim().to_lowercase().as_str() {
213 "lite" => "lite",
214 "standard" | "std" => "standard",
215 "max" => "max",
216 _ => "off",
217 }
218 } else {
219 "off"
220 };
221
222 let effective_level = if level != "off" {
223 upsert_toml_key(&mut config_content, "compression_level", level);
224 remove_toml_key(&mut config_content, "terse_agent");
225 remove_toml_key(&mut config_content, "output_density");
226 terminal_ui::print_status_ok(&format!("Compression: {level}"));
227 crate::core::config::CompressionLevel::from_str_label(level)
228 } else if config_content.contains("compression_level") {
229 upsert_toml_key(&mut config_content, "compression_level", "off");
230 terminal_ui::print_status_ok("Compression: off");
231 Some(crate::core::config::CompressionLevel::Off)
232 } else {
233 terminal_ui::print_status_skip(
234 "Compression: off (change later with: lean-ctx compression <level>)",
235 );
236 Some(crate::core::config::CompressionLevel::Off)
237 };
238
239 if let Some(lvl) = effective_level {
240 let n = crate::core::terse::rules_inject::inject(&lvl);
241 if n > 0 {
242 terminal_ui::print_status_ok(&format!(
243 "Updated {n} rules file(s) with compression prompt"
244 ));
245 }
246 }
247
248 println!(
250 "\n {bold}Tool Result Archive{rst} {dim}(zero-loss: large outputs archived, retrievable via ctx_expand){rst}"
251 );
252 print!(" Enable auto-archive? {bold}[Y/n]{rst} ");
253 std::io::stdout().flush().ok();
254
255 let mut archive_input = String::new();
256 let archive_on = if std::io::stdin().read_line(&mut archive_input).is_ok() {
257 let a = archive_input.trim().to_lowercase();
258 a.is_empty() || a == "y" || a == "yes"
259 } else {
260 true
261 };
262
263 if archive_on && !config_content.contains("[archive]") {
264 if !config_content.is_empty() && !config_content.ends_with('\n') {
265 config_content.push('\n');
266 }
267 config_content.push_str("\n[archive]\nenabled = true\n");
268 terminal_ui::print_status_ok("Tool Result Archive: enabled");
269 } else if !archive_on {
270 terminal_ui::print_status_skip("Archive: off (enable later in config.toml)");
271 }
272
273 let _ = crate::config_io::write_atomic_with_backup(&config_path, &config_content);
274}