Skip to main content

robit_agent/
bootstrap.rs

1//! Bootstrap module — common setup for loading skills and creating tools.
2//!
3//! This module provides reusable functions for frontends (robit-tui, robit-gui, etc.)
4//! to avoid duplicating skill loading and tool creation code.
5
6use std::path::PathBuf;
7use std::sync::Arc;
8
9use robit_ai::config::RobitConfig;
10
11use crate::skill::{load_skills, Skill, SkillRegistry};
12use crate::tool::bash::BashTool;
13use crate::tool::edit::EditTool;
14use crate::tool::find::FindTool;
15use crate::tool::grep::GrepTool;
16use crate::tool::load_skill::LoadSkillTool;
17use crate::tool::ls::LsTool;
18use crate::tool::read::ReadTool;
19use crate::tool::write::WriteTool;
20use crate::tool::ToolRegistry;
21use crate::SkillLoadError;
22
23// ============================================================================
24// BootstrapResult
25// ============================================================================
26
27/// Result of bootstrapping skills and tools.
28pub struct BootstrapResult {
29    /// The skill registry, ready for use.
30    pub skill_registry: Arc<SkillRegistry>,
31    /// The tool registry, ready for use.
32    pub tool_registry: Arc<ToolRegistry>,
33    /// Total skills loaded (before filtering by enabled_skills).
34    pub total_skills_loaded: usize,
35    /// Any errors that occurred during skill loading (non-fatal).
36    pub skill_load_errors: Vec<SkillLoadError>,
37}
38
39// ============================================================================
40// Bootstrap functions
41// ============================================================================
42
43/// Bootstrap both skills and tools in one call.
44///
45/// This is the main entry point for frontends. It:
46/// 1. Loads skills from global and project directories
47/// 2. Filters skills by config.enabled_skills
48/// 3. Creates SkillRegistry
49/// 4. Creates ToolRegistry with all standard tools
50///
51/// Returns a BootstrapResult with both registries and metadata.
52pub fn bootstrap(
53    config: &RobitConfig,
54    working_dir: &PathBuf,
55    base_tool_names: &[&str],
56) -> BootstrapResult {
57    let (skills, skill_load_errors) = load_all_skills(working_dir);
58    let total_skills_loaded = skills.len();
59
60    let filtered_skills = filter_skills_by_config(skills, config);
61
62    let skill_registry = Arc::new(SkillRegistry::new(filtered_skills, base_tool_names));
63    let tool_registry = Arc::new(create_tools_from_config(config, Arc::clone(&skill_registry)));
64
65    BootstrapResult {
66        skill_registry,
67        tool_registry,
68        total_skills_loaded,
69        skill_load_errors,
70    }
71}
72
73/// Load skills from standard locations (global ~/.robit/skills and project .robit/skills).
74///
75/// Returns (loaded_skills, load_errors).
76pub fn load_all_skills(working_dir: &PathBuf) -> (Vec<Skill>, Vec<SkillLoadError>) {
77    let global_skills_dir = dirs::home_dir().map(|h| h.join(".robit/skills"));
78    let project_skills_dir = Some(working_dir.join(".robit/skills"));
79
80    load_skills(global_skills_dir, project_skills_dir)
81}
82
83/// Filter skills by the enabled_skills list in config, if present.
84pub fn filter_skills_by_config(skills: Vec<Skill>, config: &RobitConfig) -> Vec<Skill> {
85    let enabled_skills = config.app.as_ref().and_then(|a| a.enabled_skills.as_ref());
86
87    match enabled_skills {
88        Some(list) => skills
89            .into_iter()
90            .filter(|s| list.contains(&s.frontmatter.name))
91            .collect(),
92        None => skills,
93    }
94}
95
96/// Create a ToolRegistry with tools filtered by config.enabled_tools.
97///
98/// - If enabled_tools is not specified: all tools are registered
99/// - If enabled_tools is specified: only register tools in the list
100/// - `read` and `load_skill` are always registered (required for basic functionality)
101pub fn create_tools_from_config(
102    config: &RobitConfig,
103    skill_registry: Arc<SkillRegistry>,
104) -> ToolRegistry {
105    let mut tools = ToolRegistry::new();
106    let context_config = config.app.as_ref().and_then(|a| a.context.as_ref());
107    let max_lines = context_config.and_then(|c| c.max_output_lines).unwrap_or(500);
108    let max_bytes = context_config
109        .and_then(|c| c.max_output_bytes)
110        .unwrap_or(51200);
111
112    // Always register read and load_skill (required for basic functionality)
113    tools.register(ReadTool::new(max_lines, max_bytes));
114    tools.register(LoadSkillTool::new(skill_registry));
115
116    // Get enabled tools from config
117    let enabled_tools = config.app.as_ref().and_then(|a| a.enabled_tools.as_ref());
118
119    match enabled_tools {
120        Some(list) => {
121            // Configured: only register specified tools (read and load_skill already registered)
122            for tool_name in list {
123                match tool_name.as_str() {
124                    "read" => {} // already registered
125                    "load_skill" => {} // already registered
126                    "bash" => tools.register(BashTool::new(max_bytes)),
127                    "write" => tools.register(WriteTool::new()),
128                    "edit" => tools.register(EditTool::new()),
129                    "ls" => tools.register(LsTool::new()),
130                    "find" => tools.register(FindTool::new(max_bytes)),
131                    "grep" => tools.register(GrepTool::new(max_lines, max_bytes)),
132                    _ => tracing::warn!("Unknown tool in enabled_tools config: {}", tool_name),
133                }
134            }
135        }
136        None => {
137            // Not configured: register all tools
138            tools.register(BashTool::new(max_bytes));
139            tools.register(WriteTool::new());
140            tools.register(EditTool::new());
141            tools.register(LsTool::new());
142            tools.register(FindTool::new(max_bytes));
143            tools.register(GrepTool::new(max_lines, max_bytes));
144        }
145    }
146
147    tools
148}
149
150/// Log any skill load errors as warnings.
151///
152/// Convenience function for frontends to log errors without duplicating code.
153pub fn log_skill_errors(errors: &[SkillLoadError]) {
154    for err in errors {
155        tracing::warn!("Skill load error: {:?}", err);
156    }
157}