Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b29a2716d1 | |||
| 555beb9fd9 |
@@ -355,7 +355,7 @@ math.randomseed(os.time())
|
||||
|
||||
local server = lmcp.new(os.getenv("LMCP_NAME") or "hub-tools", {
|
||||
port = tonumber(os.getenv("LMCP_PORT") or arg[1]) or 8090,
|
||||
version = "0.5.0",
|
||||
version = "0.5.2",
|
||||
conf = os.getenv("LMCP_HUB_CONF") or "/opt/herding/etc/lmcp-hub.conf",
|
||||
})
|
||||
|
||||
@@ -433,6 +433,17 @@ server:tool("remote_edit_file",
|
||||
function(a) return call_remote("edit_file", a, false, nil) end
|
||||
)
|
||||
|
||||
server:tool("remote_shell_bg",
|
||||
"Launch a detached background command on a fleet host (Linux). Returns PID + log path immediately. Requires backend lmcp v0.5.2+.",
|
||||
{ type = "object", properties = {
|
||||
host = HOST_ARG,
|
||||
command = { type = "string", description = "Shell command" },
|
||||
cwd = { type = "string" },
|
||||
log = { type = "string", description = "Log file path" },
|
||||
}, required = { "host", "command" } },
|
||||
function(a) return call_remote("shell_bg", a, false, nil) end
|
||||
)
|
||||
|
||||
server:tool("remote_list_dir", "List directory entries on a fleet host.",
|
||||
{ type = "object", properties = {
|
||||
host = HOST_ARG,
|
||||
|
||||
+47
-1
@@ -170,6 +170,49 @@ server:tool("shell", "Execute a shell command.", {
|
||||
return run(cmd, a.timeout or 120)
|
||||
end)
|
||||
|
||||
server:tool("shell_bg",
|
||||
"Fire-and-forget shell command (Linux-only). Fully detaches via setsid+nohup+stdio-redirect and returns immediately with PID and log path. Use for daemons that must outlive the lmcp request.",
|
||||
{
|
||||
type = "object",
|
||||
properties = {
|
||||
command = { type = "string", description = "Shell command to launch" },
|
||||
cwd = { type = "string", description = "Working directory" },
|
||||
log = { type = "string", description = "Log file (stdout+stderr). Default: /tmp/lmcp-bg-<ts>-<rand>.log" },
|
||||
},
|
||||
required = { "command" },
|
||||
},
|
||||
function(a)
|
||||
if WINDOWS then
|
||||
return "Error: shell_bg is Linux-only (Windows Start-Process equivalent TBD)"
|
||||
end
|
||||
if type(a.command) ~= "string" or a.command == "" then
|
||||
return "Error: command required"
|
||||
end
|
||||
local log = a.log
|
||||
if not log or log == "" then
|
||||
log = string.format("/tmp/lmcp-bg-%d-%d.log", os.time(), math.random(1000, 9999))
|
||||
end
|
||||
local pid_file = log .. ".pid"
|
||||
local inner = a.command
|
||||
if a.cwd and a.cwd ~= "" then
|
||||
inner = "cd '" .. a.cwd:gsub("'", "'\\''") .. "' && " .. inner
|
||||
end
|
||||
local sq = function(s) return "'" .. s:gsub("'", "'\\''") .. "'" end
|
||||
local full = string.format(
|
||||
"setsid nohup sh -c %s </dev/null >%s 2>&1 & echo $! > %s",
|
||||
sq(inner), sq(log), sq(pid_file)
|
||||
)
|
||||
os.execute(full)
|
||||
local f = io.open(pid_file, 'r')
|
||||
local pid = "?"
|
||||
if f then
|
||||
pid = (f:read('*a') or ""):match("(%d+)") or "?"
|
||||
f:close()
|
||||
os.remove(pid_file)
|
||||
end
|
||||
return string.format("launched pid=%s log=%s", pid, log)
|
||||
end)
|
||||
|
||||
server:tool("read_file", "Read a file.", {
|
||||
type = "object",
|
||||
properties = { path = { type = "string" } },
|
||||
@@ -272,7 +315,10 @@ server:tool("search_files", "Search for files by pattern.", {
|
||||
if WINDOWS then
|
||||
return run('dir /b /s "' .. path .. '\\' .. a.pattern .. '"', 30)
|
||||
else
|
||||
return run("find '" .. path:gsub("'", "'\\''") .. "' -name '" .. a.pattern:gsub("'", "'\\''") .. "' 2>/dev/null", 30)
|
||||
-- -L: follow symlinks on the start path. macOS BSD find otherwise
|
||||
-- silently emits nothing when the start path is itself a symlink
|
||||
-- (common on Homebrew, e.g. /usr/local/share/lua -> Cellar/…/share/lua).
|
||||
return run("find -L '" .. path:gsub("'", "'\\''") .. "' -name '" .. a.pattern:gsub("'", "'\\''") .. "' 2>/dev/null", 30)
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user