// confluence's tools — confluence's own code (novox/hq ADR 0039), importing confluence's own // Confluence API client. They return structured data; the mesh serves them through the sdk's tool // harness. confluence is tools-only and outbound-only: no service, no events, no listener — it // reaches out to an Atlassian Confluence instance and exposes its content search, pages and spaces. // // Every tool is registered unconditionally, even with no credentials configured (the Servarr // lesson): the client is built lazily and never throws, so the runtime always comes up and serves // the full tool surface — a call made before the URL/email/token are set fails with a clear error, // but the runtime does not refuse to serve. The install proof is the runtime logging // `[mesh-tools] serving N tool(s)`. import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools"; import { ConfluenceClient } from "../client.js"; function str(value: unknown): string { return String(value ?? ""); } function num(value: unknown, fallback: number): number { const n = Number(value); return Number.isFinite(n) ? n : fallback; } export function getConfluenceTools(confluence: ConfluenceClient): ToolDefinition[] { return [ { name: "confluence_search", description: "Search Confluence content using CQL.", input: { cql: { type: "string", description: "CQL query string" }, limit: { type: "number", description: "max results (default 25)" }, }, run: async (args) => ({ results: await confluence.search(str(args.cql), num(args.limit, 25)) }), }, { name: "confluence_get_page", description: "Get a Confluence page by ID.", input: { page_id: { type: "string", description: "Page ID" }, }, run: async (args) => ({ page: await confluence.getPage(str(args.page_id)) }), }, { name: "confluence_list_spaces", description: "List Confluence spaces.", input: { page: { type: "number", description: "page number, 1-based (default 1)" }, limit: { type: "number", description: "results per page (default 25)" }, }, run: async (args) => { const page = num(args.page, 1); const limit = num(args.limit, 25); const cursor = String((page - 1) * limit); return { spaces: await confluence.listSpaces({ cursor, limit: String(limit) }) }; }, }, ]; } // confluence always registers its full tool surface: the client is built lazily and never throws, so // the runtime comes up and serves every tool even before the URL/email/token are configured (the lab // has no real Atlassian). A tool called before the module is configured fails with a clear error from // that call. registerModuleTools("confluence", (env) => { try { return getConfluenceTools(ConfluenceClient.fromEnv(env)); } catch { return []; } });