pub mod read_file;
pub mod round_guard;
pub mod run_command;
use super::tool_types::ToolSpec;
use crate::session_context::SessionContext;
pub fn catalog(ctx: &SessionContext) -> Vec<ToolSpec> {
let manifest = crate::capability::manifest();
let available = manifest.available(ctx);
let mut tools = Vec::new();
if let Some(descriptor) = available.iter().find(|d| d.name == "repo.read") {
tools.push(ToolSpec {
name: "read_file",
description: "Read a UTF-8 text file within the repository. \
Path is relative to the repository root; paths that \
resolve outside it are refused.",
parameters_schema: descriptor.input_schema.clone(),
});
}
if let Some(descriptor) = available.iter().find(|d| d.name == "command.execute") {
tools.push(ToolSpec {
name: "run_command",
description: "Propose running a shell command. Requires \
explicit human approval in the terminal before it \
executes — never runs silently, and commands matching a \
known-destructive pattern (rm -rf, force-push, etc.) are \
never offered for approval at all.",
parameters_schema: descriptor.input_schema.clone(),
});
}
tools
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
fn ctx() -> SessionContext {
SessionContext::new("s", PathBuf::from("/tmp"), "ollama", "m", false)
}
#[test]
fn catalog_has_exactly_two_tools() {
let tools = catalog(&ctx());
assert_eq!(tools.len(), 2);
assert_eq!(tools[0].name, "read_file");
assert_eq!(tools[1].name, "run_command");
}
#[test]
fn catalog_schema_matches_original_hardcoded_shape() {
let tools = catalog(&ctx());
assert_eq!(
tools[0].parameters_schema,
serde_json::json!({
"type": "object",
"properties": { "path": { "type": "string" } },
"required": ["path"],
})
);
assert_eq!(
tools[1].parameters_schema,
serde_json::json!({
"type": "object",
"properties": { "command": { "type": "string" } },
"required": ["command"],
})
);
}
}