diff --git a/src/crates/core/src/agentic/tools/implementations/file_edit_tool.rs b/src/crates/core/src/agentic/tools/implementations/file_edit_tool.rs index 8ea595f2..207cd475 100644 --- a/src/crates/core/src/agentic/tools/implementations/file_edit_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/file_edit_tool.rs @@ -68,6 +68,10 @@ Usage: false } + fn needs_permissions(&self, _input: Option<&Value>) -> bool { + false + } + async fn call_impl( &self, input: &Value, diff --git a/src/crates/core/src/agentic/tools/implementations/grep_tool.rs b/src/crates/core/src/agentic/tools/implementations/grep_tool.rs index 5dc416a5..26c0f191 100644 --- a/src/crates/core/src/agentic/tools/implementations/grep_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/grep_tool.rs @@ -18,9 +18,9 @@ impl GrepTool { input: &Value, context: &ToolUseContext, ) -> BitFunResult> { - let ws_shell = context.ws_shell().ok_or_else(|| { - BitFunError::tool("Workspace shell not available".to_string()) - })?; + let ws_shell = context + .ws_shell() + .ok_or_else(|| BitFunError::tool("Workspace shell not available".to_string()))?; let pattern = input .get("pattern") @@ -52,7 +52,10 @@ impl GrepTool { if let Some(ft) = file_type { cmd.push_str(&format!(" --type {}", ft)); } - cmd.push_str(&format!(" '{}' '{}' 2>/dev/null | head -n {}", escaped_pattern, escaped_path, head_limit)); + cmd.push_str(&format!( + " '{}' '{}' 2>/dev/null | head -n {}", + escaped_pattern, escaped_path, head_limit + )); let full_cmd = format!( "if command -v rg >/dev/null 2>&1; then {}; else grep -rn{} '{}' '{}' 2>/dev/null | head -n {}; fi", @@ -103,7 +106,10 @@ impl GrepTool { let resolved_path = resolve_path_with_workspace(search_path, context.workspace_root())?; let case_insensitive = input.get("-i").and_then(|v| v.as_bool()).unwrap_or(false); - let multiline = input.get("multiline").and_then(|v| v.as_bool()).unwrap_or(false); + let multiline = input + .get("multiline") + .and_then(|v| v.as_bool()) + .unwrap_or(false); let output_mode_str = input .get("output_mode") .and_then(|v| v.as_str()) @@ -113,9 +119,18 @@ impl GrepTool { let context_c = input.get("-C").and_then(|v| v.as_u64()).map(|v| v as usize); let before_context = input.get("-B").and_then(|v| v.as_u64()).map(|v| v as usize); let after_context = input.get("-A").and_then(|v| v.as_u64()).map(|v| v as usize); - let head_limit = input.get("head_limit").and_then(|v| v.as_u64()).map(|v| v as usize); - let glob_pattern = input.get("glob").and_then(|v| v.as_str()).map(|s| s.to_string()); - let file_type = input.get("type").and_then(|v| v.as_str()).map(|s| s.to_string()); + let head_limit = input + .get("head_limit") + .and_then(|v| v.as_u64()) + .map(|v| v as usize); + let glob_pattern = input + .get("glob") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()); + let file_type = input + .get("type") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()); let mut options = GrepOptions::new(pattern, resolved_path) .case_insensitive(case_insensitive) @@ -123,12 +138,24 @@ impl GrepTool { .output_mode(output_mode) .show_line_numbers(show_line_numbers); - if let Some(c) = context_c { options = options.context(c); } - if let Some(b) = before_context { options = options.before_context(b); } - if let Some(a) = after_context { options = options.after_context(a); } - if let Some(h) = head_limit { options = options.head_limit(h); } - if let Some(g) = glob_pattern { options = options.glob(g); } - if let Some(t) = file_type { options = options.file_type(t); } + if let Some(c) = context_c { + options = options.context(c); + } + if let Some(b) = before_context { + options = options.before_context(b); + } + if let Some(a) = after_context { + options = options.after_context(a); + } + if let Some(h) = head_limit { + options = options.head_limit(h); + } + if let Some(g) = glob_pattern { + options = options.glob(g); + } + if let Some(t) = file_type { + options = options.file_type(t); + } Ok(options) } @@ -188,9 +215,17 @@ Usage: }) } - fn is_readonly(&self) -> bool { true } - fn is_concurrency_safe(&self, _input: Option<&Value>) -> bool { true } - fn needs_permissions(&self, _input: Option<&Value>) -> bool { false } + fn is_readonly(&self) -> bool { + true + } + + fn is_concurrency_safe(&self, _input: Option<&Value>) -> bool { + true + } + + fn needs_permissions(&self, _input: Option<&Value>) -> bool { + false + } fn render_tool_use_message( &self, @@ -201,9 +236,16 @@ Usage: let search_path = input.get("path").and_then(|v| v.as_str()).unwrap_or("."); let file_type = input.get("type").and_then(|v| v.as_str()); let glob_pattern = input.get("glob").and_then(|v| v.as_str()); - let output_mode = input.get("output_mode").and_then(|v| v.as_str()).unwrap_or("files_with_matches"); + let output_mode = input + .get("output_mode") + .and_then(|v| v.as_str()) + .unwrap_or("files_with_matches"); - let scope = if search_path == "." { "Current workspace".to_string() } else { search_path.to_string() }; + let scope = if search_path == "." { + "Current workspace".to_string() + } else { + search_path.to_string() + }; let scope_with_filter = if let Some(ft) = file_type { format!("{} (*.{})", scope, ft) } else if let Some(gp) = glob_pattern { @@ -217,7 +259,10 @@ Usage: _ => "List matching files", }; - format!("Search \"{}\" | {} | {}", pattern, scope_with_filter, mode_desc) + format!( + "Search \"{}\" | {} | {}", + pattern, scope_with_filter, mode_desc + ) } async fn call_impl( diff --git a/src/crates/core/src/agentic/tools/implementations/miniapp_init_tool.rs b/src/crates/core/src/agentic/tools/implementations/miniapp_init_tool.rs index 077f9384..d1d97a9f 100644 --- a/src/crates/core/src/agentic/tools/implementations/miniapp_init_tool.rs +++ b/src/crates/core/src/agentic/tools/implementations/miniapp_init_tool.rs @@ -103,6 +103,10 @@ Returns app_id and the app root directory. Use the root directory and file names false } + fn needs_permissions(&self, _input: Option<&Value>) -> bool { + false + } + async fn call_impl( &self, input: &Value, diff --git a/src/crates/core/src/agentic/tools/implementations/skills/registry.rs b/src/crates/core/src/agentic/tools/implementations/skills/registry.rs index b6671357..53cc113c 100644 --- a/src/crates/core/src/agentic/tools/implementations/skills/registry.rs +++ b/src/crates/core/src/agentic/tools/implementations/skills/registry.rs @@ -2,7 +2,7 @@ //! //! Manages Skill loading and enabled/disabled filtering //! Supports multiple application paths: -//! .bitfun/skills, .claude/skills, .cursor/skills, .codex/skills, .agents/skills +//! .bitfun/skills, .claude/skills, .cursor/skills, .codex/skills, .opencode/skills, .agents/skills use super::builtin::ensure_builtin_skills_installed; use super::types::{SkillData, SkillInfo, SkillLocation}; @@ -22,18 +22,23 @@ static SKILL_REGISTRY: OnceLock = OnceLock::new(); const PROJECT_SKILL_SUBDIRS: &[(&str, &str)] = &[ (".bitfun", "skills"), (".claude", "skills"), - (".cursor", "skills"), (".codex", "skills"), + (".cursor", "skills"), + (".opencode", "skills"), (".agents", "skills"), ]; /// Home-directory based user-level Skill paths. const USER_HOME_SKILL_SUBDIRS: &[(&str, &str)] = &[ (".claude", "skills"), - (".cursor", "skills"), (".codex", "skills"), + (".cursor", "skills"), + (".agents", "skills"), ]; +/// Config-directory based user-level Skill paths. +const USER_CONFIG_SKILL_SUBDIRS: &[(&str, &str)] = &[("opencode", "skills"), ("agents", "skills")]; + /// Skill directory entry #[derive(Debug, Clone)] pub struct SkillDirEntry { @@ -87,12 +92,14 @@ impl SkillRegistry { } if let Some(config_dir) = dirs::config_dir() { - let p = config_dir.join("agents").join("skills"); - if p.exists() && p.is_dir() { - entries.push(SkillDirEntry { - path: p, - level: SkillLocation::User, - }); + for (parent, sub) in USER_CONFIG_SKILL_SUBDIRS { + let p = config_dir.join(parent).join(sub); + if p.exists() && p.is_dir() { + entries.push(SkillDirEntry { + path: p, + level: SkillLocation::User, + }); + } } } @@ -142,8 +149,8 @@ impl SkillRegistry { /// Get all possible Skill directory paths /// /// Returns existing directories and their levels (project/user) - /// - Project-level: .bitfun/skills, .claude/skills, .cursor/skills, .codex/skills, .agents/skills under workspace - /// - User-level: skills under bitfun user config, ~/.claude/skills, ~/.cursor/skills, ~/.codex/skills, ~/.config/agents/skills + /// - Project-level: .bitfun/skills, .claude/skills, .cursor/skills, .codex/skills, .opencode/skills, .agents/skills under workspace + /// - User-level: skills under bitfun user config, ~/.claude/skills, ~/.cursor/skills, ~/.codex/skills, ~/.agents/skills, ~/.config/opencode/skills, ~/.config/agents/skills pub fn get_possible_paths() -> Vec { Self::get_possible_paths_for_workspace(None) }