hub: serve apropos + wake_fleet (fleet-central tools) alongside remote_*
Consolidate the two fleet-central local tools (stash recall + pve WoL) onto the hub broker so :8090 is a complete fleet-management endpoint. They run locally on hertz via a new run_local() helper (no ssh backend hop). Still also served by hertz-tools (:8080) for now; the hertz-side removal is the follow-up once every client (pi-agents) has a @hub session. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWpfhDgYNA21tETDP9ueBE
This commit is contained in:
@@ -631,6 +631,62 @@ server:tool("remote_search_files", "find-by-pattern on a fleet host.",
|
||||
} }
|
||||
)
|
||||
|
||||
-- ---- Fleet-central local tools (migrated from tools.d/hertz.lua, 2026-07-18) ----
|
||||
-- These run LOCALLY on hertz (where the hub process also lives), so no ssh
|
||||
-- backend hop. Consolidated here so the hub is the single fleet-management
|
||||
-- endpoint. Still also served by hertz-tools (:8080) for now — remove there
|
||||
-- once every client (pi-agents etc.) has a @hub session.
|
||||
local function run_local(cmd, timeout)
|
||||
local full = timeout and ("timeout " .. tostring(timeout) .. " " .. cmd) or cmd
|
||||
local p = io.popen(full .. " 2>&1")
|
||||
if not p then return "Error: popen failed" end
|
||||
local out = p:read("*a")
|
||||
p:close()
|
||||
return out or ""
|
||||
end
|
||||
|
||||
server:tool("apropos",
|
||||
"Search shared fleet memory (stash) for facts about the fleet, projects, decisions, and preferences. query = 2-6 words on the topic; limit = max results (default 3). Read-only.",
|
||||
{ type = "object", properties = {
|
||||
query = { type = "string", description = "2-6 words describing what to recall" },
|
||||
limit = { type = "integer", description = "max results, default 3" },
|
||||
}, required = { "query" } },
|
||||
function(a)
|
||||
local q = tostring(a.query or ""):gsub("[^%w%s%-%.]", " "):gsub("%s+", " ")
|
||||
if q:gsub("%s", "") == "" then return "Error: query required" end
|
||||
local lim = tonumber(a.limit) or 3
|
||||
return run_local("python3 /opt/lmcp/helpers/stash_recall.py '" .. q .. "' " .. lim, 30)
|
||||
end,
|
||||
{ annotations = {
|
||||
title = "Apropos (fleet memory)",
|
||||
readOnlyHint = true,
|
||||
destructiveHint = false,
|
||||
idempotentHint = true,
|
||||
openWorldHint = true,
|
||||
} }
|
||||
)
|
||||
|
||||
server:tool("wake_fleet",
|
||||
"Wake a fleet NUC (pve1..pve4) via Fritz!Box Wake-on-LAN. Powers a node ON only; it cannot power anything off. Node boots in ~30-60s.",
|
||||
{ type = "object", properties = {
|
||||
node = { type = "string", description = "Node to wake: '1'..'4' or 'pve1'..'pve4'" },
|
||||
}, required = { "node" } },
|
||||
function(a)
|
||||
local node = tostring(a.node or ""):gsub("[^%w]", "")
|
||||
if not node:match("^p?v?e?[1-4]$") then
|
||||
return "Error: node must be 1-4 or pve1-pve4 (got: " .. tostring(a.node) .. ")"
|
||||
end
|
||||
return run_local("sudo /root/.local/bin/wake-pve " .. node, 15)
|
||||
end,
|
||||
{ annotations = {
|
||||
title = "Wake fleet NUC",
|
||||
readOnlyHint = false,
|
||||
destructiveHint = false,
|
||||
idempotentHint = true,
|
||||
openWorldHint = true,
|
||||
} }
|
||||
)
|
||||
|
||||
io.stderr:write(string.format("lmcp-hub starting on port %d with %d backends from %s\n",
|
||||
server.port, (function() local n = 0; for _ in pairs(backends) do n = n + 1 end; return n end)(), CONF_PATH))
|
||||
server:run()
|
||||
|
||||
Reference in New Issue
Block a user