Files
mesh-media-catalog/modules/radarr/tools/index.ts
T
jschoubben 9ffd0dd96a radarr, sonarr, lidarr: a search tells everything the app keeps, and where the item is in Plex
The library search returned title, year, status and monitored. It now
returns every field the app keeps (file on disk, quality, size, path,
counts, ratings, genres, added…), and each module binds plex-api so the
first ten hits carry Plex's view: rating key, library, resolution, added,
watched, and a link that opens the item in Plex.
2026-10-01 15:54:20 +02:00

116 lines
4.8 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 (every field Radarr keeps, plus where each of the first ten hits is in Plex: quality, added, watched, link) 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). The first quality profile stands in unless named; the root folder must be named when several exist. 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 (required when the app has several)" },
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 [];
}
});