- black_* MCP tools drive mpv-on-DRM on black over SSH (mirrors transmission_*) - black-tv.sh owns one mpv + IPC socket; display bring-up via nouveau atomic KMS - boot-persistence systemd unit + nouveau atomic=1 modprobe.d - README documents the black_* control surface Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
129 lines
5.1 KiB
TypeScript
129 lines
5.1 KiB
TypeScript
import { transmissionAdd, transmissionInfo, transmissionList, transmissionRemove } from "./client.ts";
|
|
import { searchTorrents } from "./search.ts";
|
|
|
|
export const TRANSMISSION_TOOLS = [
|
|
{
|
|
name: "torrent_search",
|
|
description: "Search for torrents across ThePirateBay, Nyaa, and 1337x (via FlareSolverr). Returns results with filename, source, size, seed/leech counts, and magnet links ready to pass to transmission_add. FlareSolverr must be running on localhost:8191 for 1337x results.",
|
|
inputSchema: {
|
|
type: "object" as const,
|
|
properties: {
|
|
query: {
|
|
type: "string" as const,
|
|
description: "Search query. For TV: 'show name sXX' or 'show name season N complete'. Add '1080p' for HD.",
|
|
},
|
|
limit: {
|
|
type: "number" as const,
|
|
description: "Max results to return (default 15, max 30).",
|
|
},
|
|
},
|
|
required: ["query"],
|
|
},
|
|
},
|
|
{
|
|
name: "transmission_add",
|
|
description: "Add one or more magnet links to Transmission on black. Pass magnets as an array. Returns the server response for each.",
|
|
inputSchema: {
|
|
type: "object" as const,
|
|
properties: {
|
|
magnets: {
|
|
type: "array" as const,
|
|
items: { type: "string" as const },
|
|
description: "Magnet URIs (magnet:?xt=urn:btih:...) to add.",
|
|
},
|
|
},
|
|
required: ["magnets"],
|
|
},
|
|
},
|
|
{
|
|
name: "transmission_list",
|
|
description: "List all torrents in Transmission on black with their ID, progress, status, and name. Optionally filter by a name substring.",
|
|
inputSchema: {
|
|
type: "object" as const,
|
|
properties: {
|
|
filter: {
|
|
type: "string" as const,
|
|
description: "Optional case-insensitive substring to filter torrent names.",
|
|
},
|
|
status: {
|
|
type: "string" as const,
|
|
enum: ["all", "downloading", "seeding", "idle", "stopped"] as const,
|
|
description: "Filter by status (default: all).",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "transmission_info",
|
|
description: "Get detailed info about a single torrent by its numeric ID.",
|
|
inputSchema: {
|
|
type: "object" as const,
|
|
properties: {
|
|
id: { type: "number" as const, description: "Torrent ID from transmission_list." },
|
|
},
|
|
required: ["id"],
|
|
},
|
|
},
|
|
{
|
|
name: "transmission_remove",
|
|
description: "Remove a torrent by ID. Set delete_data=true to also delete the downloaded files.",
|
|
inputSchema: {
|
|
type: "object" as const,
|
|
properties: {
|
|
id: { type: "number" as const, description: "Torrent ID from transmission_list." },
|
|
delete_data: { type: "boolean" as const, description: "Also delete downloaded files (default false)." },
|
|
},
|
|
required: ["id"],
|
|
},
|
|
},
|
|
] as const;
|
|
|
|
export function dispatchTransmission(name: string, args: Record<string, unknown>): unknown {
|
|
try {
|
|
switch (name) {
|
|
case "torrent_search": {
|
|
const query = args["query"];
|
|
if (typeof query !== "string" || query.trim().length === 0) throw new Error("query must be a non-empty string");
|
|
const rawLimit = args["limit"];
|
|
const limit = rawLimit === undefined ? 15 : typeof rawLimit === "number" ? Math.min(30, Math.max(1, Math.floor(rawLimit))) : (() => { throw new Error("limit must be a number"); })();
|
|
return searchTorrents(query.trim(), limit);
|
|
}
|
|
case "transmission_add": {
|
|
const magnets = args["magnets"];
|
|
if (!Array.isArray(magnets) || magnets.length === 0) throw new Error("magnets must be a non-empty array");
|
|
const results: Array<{ magnet: string; result: string }> = [];
|
|
for (const m of magnets) {
|
|
if (typeof m !== "string" || !m.startsWith("magnet:")) throw new Error(`invalid magnet: ${String(m).slice(0, 80)}`);
|
|
results.push({ magnet: m.slice(0, 80) + "…", result: transmissionAdd(m) });
|
|
}
|
|
return results;
|
|
}
|
|
case "transmission_list": {
|
|
const filter = typeof args["filter"] === "string" ? args["filter"].toLowerCase() : null;
|
|
const statusFilter = typeof args["status"] === "string" ? args["status"] : "all";
|
|
let rows = transmissionList();
|
|
if (filter) rows = rows.filter(r => r.name.toLowerCase().includes(filter));
|
|
if (statusFilter !== "all") {
|
|
const s = statusFilter.toLowerCase();
|
|
rows = rows.filter(r => r.status.toLowerCase().includes(s));
|
|
}
|
|
return rows;
|
|
}
|
|
case "transmission_info": {
|
|
const id = args["id"];
|
|
if (typeof id !== "number" || !Number.isFinite(id)) throw new Error("id must be a number");
|
|
return transmissionInfo(Math.floor(id));
|
|
}
|
|
case "transmission_remove": {
|
|
const id = args["id"];
|
|
if (typeof id !== "number" || !Number.isFinite(id)) throw new Error("id must be a number");
|
|
return transmissionRemove(Math.floor(id), args["delete_data"] === true);
|
|
}
|
|
default:
|
|
throw new Error(`unknown transmission tool: ${name}`);
|
|
}
|
|
} catch (err) {
|
|
if (err instanceof Error) throw err;
|
|
throw new Error(String(err));
|
|
}
|
|
}
|