Files
mesh-catalog/modules/lidarr/tools/index.ts
T
jschoubben c82b3ff706 Convert four hal modules: lidarr, mongodb, mssql, mosquitto
Mirrors the proven catalog patterns field-for-field:
- lidarr  -> the Servarr twin of radarr/sonarr (API v1, artist content); no
  provisioner (it is a consumer app).
- mongodb -> postgres shape: mongodb-database provider, provisioner mints a
  per-consumer db+user (ADR 0053), client shells to mongosh (no npm driver,
  the psql convention).
- mssql   -> postgres shape: mssql-database provider, sqlcmd client.
- mosquitto -> redis shape: mqtt-topic provider via the Dynamic Security
  plugin, deliberately avoiding hal's password_file (that file is nox issue
  011 exactly); provisioner mints a per-consumer MQTT client+role.

All four typecheck (strict, NodeNext) against the built @novox/mesh-sdk, and
their service images are digest-pinned to resolved registry digests. The
mesh-runtime-<mod> images keep the all-zeros placeholder the pipeline pins,
as postgres/redis do, and must bundle each module's CLI (mongosh/sqlcmd/
mosquitto_ctrl) as mesh-runtime-postgres bundles psql.

Not yet lab-verified: each module lists in-code what an integration test must
prove (auth model, provisioner reconcile, mosquitto dynsec bootstrap ordering).

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-05 04:17:07 +02:00

79 lines
2.8 KiB
TypeScript

// lidarr's tools — ported from the shared hal sdk (novox/hq ADR 0044), importing lidarr's own
// client. They return structured data (not pre-formatted text as hal did); the mesh serves them
// through the sdk's tool harness.
import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools";
import { LidarrClient } from "../client.js";
export function getLidarrTools(lidarr: LidarrClient): ToolDefinition[] {
return [
{
name: "lidarr_status",
description: "Lidarr status overview: version, artist count, monitored count, queue size.",
input: {},
run: async () => {
const [status, content, queue] = await Promise.all([
lidarr.getStatus(),
lidarr.getContent(),
lidarr.getQueue(),
]);
return {
app: status.appName,
version: status.version,
artists: content.length,
monitored: content.filter((c) => c.monitored).length,
queue: queue.totalRecords,
};
},
},
{
name: "lidarr_library",
description: "List artists from the Lidarr library.",
input: { limit: { type: "number", description: "max items to return (default 50)" } },
run: async (args) => {
const items = await lidarr.getContent(args.limit ? Number(args.limit) : 50);
return { count: items.length, artists: items };
},
},
{
name: "lidarr_search",
description: "Search the Lidarr library for artists by name (filters existing content, not indexers).",
input: { query: { type: "string", description: "the search term" } },
run: async (args) => {
const query = String(args.query);
return { query, results: await lidarr.searchContent(query) };
},
},
{
name: "lidarr_queue",
description: "Show the Lidarr download queue — what is downloading and how far along.",
input: {},
run: async () => {
const queue = await lidarr.getQueue();
return { count: queue.totalRecords, items: queue.items };
},
},
{
name: "lidarr_calendar",
description: "Upcoming album releases from the Lidarr calendar.",
input: { days: { type: "number", description: "how many days to look ahead (default 7)" } },
run: async (args) => {
const days = args.days ? Number(args.days) : 7;
const items = await lidarr.getCalendar(days);
items.sort((a, b) => a.date.localeCompare(b.date));
return { days, count: items.length, items };
},
},
];
}
// The tools exist only when Lidarr is configured; without a URL and key, lidarr contributes none
// rather than failing the whole runtime.
registerModuleTools("lidarr", (env) => {
try {
return getLidarrTools(LidarrClient.fromEnv(env));
} catch {
return [];
}
});