Skip to main content

tkach/tools/
write.rs

1use std::path::Path;
2
3use async_trait::async_trait;
4use serde_json::{Value, json};
5
6use crate::error::ToolError;
7use crate::tool::{Tool, ToolContext, ToolOutput};
8
9/// Write content to a file (creates or overwrites).
10pub struct Write;
11
12#[async_trait]
13impl Tool for Write {
14    fn name(&self) -> &str {
15        "write"
16    }
17
18    fn description(&self) -> &str {
19        "Write content to a file. Creates the file if it doesn't exist, \
20         overwrites if it does. Creates parent directories as needed."
21    }
22
23    fn input_schema(&self) -> Value {
24        json!({
25            "type": "object",
26            "properties": {
27                "file_path": {
28                    "type": "string",
29                    "description": "Absolute or relative path to the file"
30                },
31                "content": {
32                    "type": "string",
33                    "description": "The content to write to the file"
34                }
35            },
36            "required": ["file_path", "content"]
37        })
38    }
39
40    async fn execute(&self, input: Value, ctx: &ToolContext) -> Result<ToolOutput, ToolError> {
41        let file_path = input["file_path"]
42            .as_str()
43            .ok_or_else(|| ToolError::InvalidInput("file_path is required".into()))?;
44        let content = input["content"]
45            .as_str()
46            .ok_or_else(|| ToolError::InvalidInput("content is required".into()))?;
47
48        let path = resolve_path(&ctx.working_dir, file_path);
49
50        if let Some(parent) = path.parent() {
51            tokio::fs::create_dir_all(parent)
52                .await
53                .map_err(ToolError::Io)?;
54        }
55
56        tokio::fs::write(&path, content)
57            .await
58            .map_err(ToolError::Io)?;
59
60        let lines = content.lines().count();
61        Ok(ToolOutput::text(format!(
62            "Wrote {lines} lines to {}",
63            path.display()
64        )))
65    }
66}
67
68fn resolve_path(working_dir: &Path, file_path: &str) -> std::path::PathBuf {
69    let p = Path::new(file_path);
70    if p.is_absolute() {
71        p.to_path_buf()
72    } else {
73        working_dir.join(p)
74    }
75}