Files
mesh-media-catalog/modules/radarr/tools/index.ts
T
jschoubben 26a8ae3b88 radarr, sonarr, lidarr: control tools — look up, add and remove
Each gains _lookup (the metadata source, by title or by id), _add (by TMDb,
TVDb or MusicBrainz id, with the first quality/metadata profile and root
folder unless named, monitored, searching only when asked) and _remove (by
the app's id, files kept unless asked).
2026-10-01 14:26:17 +02:00

116 lines
4.7 KiB
TypeScript

// radarr's tools — ported from the shared hal sdk (novox/hq ADR 0039), importing radarr'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 { RadarrClient } from "../client.js";
export function getRadarrTools(radarr: RadarrClient): ToolDefinition[] {
return [
{
name: "radarr_status",
description: "Radarr status overview: version, movie count, monitored count, queue size.",
input: {},
run: async () => {
const [status, content, queue] = await Promise.all([
radarr.getStatus(),
radarr.getContent(),
radarr.getQueue(),
]);
return {
app: status.appName,
version: status.version,
movies: content.length,
monitored: content.filter((c) => c.monitored).length,
queue: queue.totalRecords,
};
},
},
{
name: "radarr_library",
description: "List movies from the Radarr library.",
input: { limit: { type: "number", description: "max items to return (default 50)" } },
run: async (args) => {
const items = await radarr.getContent(args.limit ? Number(args.limit) : 50);
return { count: items.length, movies: items };
},
},
{
name: "radarr_search",
description: "Search the Radarr library for movies 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 radarr.searchContent(query) };
},
},
{
name: "radarr_queue",
description: "Show the Radarr download queue — what is downloading and how far along.",
input: {},
run: async () => {
const queue = await radarr.getQueue();
return { count: queue.totalRecords, items: queue.items };
},
},
{
name: "radarr_calendar",
description: "Upcoming movie releases from the Radarr 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 radarr.getCalendar(days);
items.sort((a, b) => a.date.localeCompare(b.date));
return { days, count: items.length, items };
},
},
{
name: "radarr_lookup",
description: "Look a movie up outside the library, on Radarr's metadata source: title, year, TMDb id and whether it is already in the library.",
input: { query: { type: "string", description: "a title, or `tmdb:<id>`" } },
run: async (args) => {
const query = String(args.query);
return { query, results: await radarr.lookup(query) };
},
},
{
name: "radarr_add",
description: "Add a movie to Radarr by its TMDb id (from radarr_lookup). Uses the first quality profile and root folder unless named. Does not search the indexers unless `search` is true.",
input: {
id: { type: "string", description: "the TMDb id" },
qualityProfile: { type: "string", description: "a quality profile's name (default: the first)" },
rootFolder: { type: "string", description: "a root folder path (default: the first)" },
monitored: { type: "boolean", description: "monitor it (default true)" },
search: { type: "boolean", description: "search the indexers right away (default false)" },
},
run: async (args) => radarr.add(String(args.id), {
qualityProfile: args.qualityProfile ? String(args.qualityProfile) : undefined,
rootFolder: args.rootFolder ? String(args.rootFolder) : undefined,
monitored: args.monitored === undefined ? undefined : Boolean(args.monitored),
search: Boolean(args.search),
}),
},
{
name: "radarr_remove",
description: "Remove a movie from Radarr's library by its Radarr id. Files stay on disk unless `deleteFiles` is true.",
input: {
id: { type: "number", description: "the Radarr id of the movie" },
deleteFiles: { type: "boolean", description: "also delete its files (default false)" },
},
run: async (args) => radarr.remove(Number(args.id), Boolean(args.deleteFiles)),
},
];
}
// The tools exist only when Radarr is configured; without a URL and key, radarr contributes none
// rather than failing the whole runtime.
registerModuleTools("radarr", (env) => {
try {
return getRadarrTools(RadarrClient.fromEnv(env));
} catch {
return [];
}
});