use std::{fmt, str::FromStr};
use rho_sdk::CapabilityKind;
use thiserror::Error;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum CursorTool {
Read,
Grep,
Glob,
Ls,
SemSearch,
ReadLints,
Edit,
Delete,
Shell,
WriteShellStdin,
WebSearch,
WebFetch,
Fetch,
Mcp,
ListMcpResources,
ReadMcpResource,
UpdateTodos,
ReadTodos,
CreatePlan,
ApplyAgentDiff,
}
impl CursorTool {
pub const ALL: &[CursorTool] = &[
Self::Read,
Self::Grep,
Self::Glob,
Self::Ls,
Self::SemSearch,
Self::ReadLints,
Self::Edit,
Self::Delete,
Self::Shell,
Self::WriteShellStdin,
Self::WebSearch,
Self::WebFetch,
Self::Fetch,
Self::Mcp,
Self::ListMcpResources,
Self::ReadMcpResource,
Self::UpdateTodos,
Self::ReadTodos,
Self::CreatePlan,
Self::ApplyAgentDiff,
];
pub fn as_flag(self) -> &'static str {
match self {
Self::Read => "read_tool_call",
Self::Grep => "grep_tool_call",
Self::Glob => "glob_tool_call",
Self::Ls => "ls_tool_call",
Self::SemSearch => "sem_search_tool_call",
Self::ReadLints => "read_lints_tool_call",
Self::Edit => "edit_tool_call",
Self::Delete => "delete_tool_call",
Self::Shell => "shell_tool_call",
Self::WriteShellStdin => "write_shell_stdin_tool_call",
Self::WebSearch => "web_search_tool_call",
Self::WebFetch => "web_fetch_tool_call",
Self::Fetch => "fetch_tool_call",
Self::Mcp => "mcp_tool_call",
Self::ListMcpResources => "list_mcp_resources_tool_call",
Self::ReadMcpResource => "read_mcp_resource_tool_call",
Self::UpdateTodos => "update_todos_tool_call",
Self::ReadTodos => "read_todos_tool_call",
Self::CreatePlan => "create_plan_tool_call",
Self::ApplyAgentDiff => "apply_agent_diff_tool_call",
}
}
pub fn label(self) -> &'static str {
let flag = self.as_flag();
flag.strip_suffix("_tool_call").unwrap_or(flag)
}
pub fn detail(self) -> &'static str {
match self {
Self::Read => "Read a file.",
Self::Grep => "Search file contents with a regex.",
Self::Glob => "Find files by pattern.",
Self::Ls => "List a directory.",
Self::SemSearch => "Semantic search over the indexed codebase.",
Self::ReadLints => "Read linter diagnostics for a file.",
Self::Edit => "Edit or create a file.",
Self::Delete => "Delete a file.",
Self::Shell => "Run a shell command.",
Self::WriteShellStdin => "Write to a running shell command's stdin.",
Self::WebSearch => "Search the web.",
Self::WebFetch => "Fetch a web page.",
Self::Fetch => "Fetch a URL as text.",
Self::Mcp => "Call any tool on a configured MCP server.",
Self::ListMcpResources => "List resources exposed by MCP servers.",
Self::ReadMcpResource => "Read one MCP resource.",
Self::UpdateTodos => "Create or update the todo list.",
Self::ReadTodos => "Read the todo list.",
Self::CreatePlan => "Write a plan document.",
Self::ApplyAgentDiff => "Apply a diff produced by the agent.",
}
}
pub fn capability_kind(self) -> CapabilityKind {
match self {
Self::Read
| Self::Grep
| Self::Glob
| Self::Ls
| Self::SemSearch
| Self::ReadLints
| Self::ReadTodos
| Self::ReadMcpResource
| Self::ListMcpResources => CapabilityKind::Read,
Self::Edit | Self::Delete | Self::ApplyAgentDiff => CapabilityKind::Write,
Self::Shell | Self::WriteShellStdin => CapabilityKind::Process,
Self::WebSearch | Self::WebFetch | Self::Fetch => CapabilityKind::Network,
Self::Mcp => CapabilityKind::Process,
Self::UpdateTodos | Self::CreatePlan => CapabilityKind::Write,
}
}
pub fn is_read_only(self) -> bool {
matches!(self.capability_kind(), CapabilityKind::Read)
}
fn accepted_names() -> String {
Self::ALL
.iter()
.map(|tool| tool.as_flag())
.collect::<Vec<_>>()
.join(", ")
}
}
impl fmt::Display for CursorTool {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_flag())
}
}
impl FromStr for CursorTool {
type Err = CursorToolError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Self::ALL
.iter()
.copied()
.find(|tool| tool.as_flag() == value)
.ok_or_else(|| CursorToolError {
value: value.to_string(),
expected: Self::accepted_names(),
})
}
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[error("unknown Cursor tool '{value}'; expected one of: {expected}")]
pub struct CursorToolError {
value: String,
expected: String,
}
#[cfg(test)]
#[path = "cursor_tools_tests.rs"]
mod tests;