1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
-- Write text to a file, creating parent directories as needed. Pi-style
-- terse description because every byte of every tool description rides
-- in the system prompt on every turn.
local uv = require("luv")
-- mkdir -p equivalent: walk the path components and create each.
-- Ignores EEXIST so the call is idempotent.
local function mkdir_p(path)
if path == "" or path == "." or path == "/" then return true end
local parts = {}
for piece in string.gmatch(path, "[^/]+") do
parts[#parts + 1] = piece
end
local cur = (path:sub(1, 1) == "/") and "" or "."
for _, piece in ipairs(parts) do
cur = cur .. "/" .. piece
local ok, err, name = uv.fs_mkdir(cur, tonumber("755", 8))
if not ok and name ~= "EEXIST" then
return nil, err
end
end
return true
end
-- Return the dirname of `path`, or nil if there isn't one (basename only).
local function dirname(path)
local slash = path:find("/[^/]*$")
if not slash or slash == 1 then return nil end
return path:sub(1, slash - 1)
end
return {
name = "write",
description = "Save content to a path. Overwrites existing files; missing parent directories are created automatically.",
schema = {
type = "object",
properties = {
path = { type = "string", description = "Path to the file (relative or absolute)." },
content = { type = "string", description = "Exact bytes to write. No newline is added." },
},
required = { "path", "content" },
},
handler = function(input)
local path = input.path
local content = input.content
if type(path) ~= "string" or path == "" then
return "Error: `path` must be a non-empty string."
end
if type(content) ~= "string" then
return "Error: `content` must be a string."
end
local parent = dirname(path)
if parent then
local ok, err = mkdir_p(parent)
if not ok then
return "Error: could not create parent directory " .. parent .. ": " .. tostring(err)
end
end
local f, open_err = io.open(path, "wb")
if not f then
return "Error: " .. (open_err or ("could not open " .. path .. " for writing"))
end
local ok, write_err = f:write(content)
if not ok then
f:close()
return "Error: write failed: " .. tostring(write_err)
end
local close_ok, close_err = f:close()
if not close_ok then
return "Error: close failed: " .. tostring(close_err)
end
return string.format("Wrote %d bytes to %s.", #content, path)
end,
}
|