// nodered's tools — importing nodered's own admin-API client (novox/hq ADR 0039). They return // structured data; the mesh serves them through the sdk's tool harness. // // The deploy tool is nodered's one event source (novox/hq ADR 0041/0042): a successful deploy // emits module.nodered.flows.deployed. nodered has nothing to observe on a timer, so there is no // separate events entrypoint — the emit rides the action that causes it. The emit is best-effort: // if no broker is bound, the deploy still succeeds and the announcement is simply skipped. import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools"; import { emit } from "@novox/mesh-sdk/events"; import { NodeRedClient } from "../client.js"; export function getNodeRedTools(nodered: NodeRedClient): ToolDefinition[] { return [ { name: "nodered_list_flows", description: "List Node-RED flows (tabs) — id, label, disabled state — and the total node count.", input: {}, run: async () => nodered.listFlows(), }, { name: "nodered_list_nodes", description: "List the Node-RED node modules installed in the runtime and their versions.", input: {}, run: async () => { const nodes = await nodered.listNodes(); return { count: nodes.length, nodes }; }, }, { name: "nodered_deploy", description: "Replace the whole Node-RED flow configuration and deploy it. `flows` is the full node " + "array (as GET /flows returns). Emits module.nodered.flows.deployed on success.", input: { flows: { type: "array", description: "the full flow configuration — every node across every tab" }, type: { type: "string", description: "deployment type: full (default), nodes, or flows" }, }, run: async (args) => { const flows = args.flows as unknown[]; if (!Array.isArray(flows)) throw new Error("flows must be an array of Node-RED nodes"); const type = args.type ? String(args.type) : "full"; const result = await nodered.deployFlows(flows, type); // Best-effort announcement — a deploy must not fail because the broker is unbound here. try { await emit("module.nodered.flows.deployed", { rev: result.rev, nodeCount: result.nodeCount, type }); } catch (err) { console.error(`[nodered] deployed but could not emit: ${err}`); } return result; }, }, ]; } // The tools exist only when a Node-RED URL is configured; without one, nodered contributes none // rather than failing the whole tool runtime. registerModuleTools("nodered", (env) => { try { return getNodeRedTools(NodeRedClient.fromEnv(env)); } catch { return []; } });