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.
116 lines
5.1 KiB
TypeScript
116 lines
5.1 KiB
TypeScript
// lidarr's tools — ported from the shared hal sdk (novox/hq ADR 0039), 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 (every field Lidarr keeps, plus where each of the first ten hits is in Plex: quality, added, watched, link) 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 };
|
|
},
|
|
},
|
|
{
|
|
name: "lidarr_lookup",
|
|
description: "Look a artist up outside the library, on Lidarr's metadata source: title, year, MusicBrainz artist id and whether it is already in the library.",
|
|
input: { query: { type: "string", description: "a title, or `lidarr:<id>`" } },
|
|
run: async (args) => {
|
|
const query = String(args.query);
|
|
return { query, results: await lidarr.lookup(query) };
|
|
},
|
|
},
|
|
{
|
|
name: "lidarr_add",
|
|
description: "Add a artist to Lidarr by its MusicBrainz artist id (from lidarr_lookup). The first quality and metadata profiles stand 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 MusicBrainz artist id" },
|
|
qualityProfile: { type: "string", description: "a quality profile's name (default: the first)" },
|
|
metadataProfile: { type: "string", description: "a metadata 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) => lidarr.add(String(args.id), {
|
|
qualityProfile: args.qualityProfile ? String(args.qualityProfile) : undefined,
|
|
metadataProfile: args.metadataProfile ? String(args.metadataProfile) : undefined,
|
|
rootFolder: args.rootFolder ? String(args.rootFolder) : undefined,
|
|
monitored: args.monitored === undefined ? undefined : Boolean(args.monitored),
|
|
search: Boolean(args.search),
|
|
}),
|
|
},
|
|
{
|
|
name: "lidarr_remove",
|
|
description: "Remove a artist from Lidarr's library by its Lidarr id. Files stay on disk unless `deleteFiles` is true.",
|
|
input: {
|
|
id: { type: "number", description: "the Lidarr id of the artist" },
|
|
deleteFiles: { type: "boolean", description: "also delete its files (default false)" },
|
|
},
|
|
run: async (args) => lidarr.remove(Number(args.id), Boolean(args.deleteFiles)),
|
|
},
|
|
];
|
|
}
|
|
|
|
// 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 [];
|
|
}
|
|
});
|