// unifi's tools — its own code (novox/hq ADR 0039), importing unifi's own client. They return // structured data; the mesh serves them through the sdk's tool harness. unifi is tools-only (no // events entrypoint): the controller does not push lifecycle events the mesh consumes, so this // module reads its resources — port forwards, devices, clients — and exposes them, and stops there. import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools"; import { UnifiApiClient, type UnifiPortForward } from "../client.js"; function summarizePortForward(r: UnifiPortForward): Record { return { id: r._id, name: r.name, enabled: r.enabled, src: r.src || "any", dst_port: r.dst_port, forward: `${r.fwd}:${r.fwd_port}`, proto: r.proto, }; } export function getUnifiTools(unifi: UnifiApiClient): ToolDefinition[] { return [ { name: "unifi_reachable", description: "Health probe: whether the UniFi controller answers and can be logged into. Never fails.", input: {}, run: async () => unifi.reachable(), }, { name: "unifi_list_port_forwards", description: "List all port-forwarding rules on the UniFi gateway.", input: {}, run: async () => { const rules = await unifi.listPortForwards(); return { count: rules.length, rules: rules.map(summarizePortForward) }; }, }, { name: "unifi_create_port_forward", description: "Create a port-forwarding rule on the UniFi gateway.", input: { name: { type: "string", description: "rule name (e.g. 'Redis')" }, dst_port: { type: "string", description: "external/WAN port (e.g. '6379')" }, fwd: { type: "string", description: "forward to LAN IP (e.g. '192.0.2.10')" }, fwd_port: { type: "string", description: "forward to port (e.g. '6379')" }, proto: { type: "string", description: "protocol: tcp, udp or tcp_udp (default tcp)" }, src: { type: "string", description: "source IP/CIDR restriction (omitted = any)" }, enabled: { type: "boolean", description: "enable the rule (default true)" }, }, run: async (args) => { const rule = await unifi.createPortForward({ name: String(args.name), dst_port: String(args.dst_port), fwd: String(args.fwd), fwd_port: String(args.fwd_port), proto: args.proto ? String(args.proto) : "tcp", src: args.src ? String(args.src) : "any", enabled: args.enabled === undefined ? true : Boolean(args.enabled), pfwd_interface: "wan", log: false, }); return { created: summarizePortForward(rule) }; }, }, { name: "unifi_toggle_port_forward", description: "Enable or disable a port-forwarding rule by id (see unifi_list_port_forwards).", input: { id: { type: "string", description: "the port-forward rule id" }, enabled: { type: "boolean", description: "true to enable, false to disable" }, }, run: async (args) => { const enabled = Boolean(args.enabled); const rule = await unifi.updatePortForward(String(args.id), { enabled }); return { updated: summarizePortForward(rule) }; }, }, { name: "unifi_delete_port_forward", description: "Delete a port-forwarding rule by id. Requires confirm: true.", input: { id: { type: "string", description: "the port-forward rule id" }, confirm: { type: "boolean", description: "must be true to confirm deletion" }, }, run: async (args) => { if (!args.confirm) return { deleted: false, reason: "set confirm: true to delete" }; await unifi.deletePortForward(String(args.id)); return { deleted: true, id: String(args.id) }; }, }, { name: "unifi_list_devices", description: "List the network devices (APs, switches, gateways) the UniFi controller manages.", input: {}, run: async () => { const devices = await unifi.listDevices(); return { count: devices.length, devices: devices.map((d) => ({ name: d.name || d.mac, model: d.model, ip: d.ip, state: d.state === 1 ? "online" : "offline", adopted: d.adopted, version: d.version, uptimeHours: d.uptime ? Math.floor(d.uptime / 3600) : 0, })), }; }, }, { name: "unifi_list_clients", description: "List the connected network clients — wired and wireless.", input: {}, run: async () => { const clients = await unifi.listClients(); return { count: clients.length, clients: clients .slice() .sort((a, b) => (a.name || a.hostname || a.ip).localeCompare(b.name || b.hostname || b.ip)) .map((c) => ({ name: c.name || c.hostname || c.mac, ip: c.ip, mac: c.mac, type: c.is_wired ? "wired" : "wifi", network: c.network, })), }; }, }, ]; } // The tools exist only when credentials are configured; without them, unifi contributes none rather // than failing the whole runtime. registerModuleTools("unifi", (env) => { try { return getUnifiTools(UnifiApiClient.fromEnv(env)); } catch { return []; } });