Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2bb7b94a66 | |||
| 80fb60c60f |
@@ -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",
|
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.port, (function() local n = 0; for _ in pairs(backends) do n = n + 1 end; return n end)(), CONF_PATH))
|
||||||
server:run()
|
server:run()
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
-- boltzmann-tools plugin: `stash` — fleet persistent-memory CLI wrapper.
|
||||||
|
-- Receives (server, run). Runs the stash CLI inside the memory Incus container's
|
||||||
|
-- stash-stash-1 docker container (which has NO shell — /stash is invoked directly
|
||||||
|
-- as argv; docker exec handles that, no `sh -c` inside the container).
|
||||||
|
local server, run = ...
|
||||||
|
|
||||||
|
server:tool("stash",
|
||||||
|
"Fleet persistent memory (stash knowledge graph on the memory container). "
|
||||||
|
.. "`command` = a stash subcommand + its args as ONE string. "
|
||||||
|
.. "Examples: command:=\"recall escher plug AIN\" | command:=\"facts\" | "
|
||||||
|
.. "command:=\"remember 'the NAS is at 192.168.88.10'\" | command:=\"namespace list\". "
|
||||||
|
.. "Wrap multi-word text in single quotes. Read subcommands: recall, facts, namespace list, "
|
||||||
|
.. "goal list, context show. Write: remember, forget, consolidate run.",
|
||||||
|
{ type = "object", properties = {
|
||||||
|
command = { type = "string",
|
||||||
|
description = "stash subcommand + args, e.g. \"recall <query>\" or \"remember '<text>'\"" },
|
||||||
|
}, required = { "command" } },
|
||||||
|
function(a)
|
||||||
|
local c = tostring(a.command or ""):gsub("^%s+", ""):gsub("%s+$", "")
|
||||||
|
if c == "" then return "Error: command required (e.g. command:=\"recall <query>\")" end
|
||||||
|
return run("incus exec memory -- docker exec stash-stash-1 /stash " .. c, 60)
|
||||||
|
end,
|
||||||
|
{ annotations = {
|
||||||
|
title = "Stash fleet memory",
|
||||||
|
readOnlyHint = false,
|
||||||
|
destructiveHint = false,
|
||||||
|
idempotentHint = false,
|
||||||
|
openWorldHint = true,
|
||||||
|
} }
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user