sonarr, radarr: full nox modules — clients, tools and events (ADR 0044/0046)
The Servarr apps (TV, movies), each self-contained from hal's shared arr client. Tools: status, library, search, queue, calendar. Events by polling the download queue: a new item emits module.<app>.<thing>.grabbed, an item that leaves as completed emits module.<app>.download.completed — the exact key plex consumes to rescan. A grab that left as failed/warning is not reported as a completion. Both typecheck; manifests parse.
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
// sonarr's tools — ported from the shared hal sdk (novox/hq ADR 0044), importing sonarr'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 { SonarrClient } from "../client.js";
|
||||
|
||||
export function getSonarrTools(sonarr: SonarrClient): ToolDefinition[] {
|
||||
return [
|
||||
{
|
||||
name: "sonarr_status",
|
||||
description: "Sonarr status overview: version, series count, monitored count, queue size.",
|
||||
input: {},
|
||||
run: async () => {
|
||||
const [status, content, queue] = await Promise.all([
|
||||
sonarr.getStatus(),
|
||||
sonarr.getContent(),
|
||||
sonarr.getQueue(),
|
||||
]);
|
||||
return {
|
||||
app: status.appName,
|
||||
version: status.version,
|
||||
series: content.length,
|
||||
monitored: content.filter((c) => c.monitored).length,
|
||||
queue: queue.totalRecords,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sonarr_library",
|
||||
description: "List series from the Sonarr library.",
|
||||
input: { limit: { type: "number", description: "max items to return (default 50)" } },
|
||||
run: async (args) => {
|
||||
const items = await sonarr.getContent(args.limit ? Number(args.limit) : 50);
|
||||
return { count: items.length, series: items };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sonarr_search",
|
||||
description: "Search the Sonarr library for series by title (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 sonarr.searchContent(query) };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sonarr_queue",
|
||||
description: "Show the Sonarr download queue — what is downloading and how far along.",
|
||||
input: {},
|
||||
run: async () => {
|
||||
const queue = await sonarr.getQueue();
|
||||
return { count: queue.totalRecords, items: queue.items };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sonarr_calendar",
|
||||
description: "Upcoming episode releases from the Sonarr 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 sonarr.getCalendar(days);
|
||||
items.sort((a, b) => a.date.localeCompare(b.date));
|
||||
return { days, count: items.length, items };
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// The tools exist only when Sonarr is configured; without a URL and key, sonarr contributes none
|
||||
// rather than failing the whole runtime.
|
||||
registerModuleTools("sonarr", (env) => {
|
||||
try {
|
||||
return getSonarrTools(SonarrClient.fromEnv(env));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user