// mosquitto's tools — mosquitto's own code (novox/hq ADR 0039), importing mosquitto's own admin // client. They return structured data; the mesh serves them through the sdk's tool harness. import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools"; import { MosquittoClient } from "../client.js"; export function getMosquittoTools(mosquitto: MosquittoClient): ToolDefinition[] { return [ { name: "mqtt_list_clients", description: "List the Dynamic Security clients registered on the mosquitto broker.", input: {}, run: async () => ({ clients: await mosquitto.listClients() }), }, { name: "mqtt_get_client", description: "Show one Dynamic Security client — its roles and enabled state.", input: { username: { type: "string", description: "the client's username" } }, run: async (args) => { const username = String(args.username ?? ""); if (!username) throw new Error("mqtt_get_client: username is required"); return { username, detail: await mosquitto.ctl("getClient", username) }; }, }, { name: "mqtt_ctrl", description: "Run an arbitrary 'mosquitto_ctrl dynsec' subcommand, e.g. 'listRoles', 'getRole myrole'. Admin surface.", input: { command: { type: "string", description: "the dynsec subcommand and its arguments, space-separated" } }, run: async (args) => { const parts = tokenize(String(args.command ?? "")); if (parts.length === 0) throw new Error("mqtt_ctrl: empty command"); const output = await mosquitto.ctl(...parts); return { command: parts.join(" "), output }; }, }, ]; } /** Split a command line into arguments, honouring double-quoted spans. */ function tokenize(command: string): string[] { const matches = command.match(/(?:[^\s"]+|"[^"]*")+/g) ?? []; return matches.map((p) => p.replace(/^"|"$/g, "")); } // The tools exist only when the broker can be reached from the environment; without it, mosquitto // contributes none rather than failing the whole tool runtime. registerModuleTools("mosquitto", (env) => { try { return getMosquittoTools(MosquittoClient.fromEnv(env)); } catch { return []; } });