tsarr 2.3.2 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -1471,6 +1471,14 @@ var getApiV3SystemBackup = (options) => (options?.client ?? client).get({
1471
1471
  }],
1472
1472
  url: "/api/v3/customformat/schema",
1473
1473
  ...options
1474
+ }), getApiV3WantedCutoff = (options) => (options?.client ?? client).get({
1475
+ security: [{ name: "X-Api-Key", type: "apiKey" }, {
1476
+ in: "query",
1477
+ name: "apikey",
1478
+ type: "apiKey"
1479
+ }],
1480
+ url: "/api/v3/wanted/cutoff",
1481
+ ...options
1474
1482
  }), getApiV3Diskspace = (options) => (options?.client ?? client).get({
1475
1483
  security: [{ name: "X-Api-Key", type: "apiKey" }, {
1476
1484
  in: "query",
@@ -1867,6 +1875,14 @@ var getApiV3SystemBackup = (options) => (options?.client ?? client).get({
1867
1875
  "Content-Type": "application/json",
1868
1876
  ...options.headers
1869
1877
  }
1878
+ }), getApiV3WantedMissing = (options) => (options?.client ?? client).get({
1879
+ security: [{ name: "X-Api-Key", type: "apiKey" }, {
1880
+ in: "query",
1881
+ name: "apikey",
1882
+ type: "apiKey"
1883
+ }],
1884
+ url: "/api/v3/wanted/missing",
1885
+ ...options
1870
1886
  }), getApiV3Movie = (options) => (options?.client ?? client).get({
1871
1887
  security: [{ name: "X-Api-Key", type: "apiKey" }, {
1872
1888
  in: "query",
@@ -2370,8 +2386,11 @@ class RadarrClient {
2370
2386
  async updateMovie(id, movie) {
2371
2387
  return putApiV3MovieById({ path: { id: String(id) }, body: movie });
2372
2388
  }
2373
- async deleteMovie(id) {
2374
- return deleteApiV3MovieById({ path: { id } });
2389
+ async deleteMovie(id, options) {
2390
+ return deleteApiV3MovieById({
2391
+ path: { id },
2392
+ ...options ? { query: options } : {}
2393
+ });
2375
2394
  }
2376
2395
  async searchMovies(term) {
2377
2396
  return getApiV3MovieLookup({ query: { term } });
@@ -2700,6 +2719,22 @@ class RadarrClient {
2700
2719
  async removeBlocklistItemsBulk(ids) {
2701
2720
  return deleteApiV3BlocklistBulk({ body: { ids } });
2702
2721
  }
2722
+ async getWantedMissing(page, pageSize) {
2723
+ const query = {};
2724
+ if (page !== undefined)
2725
+ query.page = page;
2726
+ if (pageSize !== undefined)
2727
+ query.pageSize = pageSize;
2728
+ return getApiV3WantedMissing(Object.keys(query).length > 0 ? { query } : {});
2729
+ }
2730
+ async getWantedCutoff(page, pageSize) {
2731
+ const query = {};
2732
+ if (page !== undefined)
2733
+ query.page = page;
2734
+ if (pageSize !== undefined)
2735
+ query.pageSize = pageSize;
2736
+ return getApiV3WantedCutoff(Object.keys(query).length > 0 ? { query } : {});
2737
+ }
2703
2738
  async getHostConfig() {
2704
2739
  return getApiV3ConfigHost();
2705
2740
  }
@@ -4810,6 +4845,10 @@ function formatOutput(data, options) {
4810
4845
  if (data == null) {
4811
4846
  return;
4812
4847
  }
4848
+ if (isMessageOnly(data) && options.format !== "json" && options.format !== "quiet") {
4849
+ console.log(data.message);
4850
+ return;
4851
+ }
4813
4852
  switch (options.format) {
4814
4853
  case "json": {
4815
4854
  const output = options.select ? selectFields(data, options.select) : data;
@@ -4901,6 +4940,13 @@ function formatHeader(col) {
4901
4940
  function formatCell(column, value) {
4902
4941
  if (value == null)
4903
4942
  return "—";
4943
+ const flattened = flattenStructuredValue(value);
4944
+ if (flattened !== undefined) {
4945
+ if (column === "status") {
4946
+ return formatStatus(flattened);
4947
+ }
4948
+ return flattened;
4949
+ }
4904
4950
  if (typeof value === "boolean") {
4905
4951
  return value ? `${GREEN}✓${RESET}` : `${RED}✗${RESET}`;
4906
4952
  }
@@ -4913,8 +4959,6 @@ function formatCell(column, value) {
4913
4959
  if (column.toLowerCase().includes("date") || column === "createdAt" || column === "updatedAt") {
4914
4960
  return formatDate(value);
4915
4961
  }
4916
- if (typeof value === "object")
4917
- return JSON.stringify(value);
4918
4962
  return String(value);
4919
4963
  }
4920
4964
  function formatCellPlain(value) {
@@ -4922,10 +4966,46 @@ function formatCellPlain(value) {
4922
4966
  return "";
4923
4967
  if (typeof value === "boolean")
4924
4968
  return value ? "true" : "false";
4969
+ const flattened = flattenStructuredValue(value);
4970
+ if (flattened !== undefined)
4971
+ return flattened;
4925
4972
  if (typeof value === "object")
4926
4973
  return JSON.stringify(value);
4927
4974
  return String(value);
4928
4975
  }
4976
+ function flattenStructuredValue(value) {
4977
+ if (Array.isArray(value)) {
4978
+ if (value.length === 0)
4979
+ return "—";
4980
+ const flattened = value.map((item) => getObjectLabel(item) ?? (isPrimitive(item) ? String(item) : undefined)).filter((item) => typeof item === "string" && item.length > 0);
4981
+ if (flattened.length === value.length) {
4982
+ return flattened.join(", ");
4983
+ }
4984
+ return `${value.length} item${value.length === 1 ? "" : "s"}`;
4985
+ }
4986
+ if (typeof value === "object") {
4987
+ return getObjectLabel(value) ?? JSON.stringify(value);
4988
+ }
4989
+ return;
4990
+ }
4991
+ function getObjectLabel(value) {
4992
+ if (value == null || typeof value !== "object" || Array.isArray(value)) {
4993
+ return;
4994
+ }
4995
+ const record = value;
4996
+ for (const key of ["name", "title", "label", "path", "value"]) {
4997
+ if (typeof record[key] === "string" || typeof record[key] === "number") {
4998
+ return String(record[key]);
4999
+ }
5000
+ }
5001
+ return;
5002
+ }
5003
+ function isPrimitive(value) {
5004
+ return ["string", "number", "boolean", "bigint", "symbol"].includes(typeof value);
5005
+ }
5006
+ function isMessageOnly(data) {
5007
+ return typeof data === "object" && data !== null && !Array.isArray(data) && Object.keys(data).length === 1 && typeof data.message === "string";
5008
+ }
4929
5009
  function formatStatus(status) {
4930
5010
  const lower = status.toLowerCase();
4931
5011
  if (lower === "ok" || lower === "available" || lower === "ended" || lower === "continuing") {
@@ -4963,20 +5043,10 @@ function stripAnsi3(str) {
4963
5043
  return str.replace(ANSI_PATTERN, "");
4964
5044
  }
4965
5045
  function truncateWithAnsi(str, maxWidth) {
4966
- let visible = 0;
4967
- let i2 = 0;
4968
- while (i2 < str.length && visible < maxWidth - 1) {
4969
- if (str[i2] === ESC) {
4970
- const end = str.indexOf("m", i2);
4971
- if (end !== -1) {
4972
- i2 = end + 1;
4973
- continue;
4974
- }
4975
- }
4976
- visible++;
4977
- i2++;
4978
- }
4979
- return `${str.slice(0, i2)}…${RESET}`;
5046
+ const plain = stripAnsi3(str);
5047
+ if (plain.length <= maxWidth)
5048
+ return str;
5049
+ return `${plain.slice(0, Math.max(0, maxWidth - 1))}…`;
4980
5050
  }
4981
5051
  var ESC, GREEN, RED, YELLOW, RESET, ANSI_PATTERN;
4982
5052
  var init_output = __esm(() => {
@@ -5005,6 +5075,7 @@ function buildServiceCommand(serviceName, description, clientFactory, resources)
5005
5075
  plain: { type: "boolean", description: "Output as TSV (no colors, for piping)" },
5006
5076
  quiet: { type: "boolean", alias: "q", description: "Output IDs only" },
5007
5077
  "no-header": { type: "boolean", description: "Hide table header row" },
5078
+ "dry-run": { type: "boolean", description: "Show what would happen without executing" },
5008
5079
  select: {
5009
5080
  type: "string",
5010
5081
  description: "Cherry-pick fields (comma-separated, JSON mode)"
@@ -5012,7 +5083,7 @@ function buildServiceCommand(serviceName, description, clientFactory, resources)
5012
5083
  yes: { type: "boolean", alias: "y", description: "Skip confirmation prompts" },
5013
5084
  ...(action.args ?? []).reduce((acc, arg) => {
5014
5085
  acc[arg.name] = {
5015
- type: arg.type === "number" ? "string" : "string",
5086
+ type: arg.type === "boolean" ? "boolean" : arg.type === "number" ? "string" : "string",
5016
5087
  description: arg.description,
5017
5088
  required: false
5018
5089
  };
@@ -5038,8 +5109,20 @@ function buildServiceCommand(serviceName, description, clientFactory, resources)
5038
5109
  consola.error(`${argDef.name} must be a number.`);
5039
5110
  process.exit(1);
5040
5111
  }
5112
+ } else if (argDef.type === "boolean" && resolvedArgs[argDef.name] != null) {
5113
+ resolvedArgs[argDef.name] = coerceBooleanArg(resolvedArgs[argDef.name]);
5041
5114
  }
5042
5115
  }
5116
+ const format = detectFormat(args);
5117
+ const noHeader = process.argv.includes("--no-header") || !!args.noHeader;
5118
+ const dryRun = !!(args["dry-run"] ?? args.dryRun ?? process.argv.includes("--dry-run"));
5119
+ if (dryRun && isWriteAction(action.name)) {
5120
+ formatOutput(buildDryRunPreview(format, serviceName, resource.name, action.name, resolvedArgs), {
5121
+ format,
5122
+ noHeader
5123
+ });
5124
+ return;
5125
+ }
5043
5126
  if (action.confirmMessage) {
5044
5127
  const confirmed = await promptConfirm(action.confirmMessage, !!args.yes);
5045
5128
  if (!confirmed) {
@@ -5068,12 +5151,11 @@ Run \`tsarr config init\` or set TSARR_${serviceName.toUpperCase()}_API_KEY`);
5068
5151
  if (result?.records !== undefined && Array.isArray(result.records)) {
5069
5152
  result = result.records;
5070
5153
  }
5071
- const format = detectFormat(args);
5072
5154
  formatOutput(result, {
5073
5155
  format,
5074
5156
  columns: action.columns,
5075
5157
  idField: action.idField,
5076
- noHeader: !!args["no-header"],
5158
+ noHeader,
5077
5159
  select: args.select
5078
5160
  });
5079
5161
  } catch (error) {
@@ -5098,6 +5180,53 @@ Run \`tsarr config init\` or set TSARR_${serviceName.toUpperCase()}_API_KEY`);
5098
5180
  subCommands
5099
5181
  });
5100
5182
  }
5183
+ function coerceBooleanArg(value) {
5184
+ if (typeof value === "boolean")
5185
+ return value;
5186
+ if (typeof value === "string") {
5187
+ const normalized = value.trim().toLowerCase();
5188
+ if (normalized === "true")
5189
+ return true;
5190
+ if (normalized === "false")
5191
+ return false;
5192
+ }
5193
+ return Boolean(value);
5194
+ }
5195
+ function isWriteAction(actionName) {
5196
+ return [
5197
+ "add",
5198
+ "create",
5199
+ "delete",
5200
+ "edit",
5201
+ "refresh",
5202
+ "manual-search",
5203
+ "grab",
5204
+ "sync",
5205
+ "test",
5206
+ "search"
5207
+ ].includes(actionName);
5208
+ }
5209
+ function buildDryRunPreview(format, serviceName, resourceName, actionName, args) {
5210
+ const filteredArgs = Object.fromEntries(Object.entries(args).filter(([key, value]) => value !== undefined && key !== "_" && key !== "json" && key !== "table" && key !== "plain" && key !== "quiet" && key !== "select" && key !== "no-header" && key !== "noHeader" && key !== "dry-run" && key !== "dryRun"));
5211
+ if (format === "json") {
5212
+ return {
5213
+ dryRun: true,
5214
+ service: serviceName,
5215
+ resource: resourceName,
5216
+ action: actionName,
5217
+ args: filteredArgs
5218
+ };
5219
+ }
5220
+ return {
5221
+ message: `Dry run: would execute ${serviceName} ${resourceName} ${actionName}${formatDryRunArgs(filteredArgs)}`
5222
+ };
5223
+ }
5224
+ function formatDryRunArgs(args) {
5225
+ const entries = Object.entries(args);
5226
+ if (entries.length === 0)
5227
+ return "";
5228
+ return ` with ${entries.map(([key, value]) => `${key}=${String(value)}`).join(", ")}`;
5229
+ }
5101
5230
  function handleError(error, serviceName) {
5102
5231
  if (error instanceof ApiKeyError) {
5103
5232
  consola.error(`API key error: ${error.message}
@@ -5130,6 +5259,51 @@ var exports_radarr3 = {};
5130
5259
  __export(exports_radarr3, {
5131
5260
  radarr: () => radarr
5132
5261
  });
5262
+ import { readFileSync as readFileSync2 } from "node:fs";
5263
+ function unwrapData(result) {
5264
+ return result?.data ?? result;
5265
+ }
5266
+ function parseBooleanArg(value, fallback) {
5267
+ if (value === undefined)
5268
+ return fallback;
5269
+ if (typeof value === "boolean")
5270
+ return value;
5271
+ if (typeof value === "string") {
5272
+ const normalized = value.trim().toLowerCase();
5273
+ if (normalized === "true")
5274
+ return true;
5275
+ if (normalized === "false")
5276
+ return false;
5277
+ }
5278
+ return Boolean(value);
5279
+ }
5280
+ function resolveQualityProfileId(profiles, profileId) {
5281
+ const profile = profiles.find((item) => item?.id === profileId);
5282
+ if (!profile) {
5283
+ throw new Error(`Quality profile ${profileId} was not found.`);
5284
+ }
5285
+ return profileId;
5286
+ }
5287
+ function resolveRootFolderPath(folders, rootFolderPath) {
5288
+ const folder = folders.find((item) => item?.path === rootFolderPath);
5289
+ if (!folder) {
5290
+ throw new Error(`Root folder "${rootFolderPath}" was not found.`);
5291
+ }
5292
+ return rootFolderPath;
5293
+ }
5294
+ async function findMovieByTmdbId(client2, tmdbId) {
5295
+ if (tmdbId === undefined)
5296
+ return;
5297
+ const movies = unwrapData(await client2.getMovies());
5298
+ return movies.find((movie) => movie?.tmdbId === tmdbId);
5299
+ }
5300
+ function getApiStatus(result) {
5301
+ return result?.error?.status ?? result?.response?.status;
5302
+ }
5303
+ function readJsonInput(filePath) {
5304
+ const raw = filePath === "-" ? readFileSync2(0, "utf-8") : readFileSync2(filePath, "utf-8");
5305
+ return JSON.parse(raw);
5306
+ }
5133
5307
  var resources, radarr;
5134
5308
  var init_radarr3 = __esm(() => {
5135
5309
  init_radarr2();
@@ -5150,6 +5324,7 @@ var init_radarr3 = __esm(() => {
5150
5324
  name: "get",
5151
5325
  description: "Get a movie by ID",
5152
5326
  args: [{ name: "id", description: "Movie ID", required: true, type: "number" }],
5327
+ columns: ["id", "title", "year", "monitored", "hasFile", "status", "qualityProfileId"],
5153
5328
  run: (c3, a2) => c3.getMovie(a2.id)
5154
5329
  },
5155
5330
  {
@@ -5163,40 +5338,68 @@ var init_radarr3 = __esm(() => {
5163
5338
  {
5164
5339
  name: "add",
5165
5340
  description: "Search and add a movie",
5166
- args: [{ name: "term", description: "Search term", required: true }],
5341
+ args: [
5342
+ { name: "term", description: "Search term" },
5343
+ { name: "tmdb-id", description: "TMDB ID", type: "number" },
5344
+ { name: "quality-profile-id", description: "Quality profile ID", type: "number" },
5345
+ { name: "root-folder", description: "Root folder path" },
5346
+ { name: "monitored", description: "Set monitored (true/false)" }
5347
+ ],
5167
5348
  run: async (c3, a2) => {
5168
- const searchResult = await c3.searchMovies(a2.term);
5169
- const results = searchResult?.data ?? searchResult;
5170
- if (!Array.isArray(results) || results.length === 0) {
5171
- throw new Error("No movies found.");
5172
- }
5173
- const movieId = await promptSelect("Select a movie:", results.map((m2) => ({ label: `${m2.title} (${m2.year})`, value: String(m2.tmdbId) })));
5174
- const movie = results.find((m2) => String(m2.tmdbId) === movieId);
5175
- if (!movie) {
5176
- throw new Error("Selected movie was not found in the search results.");
5349
+ let movie;
5350
+ if (a2["tmdb-id"] !== undefined) {
5351
+ const lookupResult = await c3.lookupMovieByTmdbId(a2["tmdb-id"]);
5352
+ const lookup = unwrapData(lookupResult);
5353
+ const matches = Array.isArray(lookup) ? lookup : [lookup];
5354
+ movie = matches.find((m2) => m2?.tmdbId === a2["tmdb-id"]) ?? matches[0];
5355
+ if (!movie) {
5356
+ throw new Error(`No movie found for TMDB ID ${a2["tmdb-id"]}.`);
5357
+ }
5358
+ } else {
5359
+ const term = await promptIfMissing(a2.term, "Search term:");
5360
+ const searchResult = await c3.searchMovies(term);
5361
+ const results = unwrapData(searchResult);
5362
+ if (!Array.isArray(results) || results.length === 0) {
5363
+ throw new Error("No movies found.");
5364
+ }
5365
+ const movieId = await promptSelect("Select a movie:", results.map((m2) => ({
5366
+ label: `${m2.title} (${m2.year})`,
5367
+ value: String(m2.tmdbId)
5368
+ })));
5369
+ movie = results.find((m2) => String(m2.tmdbId) === movieId);
5370
+ if (!movie) {
5371
+ throw new Error("Selected movie was not found in the search results.");
5372
+ }
5177
5373
  }
5178
5374
  const profilesResult = await c3.getQualityProfiles();
5179
- const profiles = profilesResult?.data ?? profilesResult;
5375
+ const profiles = unwrapData(profilesResult);
5180
5376
  if (!Array.isArray(profiles) || profiles.length === 0) {
5181
5377
  throw new Error("No quality profiles found. Configure one in Radarr first.");
5182
5378
  }
5183
- const profileId = await promptSelect("Select quality profile:", profiles.map((p) => ({ label: p.name, value: String(p.id) })));
5379
+ const profileId = a2["quality-profile-id"] !== undefined ? resolveQualityProfileId(profiles, a2["quality-profile-id"]) : Number(await promptSelect("Select quality profile:", profiles.map((p) => ({ label: p.name, value: String(p.id) }))));
5184
5380
  const foldersResult = await c3.getRootFolders();
5185
- const folders = foldersResult?.data ?? foldersResult;
5381
+ const folders = unwrapData(foldersResult);
5186
5382
  if (!Array.isArray(folders) || folders.length === 0) {
5187
5383
  throw new Error("No root folders found. Configure one in Radarr first.");
5188
5384
  }
5189
- const rootFolderPath = await promptSelect("Select root folder:", folders.map((f3) => ({ label: f3.path, value: f3.path })));
5385
+ const rootFolderPath = a2["root-folder"] !== undefined ? resolveRootFolderPath(folders, a2["root-folder"]) : await promptSelect("Select root folder:", folders.map((f3) => ({ label: f3.path, value: f3.path })));
5190
5386
  const confirmed = await promptConfirm(`Add "${movie.title} (${movie.year})"?`, !!a2.yes);
5191
5387
  if (!confirmed)
5192
5388
  throw new Error("Cancelled.");
5193
- return c3.addMovie({
5389
+ const addResult = await c3.addMovie({
5194
5390
  ...movie,
5195
- qualityProfileId: Number(profileId),
5391
+ qualityProfileId: profileId,
5196
5392
  rootFolderPath,
5197
- monitored: true,
5393
+ monitored: parseBooleanArg(a2.monitored, true),
5198
5394
  addOptions: { searchForMovie: true }
5199
5395
  });
5396
+ if (addResult?.error && getApiStatus(addResult) === 400) {
5397
+ const existingMovie = await findMovieByTmdbId(c3, movie.tmdbId);
5398
+ if (existingMovie) {
5399
+ throw new Error(`${existingMovie.title} is already in your library (ID: ${existingMovie.id})`);
5400
+ }
5401
+ }
5402
+ return addResult;
5200
5403
  }
5201
5404
  },
5202
5405
  {
@@ -5236,9 +5439,29 @@ var init_radarr3 = __esm(() => {
5236
5439
  {
5237
5440
  name: "delete",
5238
5441
  description: "Delete a movie",
5239
- args: [{ name: "id", description: "Movie ID", required: true, type: "number" }],
5442
+ args: [
5443
+ { name: "id", description: "Movie ID", required: true, type: "number" },
5444
+ { name: "delete-files", description: "Delete movie files", type: "boolean" },
5445
+ {
5446
+ name: "add-import-exclusion",
5447
+ description: "Add import exclusion after delete",
5448
+ type: "boolean"
5449
+ }
5450
+ ],
5240
5451
  confirmMessage: "Are you sure you want to delete this movie?",
5241
- run: (c3, a2) => c3.deleteMovie(a2.id)
5452
+ run: async (c3, a2) => {
5453
+ const movieResult = await c3.getMovie(a2.id);
5454
+ if (movieResult?.error)
5455
+ return movieResult;
5456
+ const movie = unwrapData(movieResult);
5457
+ const deleteResult = await c3.deleteMovie(a2.id, {
5458
+ deleteFiles: a2["delete-files"],
5459
+ addImportExclusion: a2["add-import-exclusion"]
5460
+ });
5461
+ if (deleteResult?.error)
5462
+ return deleteResult;
5463
+ return { message: `Deleted: ${movie.title} (ID: ${movie.id})` };
5464
+ }
5242
5465
  }
5243
5466
  ]
5244
5467
  },
@@ -5264,6 +5487,28 @@ var init_radarr3 = __esm(() => {
5264
5487
  name: "tag",
5265
5488
  description: "Manage tags",
5266
5489
  actions: [
5490
+ {
5491
+ name: "create",
5492
+ description: "Create a tag",
5493
+ args: [{ name: "label", description: "Tag label", required: true }],
5494
+ run: (c3, a2) => c3.addTag({ label: a2.label })
5495
+ },
5496
+ {
5497
+ name: "delete",
5498
+ description: "Delete a tag",
5499
+ args: [{ name: "id", description: "Tag ID", required: true, type: "number" }],
5500
+ confirmMessage: "Are you sure you want to delete this tag?",
5501
+ run: async (c3, a2) => {
5502
+ const tagResult = await c3.getTag(a2.id);
5503
+ if (tagResult?.error)
5504
+ return tagResult;
5505
+ const tag = unwrapData(tagResult);
5506
+ const deleteResult = await c3.deleteTag(a2.id);
5507
+ if (deleteResult?.error)
5508
+ return deleteResult;
5509
+ return { message: `Deleted tag: ${tag.label} (ID: ${tag.id})` };
5510
+ }
5511
+ },
5267
5512
  {
5268
5513
  name: "list",
5269
5514
  description: "List all tags",
@@ -5286,6 +5531,27 @@ var init_radarr3 = __esm(() => {
5286
5531
  name: "status",
5287
5532
  description: "Get queue status",
5288
5533
  run: (c3) => c3.getQueueStatus()
5534
+ },
5535
+ {
5536
+ name: "delete",
5537
+ description: "Remove an item from the queue",
5538
+ args: [
5539
+ { name: "id", description: "Queue item ID", required: true, type: "number" },
5540
+ { name: "blocklist", description: "Add to blocklist", type: "boolean" },
5541
+ {
5542
+ name: "remove-from-client",
5543
+ description: "Remove from download client",
5544
+ type: "boolean"
5545
+ }
5546
+ ],
5547
+ confirmMessage: "Are you sure you want to remove this queue item?",
5548
+ run: (c3, a2) => c3.removeQueueItem(a2.id, a2["remove-from-client"], a2.blocklist)
5549
+ },
5550
+ {
5551
+ name: "grab",
5552
+ description: "Force download a queue item",
5553
+ args: [{ name: "id", description: "Queue item ID", required: true, type: "number" }],
5554
+ run: (c3, a2) => c3.grabQueueItem(a2.id)
5289
5555
  }
5290
5556
  ]
5291
5557
  },
@@ -5298,6 +5564,19 @@ var init_radarr3 = __esm(() => {
5298
5564
  description: "List root folders",
5299
5565
  columns: ["id", "path", "freeSpace"],
5300
5566
  run: (c3) => c3.getRootFolders()
5567
+ },
5568
+ {
5569
+ name: "add",
5570
+ description: "Add a root folder",
5571
+ args: [{ name: "path", description: "Folder path", required: true }],
5572
+ run: (c3, a2) => c3.addRootFolder(a2.path)
5573
+ },
5574
+ {
5575
+ name: "delete",
5576
+ description: "Delete a root folder",
5577
+ args: [{ name: "id", description: "Root folder ID", required: true, type: "number" }],
5578
+ confirmMessage: "Are you sure you want to delete this root folder?",
5579
+ run: (c3, a2) => c3.deleteRootFolder(a2.id)
5301
5580
  }
5302
5581
  ]
5303
5582
  },
@@ -5325,8 +5604,222 @@ var init_radarr3 = __esm(() => {
5325
5604
  {
5326
5605
  name: "list",
5327
5606
  description: "List recent history",
5607
+ args: [
5608
+ { name: "since", description: "Start date (ISO 8601, e.g. 2024-01-01)" },
5609
+ { name: "until", description: "End date (ISO 8601, e.g. 2024-12-31)" }
5610
+ ],
5328
5611
  columns: ["id", "eventType", "sourceTitle", "date"],
5329
- run: (c3) => c3.getHistory()
5612
+ run: async (c3, a2) => {
5613
+ if (a2.since) {
5614
+ const result2 = await c3.getHistorySince(a2.since);
5615
+ const items2 = unwrapData(result2);
5616
+ if (a2.until) {
5617
+ const untilDate = new Date(a2.until);
5618
+ return items2.filter((item) => new Date(item.date) <= untilDate);
5619
+ }
5620
+ return items2;
5621
+ }
5622
+ const result = await c3.getHistory();
5623
+ const items = unwrapData(result);
5624
+ if (a2.until) {
5625
+ const untilDate = new Date(a2.until);
5626
+ return items.filter((item) => new Date(item.date) <= untilDate);
5627
+ }
5628
+ return items;
5629
+ }
5630
+ }
5631
+ ]
5632
+ },
5633
+ {
5634
+ name: "calendar",
5635
+ description: "View upcoming releases",
5636
+ actions: [
5637
+ {
5638
+ name: "list",
5639
+ description: "List upcoming movie releases",
5640
+ args: [
5641
+ { name: "start", description: "Start date (ISO 8601)" },
5642
+ { name: "end", description: "End date (ISO 8601)" },
5643
+ { name: "unmonitored", description: "Include unmonitored", type: "boolean" }
5644
+ ],
5645
+ columns: ["id", "title", "year", "inCinemas", "digitalRelease", "physicalRelease"],
5646
+ run: (c3, a2) => c3.getCalendar(a2.start, a2.end, a2.unmonitored)
5647
+ }
5648
+ ]
5649
+ },
5650
+ {
5651
+ name: "notification",
5652
+ description: "Manage notifications",
5653
+ actions: [
5654
+ {
5655
+ name: "list",
5656
+ description: "List notification providers",
5657
+ columns: ["id", "name", "implementation"],
5658
+ run: (c3) => c3.getNotifications()
5659
+ },
5660
+ {
5661
+ name: "get",
5662
+ description: "Get a notification by ID",
5663
+ args: [{ name: "id", description: "Notification ID", required: true, type: "number" }],
5664
+ run: (c3, a2) => c3.getNotification(a2.id)
5665
+ },
5666
+ {
5667
+ name: "add",
5668
+ description: "Add a notification from JSON file or stdin",
5669
+ args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
5670
+ run: async (c3, a2) => c3.addNotification(readJsonInput(a2.file))
5671
+ },
5672
+ {
5673
+ name: "edit",
5674
+ description: "Edit a notification (merges JSON with existing)",
5675
+ args: [
5676
+ { name: "id", description: "Notification ID", required: true, type: "number" },
5677
+ { name: "file", description: "JSON file with fields to update", required: true }
5678
+ ],
5679
+ run: async (c3, a2) => {
5680
+ const existing = unwrapData(await c3.getNotification(a2.id));
5681
+ return c3.updateNotification(a2.id, { ...existing, ...readJsonInput(a2.file) });
5682
+ }
5683
+ },
5684
+ {
5685
+ name: "delete",
5686
+ description: "Delete a notification",
5687
+ args: [{ name: "id", description: "Notification ID", required: true, type: "number" }],
5688
+ confirmMessage: "Are you sure you want to delete this notification?",
5689
+ run: (c3, a2) => c3.deleteNotification(a2.id)
5690
+ },
5691
+ {
5692
+ name: "test",
5693
+ description: "Test all notifications",
5694
+ run: (c3) => c3.testAllNotifications()
5695
+ }
5696
+ ]
5697
+ },
5698
+ {
5699
+ name: "downloadclient",
5700
+ description: "Manage download clients",
5701
+ actions: [
5702
+ {
5703
+ name: "list",
5704
+ description: "List download clients",
5705
+ columns: ["id", "name", "implementation", "enable"],
5706
+ run: (c3) => c3.getDownloadClients()
5707
+ },
5708
+ {
5709
+ name: "get",
5710
+ description: "Get a download client by ID",
5711
+ args: [{ name: "id", description: "Download client ID", required: true, type: "number" }],
5712
+ run: (c3, a2) => c3.getDownloadClient(a2.id)
5713
+ },
5714
+ {
5715
+ name: "add",
5716
+ description: "Add a download client from JSON file or stdin",
5717
+ args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
5718
+ run: async (c3, a2) => c3.addDownloadClient(readJsonInput(a2.file))
5719
+ },
5720
+ {
5721
+ name: "edit",
5722
+ description: "Edit a download client (merges JSON with existing)",
5723
+ args: [
5724
+ { name: "id", description: "Download client ID", required: true, type: "number" },
5725
+ { name: "file", description: "JSON file with fields to update", required: true }
5726
+ ],
5727
+ run: async (c3, a2) => {
5728
+ const existing = unwrapData(await c3.getDownloadClient(a2.id));
5729
+ return c3.updateDownloadClient(a2.id, { ...existing, ...readJsonInput(a2.file) });
5730
+ }
5731
+ },
5732
+ {
5733
+ name: "delete",
5734
+ description: "Delete a download client",
5735
+ args: [{ name: "id", description: "Download client ID", required: true, type: "number" }],
5736
+ confirmMessage: "Are you sure you want to delete this download client?",
5737
+ run: (c3, a2) => c3.deleteDownloadClient(a2.id)
5738
+ },
5739
+ {
5740
+ name: "test",
5741
+ description: "Test all download clients",
5742
+ run: (c3) => c3.testAllDownloadClients()
5743
+ }
5744
+ ]
5745
+ },
5746
+ {
5747
+ name: "blocklist",
5748
+ description: "Manage blocked releases",
5749
+ actions: [
5750
+ {
5751
+ name: "list",
5752
+ description: "List blocked releases",
5753
+ columns: ["id", "sourceTitle", "date"],
5754
+ run: (c3) => c3.getBlocklist()
5755
+ },
5756
+ {
5757
+ name: "delete",
5758
+ description: "Remove a release from the blocklist",
5759
+ args: [{ name: "id", description: "Blocklist item ID", required: true, type: "number" }],
5760
+ confirmMessage: "Are you sure you want to remove this blocklist entry?",
5761
+ run: (c3, a2) => c3.removeBlocklistItem(a2.id)
5762
+ }
5763
+ ]
5764
+ },
5765
+ {
5766
+ name: "wanted",
5767
+ description: "View missing and cutoff unmet movies",
5768
+ actions: [
5769
+ {
5770
+ name: "missing",
5771
+ description: "List movies with missing files",
5772
+ columns: ["id", "title", "year", "monitored"],
5773
+ run: (c3) => c3.getWantedMissing()
5774
+ },
5775
+ {
5776
+ name: "cutoff",
5777
+ description: "List movies below quality cutoff",
5778
+ columns: ["id", "title", "year", "monitored"],
5779
+ run: (c3) => c3.getWantedCutoff()
5780
+ }
5781
+ ]
5782
+ },
5783
+ {
5784
+ name: "importlist",
5785
+ description: "Manage import lists",
5786
+ actions: [
5787
+ {
5788
+ name: "list",
5789
+ description: "List import lists",
5790
+ columns: ["id", "name", "implementation", "enable"],
5791
+ run: (c3) => c3.getImportLists()
5792
+ },
5793
+ {
5794
+ name: "get",
5795
+ description: "Get an import list by ID",
5796
+ args: [{ name: "id", description: "Import list ID", required: true, type: "number" }],
5797
+ run: (c3, a2) => c3.getImportList(a2.id)
5798
+ },
5799
+ {
5800
+ name: "add",
5801
+ description: "Add an import list from JSON file or stdin",
5802
+ args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
5803
+ run: async (c3, a2) => c3.addImportList(readJsonInput(a2.file))
5804
+ },
5805
+ {
5806
+ name: "edit",
5807
+ description: "Edit an import list (merges JSON with existing)",
5808
+ args: [
5809
+ { name: "id", description: "Import list ID", required: true, type: "number" },
5810
+ { name: "file", description: "JSON file with fields to update", required: true }
5811
+ ],
5812
+ run: async (c3, a2) => {
5813
+ const existing = unwrapData(await c3.getImportList(a2.id));
5814
+ return c3.updateImportList(a2.id, { ...existing, ...readJsonInput(a2.file) });
5815
+ }
5816
+ },
5817
+ {
5818
+ name: "delete",
5819
+ description: "Delete an import list",
5820
+ args: [{ name: "id", description: "Import list ID", required: true, type: "number" }],
5821
+ confirmMessage: "Are you sure you want to delete this import list?",
5822
+ run: (c3, a2) => c3.deleteImportList(a2.id)
5330
5823
  }
5331
5824
  ]
5332
5825
  },
@@ -7261,8 +7754,11 @@ class SonarrClient {
7261
7754
  async updateSeries(id, series) {
7262
7755
  return putApiV3SeriesById({ path: { id }, body: series });
7263
7756
  }
7264
- async deleteSeries(id) {
7265
- return deleteApiV3SeriesById({ path: { id } });
7757
+ async deleteSeries(id, options) {
7758
+ return deleteApiV3SeriesById({
7759
+ path: { id },
7760
+ ...options ? { query: options } : {}
7761
+ });
7266
7762
  }
7267
7763
  async getSeriesFolder(id) {
7268
7764
  return getApiV3SeriesByIdFolder({ path: { id } });
@@ -7385,8 +7881,13 @@ class SonarrClient {
7385
7881
  async getTagDetailById(id) {
7386
7882
  return getApiV3TagDetailById2({ path: { id } });
7387
7883
  }
7388
- async getEpisodes() {
7389
- return getApiV3Episode();
7884
+ async getEpisodes(seriesId, episodeIds) {
7885
+ const query = {};
7886
+ if (seriesId !== undefined)
7887
+ query.seriesId = seriesId;
7888
+ if (episodeIds !== undefined)
7889
+ query.episodeIds = episodeIds;
7890
+ return getApiV3Episode(Object.keys(query).length > 0 ? { query } : {});
7390
7891
  }
7391
7892
  async getEpisode(id) {
7392
7893
  return getApiV3EpisodeById({ path: { id } });
@@ -7600,7 +8101,7 @@ class SonarrClient {
7600
8101
  query.tags = tags;
7601
8102
  return getFeedV3CalendarSonarrIcs(Object.keys(query).length > 0 ? { query } : {});
7602
8103
  }
7603
- async getQueue(page, pageSize, sortKey, sortDirection, includeUnknownSeriesItems) {
8104
+ async getQueue(page, pageSize, sortKey, sortDirection, includeUnknownSeriesItems, seriesId) {
7604
8105
  const query = {};
7605
8106
  if (page !== undefined)
7606
8107
  query.page = page;
@@ -7612,6 +8113,8 @@ class SonarrClient {
7612
8113
  query.sortDirection = sortDirection;
7613
8114
  if (includeUnknownSeriesItems !== undefined)
7614
8115
  query.includeUnknownSeriesItems = includeUnknownSeriesItems;
8116
+ if (seriesId !== undefined)
8117
+ query.seriesIds = [seriesId];
7615
8118
  return getApiV3Queue2(Object.keys(query).length > 0 ? { query } : {});
7616
8119
  }
7617
8120
  async removeQueueItem(id, removeFromClient, blocklist) {
@@ -7734,6 +8237,70 @@ var exports_sonarr3 = {};
7734
8237
  __export(exports_sonarr3, {
7735
8238
  sonarr: () => sonarr
7736
8239
  });
8240
+ import { readFileSync as readFileSync3 } from "node:fs";
8241
+ function unwrapData2(result) {
8242
+ return result?.data ?? result;
8243
+ }
8244
+ function parseBooleanArg2(value, fallback) {
8245
+ if (value === undefined)
8246
+ return fallback;
8247
+ if (typeof value === "boolean")
8248
+ return value;
8249
+ if (typeof value === "string") {
8250
+ const normalized = value.trim().toLowerCase();
8251
+ if (normalized === "true")
8252
+ return true;
8253
+ if (normalized === "false")
8254
+ return false;
8255
+ }
8256
+ return Boolean(value);
8257
+ }
8258
+ function resolveQualityProfileId2(profiles, profileId) {
8259
+ const profile = profiles.find((item) => item?.id === profileId);
8260
+ if (!profile) {
8261
+ throw new Error(`Quality profile ${profileId} was not found.`);
8262
+ }
8263
+ return profileId;
8264
+ }
8265
+ function resolveRootFolderPath2(folders, rootFolderPath) {
8266
+ const folder = folders.find((item) => item?.path === rootFolderPath);
8267
+ if (!folder) {
8268
+ throw new Error(`Root folder "${rootFolderPath}" was not found.`);
8269
+ }
8270
+ return rootFolderPath;
8271
+ }
8272
+ function formatSeriesListItem(series) {
8273
+ const seasons = Array.isArray(series?.seasons) ? series.seasons.filter((season) => season?.seasonNumber !== 0) : [];
8274
+ const statistics = series?.statistics ?? {};
8275
+ return {
8276
+ ...series,
8277
+ seasonCount: seasons.length,
8278
+ episodeCount: statistics.episodeCount !== undefined ? `${statistics.episodeFileCount ?? 0}/${statistics.episodeCount}` : "—",
8279
+ network: series?.network,
8280
+ status: series?.status
8281
+ };
8282
+ }
8283
+ async function lookupSeriesByTvdbId(client3, tvdbId) {
8284
+ const tvdbSearch = unwrapData2(await client3.searchSeries(`tvdb:${tvdbId}`));
8285
+ const exactTvdbMatch = tvdbSearch.find((series) => series?.tvdbId === tvdbId);
8286
+ if (exactTvdbMatch)
8287
+ return exactTvdbMatch;
8288
+ const fallbackSearch = unwrapData2(await client3.searchSeries(String(tvdbId)));
8289
+ return fallbackSearch.find((series) => series?.tvdbId === tvdbId);
8290
+ }
8291
+ async function findSeriesByTvdbId(client3, tvdbId) {
8292
+ if (tvdbId === undefined)
8293
+ return;
8294
+ const series = unwrapData2(await client3.getSeries());
8295
+ return series.find((item) => item?.tvdbId === tvdbId);
8296
+ }
8297
+ function getApiStatus2(result) {
8298
+ return result?.error?.status ?? result?.response?.status;
8299
+ }
8300
+ function readJsonInput2(filePath) {
8301
+ const raw = filePath === "-" ? readFileSync3(0, "utf-8") : readFileSync3(filePath, "utf-8");
8302
+ return JSON.parse(raw);
8303
+ }
7737
8304
  var resources2, sonarr;
7738
8305
  var init_sonarr3 = __esm(() => {
7739
8306
  init_sonarr2();
@@ -7747,14 +8314,40 @@ var init_sonarr3 = __esm(() => {
7747
8314
  {
7748
8315
  name: "list",
7749
8316
  description: "List all series",
7750
- columns: ["id", "title", "year", "monitored", "seasonCount"],
7751
- run: (c3) => c3.getSeries()
8317
+ columns: [
8318
+ "id",
8319
+ "title",
8320
+ "year",
8321
+ "monitored",
8322
+ "seasonCount",
8323
+ "episodeCount",
8324
+ "network",
8325
+ "status"
8326
+ ],
8327
+ run: async (c3) => {
8328
+ const series = unwrapData2(await c3.getSeries());
8329
+ return series.map(formatSeriesListItem);
8330
+ }
7752
8331
  },
7753
8332
  {
7754
8333
  name: "get",
7755
8334
  description: "Get a series by ID",
7756
8335
  args: [{ name: "id", description: "Series ID", required: true, type: "number" }],
7757
- run: (c3, a2) => c3.getSeriesById(a2.id)
8336
+ columns: [
8337
+ "id",
8338
+ "title",
8339
+ "year",
8340
+ "monitored",
8341
+ "seasonCount",
8342
+ "episodeCount",
8343
+ "network",
8344
+ "status"
8345
+ ],
8346
+ run: async (c3, a2) => {
8347
+ const result = await c3.getSeriesById(a2.id);
8348
+ const series = unwrapData2(result);
8349
+ return formatSeriesListItem(series);
8350
+ }
7758
8351
  },
7759
8352
  {
7760
8353
  name: "search",
@@ -7766,136 +8359,502 @@ var init_sonarr3 = __esm(() => {
7766
8359
  {
7767
8360
  name: "add",
7768
8361
  description: "Search and add a series",
7769
- args: [{ name: "term", description: "Search term", required: true }],
8362
+ args: [
8363
+ { name: "term", description: "Search term" },
8364
+ { name: "tvdb-id", description: "TVDB ID", type: "number" },
8365
+ { name: "quality-profile-id", description: "Quality profile ID", type: "number" },
8366
+ { name: "root-folder", description: "Root folder path" },
8367
+ { name: "monitored", description: "Set monitored (true/false)" }
8368
+ ],
7770
8369
  run: async (c3, a2) => {
7771
- const searchResult = await c3.searchSeries(a2.term);
7772
- const results = searchResult?.data ?? searchResult;
7773
- if (!Array.isArray(results) || results.length === 0) {
7774
- throw new Error("No series found.");
7775
- }
7776
- const seriesId = await promptSelect("Select a series:", results.map((s2) => ({ label: `${s2.title} (${s2.year})`, value: String(s2.tvdbId) })));
7777
- const series = results.find((s2) => String(s2.tvdbId) === seriesId);
7778
- if (!series) {
7779
- throw new Error("Selected series was not found in the search results.");
8370
+ let series;
8371
+ if (a2["tvdb-id"] !== undefined) {
8372
+ series = await lookupSeriesByTvdbId(c3, a2["tvdb-id"]);
8373
+ if (!series) {
8374
+ throw new Error(`No series found for TVDB ID ${a2["tvdb-id"]}.`);
8375
+ }
8376
+ } else {
8377
+ const term = await promptIfMissing(a2.term, "Search term:");
8378
+ const searchResult = await c3.searchSeries(term);
8379
+ const results = unwrapData2(searchResult);
8380
+ if (!Array.isArray(results) || results.length === 0) {
8381
+ throw new Error("No series found.");
8382
+ }
8383
+ const seriesId = await promptSelect("Select a series:", results.map((s2) => ({
8384
+ label: `${s2.title} (${s2.year})`,
8385
+ value: String(s2.tvdbId)
8386
+ })));
8387
+ series = results.find((s2) => String(s2.tvdbId) === seriesId);
8388
+ if (!series) {
8389
+ throw new Error("Selected series was not found in the search results.");
8390
+ }
7780
8391
  }
7781
8392
  const profilesResult = await c3.getQualityProfiles();
7782
- const profiles = profilesResult?.data ?? profilesResult;
8393
+ const profiles = unwrapData2(profilesResult);
7783
8394
  if (!Array.isArray(profiles) || profiles.length === 0) {
7784
8395
  throw new Error("No quality profiles found. Configure one in Sonarr first.");
7785
8396
  }
7786
- const profileId = await promptSelect("Select quality profile:", profiles.map((p) => ({ label: p.name, value: String(p.id) })));
8397
+ const profileId = a2["quality-profile-id"] !== undefined ? resolveQualityProfileId2(profiles, a2["quality-profile-id"]) : Number(await promptSelect("Select quality profile:", profiles.map((p) => ({ label: p.name, value: String(p.id) }))));
7787
8398
  const foldersResult = await c3.getRootFolders();
7788
- const folders = foldersResult?.data ?? foldersResult;
8399
+ const folders = unwrapData2(foldersResult);
7789
8400
  if (!Array.isArray(folders) || folders.length === 0) {
7790
8401
  throw new Error("No root folders found. Configure one in Sonarr first.");
7791
8402
  }
7792
- const rootFolderPath = await promptSelect("Select root folder:", folders.map((f3) => ({ label: f3.path, value: f3.path })));
8403
+ const rootFolderPath = a2["root-folder"] !== undefined ? resolveRootFolderPath2(folders, a2["root-folder"]) : await promptSelect("Select root folder:", folders.map((f3) => ({ label: f3.path, value: f3.path })));
7793
8404
  const confirmed = await promptConfirm(`Add "${series.title} (${series.year})"?`, !!a2.yes);
7794
8405
  if (!confirmed)
7795
8406
  throw new Error("Cancelled.");
7796
- return c3.addSeries({
8407
+ const addResult = await c3.addSeries({
7797
8408
  ...series,
7798
- qualityProfileId: Number(profileId),
8409
+ qualityProfileId: profileId,
7799
8410
  rootFolderPath,
7800
- monitored: true,
8411
+ monitored: parseBooleanArg2(a2.monitored, true),
7801
8412
  addOptions: { searchForMissingEpisodes: true }
7802
8413
  });
8414
+ if (addResult?.error && getApiStatus2(addResult) === 400) {
8415
+ const existingSeries = await findSeriesByTvdbId(c3, series.tvdbId);
8416
+ if (existingSeries) {
8417
+ throw new Error(`${existingSeries.title} is already in your library (ID: ${existingSeries.id})`);
8418
+ }
8419
+ }
8420
+ return addResult;
8421
+ }
8422
+ },
8423
+ {
8424
+ name: "edit",
8425
+ description: "Edit a series",
8426
+ args: [
8427
+ { name: "id", description: "Series ID", required: true, type: "number" },
8428
+ { name: "monitored", description: "Set monitored (true/false)" },
8429
+ { name: "quality-profile-id", description: "Quality profile ID", type: "number" },
8430
+ { name: "tags", description: "Comma-separated tag IDs" }
8431
+ ],
8432
+ run: async (c3, a2) => {
8433
+ const result = await c3.getSeriesById(a2.id);
8434
+ const series = result?.data ?? result;
8435
+ const updates = { ...series };
8436
+ if (a2.monitored !== undefined)
8437
+ updates.monitored = a2.monitored === "true";
8438
+ if (a2["quality-profile-id"] !== undefined)
8439
+ updates.qualityProfileId = Number(a2["quality-profile-id"]);
8440
+ if (a2.tags !== undefined)
8441
+ updates.tags = a2.tags.split(",").map((t2) => Number(t2.trim()));
8442
+ return c3.updateSeries(String(a2.id), updates);
8443
+ }
8444
+ },
8445
+ {
8446
+ name: "refresh",
8447
+ description: "Refresh series metadata",
8448
+ args: [{ name: "id", description: "Series ID", required: true, type: "number" }],
8449
+ run: (c3, a2) => c3.runCommand({ name: "RefreshSeries", seriesId: a2.id })
8450
+ },
8451
+ {
8452
+ name: "manual-search",
8453
+ description: "Trigger a manual search for releases",
8454
+ args: [{ name: "id", description: "Series ID", required: true, type: "number" }],
8455
+ run: (c3, a2) => c3.runCommand({ name: "SeriesSearch", seriesId: a2.id })
8456
+ },
8457
+ {
8458
+ name: "delete",
8459
+ description: "Delete a series",
8460
+ args: [
8461
+ { name: "id", description: "Series ID", required: true, type: "number" },
8462
+ { name: "delete-files", description: "Delete series files", type: "boolean" },
8463
+ {
8464
+ name: "add-import-list-exclusion",
8465
+ description: "Add import list exclusion after delete",
8466
+ type: "boolean"
8467
+ }
8468
+ ],
8469
+ confirmMessage: "Are you sure you want to delete this series?",
8470
+ run: async (c3, a2) => {
8471
+ const seriesResult = await c3.getSeriesById(a2.id);
8472
+ if (seriesResult?.error)
8473
+ return seriesResult;
8474
+ const series = unwrapData2(seriesResult);
8475
+ const deleteResult = await c3.deleteSeries(a2.id, {
8476
+ deleteFiles: a2["delete-files"],
8477
+ addImportListExclusion: a2["add-import-list-exclusion"]
8478
+ });
8479
+ if (deleteResult?.error)
8480
+ return deleteResult;
8481
+ return { message: `Deleted: ${series.title} (ID: ${series.id})` };
8482
+ }
8483
+ }
8484
+ ]
8485
+ },
8486
+ {
8487
+ name: "episode",
8488
+ description: "Manage episodes",
8489
+ actions: [
8490
+ {
8491
+ name: "list",
8492
+ description: "List all episodes",
8493
+ args: [{ name: "series-id", description: "Series ID", required: true, type: "number" }],
8494
+ columns: ["id", "title", "seasonNumber", "episodeNumber", "hasFile"],
8495
+ run: (c3, a2) => c3.getEpisodes(a2["series-id"])
8496
+ },
8497
+ {
8498
+ name: "get",
8499
+ description: "Get an episode by ID",
8500
+ args: [{ name: "id", description: "Episode ID", required: true, type: "number" }],
8501
+ run: (c3, a2) => c3.getEpisode(a2.id)
8502
+ },
8503
+ {
8504
+ name: "search",
8505
+ description: "Trigger a search for an episode",
8506
+ args: [{ name: "id", description: "Episode ID", required: true, type: "number" }],
8507
+ run: (c3, a2) => c3.runCommand({ name: "EpisodeSearch", episodeIds: [a2.id] })
8508
+ }
8509
+ ]
8510
+ },
8511
+ {
8512
+ name: "profile",
8513
+ description: "Manage quality profiles",
8514
+ actions: [
8515
+ {
8516
+ name: "list",
8517
+ description: "List quality profiles",
8518
+ columns: ["id", "name"],
8519
+ run: (c3) => c3.getQualityProfiles()
8520
+ },
8521
+ {
8522
+ name: "get",
8523
+ description: "Get a quality profile by ID",
8524
+ args: [{ name: "id", description: "Profile ID", required: true, type: "number" }],
8525
+ run: (c3, a2) => c3.getQualityProfile(a2.id)
8526
+ }
8527
+ ]
8528
+ },
8529
+ {
8530
+ name: "tag",
8531
+ description: "Manage tags",
8532
+ actions: [
8533
+ {
8534
+ name: "create",
8535
+ description: "Create a tag",
8536
+ args: [{ name: "label", description: "Tag label", required: true }],
8537
+ run: (c3, a2) => c3.addTag({ label: a2.label })
8538
+ },
8539
+ {
8540
+ name: "delete",
8541
+ description: "Delete a tag",
8542
+ args: [{ name: "id", description: "Tag ID", required: true, type: "number" }],
8543
+ confirmMessage: "Are you sure you want to delete this tag?",
8544
+ run: async (c3, a2) => {
8545
+ const tagResult = await c3.getTag(a2.id);
8546
+ if (tagResult?.error)
8547
+ return tagResult;
8548
+ const tag = unwrapData2(tagResult);
8549
+ const deleteResult = await c3.deleteTag(a2.id);
8550
+ if (deleteResult?.error)
8551
+ return deleteResult;
8552
+ return { message: `Deleted tag: ${tag.label} (ID: ${tag.id})` };
8553
+ }
8554
+ },
8555
+ {
8556
+ name: "list",
8557
+ description: "List all tags",
8558
+ columns: ["id", "label"],
8559
+ run: (c3) => c3.getTags()
8560
+ }
8561
+ ]
8562
+ },
8563
+ {
8564
+ name: "queue",
8565
+ description: "Manage download queue",
8566
+ actions: [
8567
+ {
8568
+ name: "list",
8569
+ description: "List queue items",
8570
+ args: [{ name: "series-id", description: "Series ID", type: "number" }],
8571
+ columns: ["id", "title", "status", "sizeleft", "timeleft"],
8572
+ run: (c3, a2) => c3.getQueue(undefined, undefined, undefined, undefined, undefined, a2["series-id"])
8573
+ },
8574
+ {
8575
+ name: "status",
8576
+ description: "Get queue status",
8577
+ run: (c3) => c3.getQueueStatus()
8578
+ },
8579
+ {
8580
+ name: "delete",
8581
+ description: "Remove an item from the queue",
8582
+ args: [
8583
+ { name: "id", description: "Queue item ID", required: true, type: "number" },
8584
+ { name: "blocklist", description: "Add to blocklist", type: "boolean" },
8585
+ {
8586
+ name: "remove-from-client",
8587
+ description: "Remove from download client",
8588
+ type: "boolean"
8589
+ }
8590
+ ],
8591
+ confirmMessage: "Are you sure you want to remove this queue item?",
8592
+ run: (c3, a2) => c3.removeQueueItem(a2.id, a2["remove-from-client"], a2.blocklist)
8593
+ },
8594
+ {
8595
+ name: "grab",
8596
+ description: "Force download a queue item",
8597
+ args: [{ name: "id", description: "Queue item ID", required: true, type: "number" }],
8598
+ run: (c3, a2) => c3.grabQueueItem(a2.id)
8599
+ }
8600
+ ]
8601
+ },
8602
+ {
8603
+ name: "history",
8604
+ description: "View history",
8605
+ actions: [
8606
+ {
8607
+ name: "list",
8608
+ description: "List recent history",
8609
+ args: [
8610
+ { name: "series-id", description: "Series ID", type: "number" },
8611
+ { name: "since", description: "Start date (ISO 8601, e.g. 2024-01-01)" },
8612
+ { name: "until", description: "End date (ISO 8601, e.g. 2024-12-31)" }
8613
+ ],
8614
+ columns: ["id", "eventType", "sourceTitle", "date"],
8615
+ run: async (c3, a2) => {
8616
+ if (a2.since) {
8617
+ const result2 = await c3.getHistorySince(a2.since, a2["series-id"]);
8618
+ const items2 = unwrapData2(result2);
8619
+ if (a2.until) {
8620
+ const untilDate = new Date(a2.until);
8621
+ return items2.filter((item) => new Date(item.date) <= untilDate);
8622
+ }
8623
+ return items2;
8624
+ }
8625
+ const result = await c3.getHistory(undefined, undefined, undefined, undefined, a2["series-id"]);
8626
+ const items = unwrapData2(result);
8627
+ if (a2.until) {
8628
+ const untilDate = new Date(a2.until);
8629
+ return items.filter((item) => new Date(item.date) <= untilDate);
8630
+ }
8631
+ return items;
8632
+ }
8633
+ }
8634
+ ]
8635
+ },
8636
+ {
8637
+ name: "rootfolder",
8638
+ description: "Manage root folders",
8639
+ actions: [
8640
+ {
8641
+ name: "list",
8642
+ description: "List root folders",
8643
+ columns: ["id", "path", "freeSpace"],
8644
+ run: (c3) => c3.getRootFolders()
8645
+ },
8646
+ {
8647
+ name: "add",
8648
+ description: "Add a root folder",
8649
+ args: [{ name: "path", description: "Folder path", required: true }],
8650
+ run: (c3, a2) => c3.addRootFolder(a2.path)
8651
+ },
8652
+ {
8653
+ name: "delete",
8654
+ description: "Delete a root folder",
8655
+ args: [{ name: "id", description: "Root folder ID", required: true, type: "number" }],
8656
+ confirmMessage: "Are you sure you want to delete this root folder?",
8657
+ run: (c3, a2) => c3.deleteRootFolder(a2.id)
8658
+ }
8659
+ ]
8660
+ },
8661
+ {
8662
+ name: "calendar",
8663
+ description: "View upcoming releases",
8664
+ actions: [
8665
+ {
8666
+ name: "list",
8667
+ description: "List upcoming episode releases",
8668
+ args: [
8669
+ { name: "start", description: "Start date (ISO 8601)" },
8670
+ { name: "end", description: "End date (ISO 8601)" },
8671
+ { name: "unmonitored", description: "Include unmonitored", type: "boolean" }
8672
+ ],
8673
+ columns: ["id", "seriesTitle", "title", "seasonNumber", "episodeNumber", "airDateUtc"],
8674
+ run: async (c3, a2) => {
8675
+ const result = await c3.getCalendar(a2.start, a2.end, a2.unmonitored);
8676
+ const episodes = unwrapData2(result);
8677
+ return episodes.map((ep) => ({
8678
+ ...ep,
8679
+ seriesTitle: ep.seriesTitle || ep.series?.title || "—"
8680
+ }));
7803
8681
  }
8682
+ }
8683
+ ]
8684
+ },
8685
+ {
8686
+ name: "notification",
8687
+ description: "Manage notifications",
8688
+ actions: [
8689
+ {
8690
+ name: "list",
8691
+ description: "List notification providers",
8692
+ columns: ["id", "name", "implementation"],
8693
+ run: (c3) => c3.getNotifications()
8694
+ },
8695
+ {
8696
+ name: "get",
8697
+ description: "Get a notification by ID",
8698
+ args: [{ name: "id", description: "Notification ID", required: true, type: "number" }],
8699
+ run: (c3, a2) => c3.getNotification(a2.id)
8700
+ },
8701
+ {
8702
+ name: "add",
8703
+ description: "Add a notification from JSON file or stdin",
8704
+ args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
8705
+ run: async (c3, a2) => c3.addNotification(readJsonInput2(a2.file))
7804
8706
  },
7805
8707
  {
7806
8708
  name: "edit",
7807
- description: "Edit a series",
8709
+ description: "Edit a notification (merges JSON with existing)",
7808
8710
  args: [
7809
- { name: "id", description: "Series ID", required: true, type: "number" },
7810
- { name: "monitored", description: "Set monitored (true/false)" },
7811
- { name: "quality-profile-id", description: "Quality profile ID", type: "number" },
7812
- { name: "tags", description: "Comma-separated tag IDs" }
8711
+ { name: "id", description: "Notification ID", required: true, type: "number" },
8712
+ { name: "file", description: "JSON file with fields to update", required: true }
7813
8713
  ],
7814
8714
  run: async (c3, a2) => {
7815
- const result = await c3.getSeriesById(a2.id);
7816
- const series = result?.data ?? result;
7817
- const updates = { ...series };
7818
- if (a2.monitored !== undefined)
7819
- updates.monitored = a2.monitored === "true";
7820
- if (a2["quality-profile-id"] !== undefined)
7821
- updates.qualityProfileId = Number(a2["quality-profile-id"]);
7822
- if (a2.tags !== undefined)
7823
- updates.tags = a2.tags.split(",").map((t2) => Number(t2.trim()));
7824
- return c3.updateSeries(String(a2.id), updates);
8715
+ const existing = unwrapData2(await c3.getNotification(a2.id));
8716
+ return c3.updateNotification(a2.id, { ...existing, ...readJsonInput2(a2.file) });
7825
8717
  }
7826
8718
  },
7827
8719
  {
7828
- name: "refresh",
7829
- description: "Refresh series metadata",
7830
- args: [{ name: "id", description: "Series ID", required: true, type: "number" }],
7831
- run: (c3, a2) => c3.runCommand({ name: "RefreshSeries", seriesId: a2.id })
7832
- },
7833
- {
7834
- name: "manual-search",
7835
- description: "Trigger a manual search for releases",
7836
- args: [{ name: "id", description: "Series ID", required: true, type: "number" }],
7837
- run: (c3, a2) => c3.runCommand({ name: "SeriesSearch", seriesId: a2.id })
8720
+ name: "delete",
8721
+ description: "Delete a notification",
8722
+ args: [{ name: "id", description: "Notification ID", required: true, type: "number" }],
8723
+ confirmMessage: "Are you sure you want to delete this notification?",
8724
+ run: (c3, a2) => c3.deleteNotification(a2.id)
7838
8725
  },
7839
8726
  {
7840
- name: "delete",
7841
- description: "Delete a series",
7842
- args: [{ name: "id", description: "Series ID", required: true, type: "number" }],
7843
- confirmMessage: "Are you sure you want to delete this series?",
7844
- run: (c3, a2) => c3.deleteSeries(a2.id)
8727
+ name: "test",
8728
+ description: "Test all notifications",
8729
+ run: (c3) => c3.testAllNotifications()
7845
8730
  }
7846
8731
  ]
7847
8732
  },
7848
8733
  {
7849
- name: "episode",
7850
- description: "Manage episodes",
8734
+ name: "downloadclient",
8735
+ description: "Manage download clients",
7851
8736
  actions: [
7852
8737
  {
7853
8738
  name: "list",
7854
- description: "List all episodes",
7855
- columns: ["id", "title", "seasonNumber", "episodeNumber", "hasFile"],
7856
- run: (c3) => c3.getEpisodes()
8739
+ description: "List download clients",
8740
+ columns: ["id", "name", "implementation", "enable"],
8741
+ run: (c3) => c3.getDownloadClients()
7857
8742
  },
7858
8743
  {
7859
8744
  name: "get",
7860
- description: "Get an episode by ID",
7861
- args: [{ name: "id", description: "Episode ID", required: true, type: "number" }],
7862
- run: (c3, a2) => c3.getEpisode(a2.id)
8745
+ description: "Get a download client by ID",
8746
+ args: [{ name: "id", description: "Download client ID", required: true, type: "number" }],
8747
+ run: (c3, a2) => c3.getDownloadClient(a2.id)
8748
+ },
8749
+ {
8750
+ name: "add",
8751
+ description: "Add a download client from JSON file or stdin",
8752
+ args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
8753
+ run: async (c3, a2) => c3.addDownloadClient(readJsonInput2(a2.file))
8754
+ },
8755
+ {
8756
+ name: "edit",
8757
+ description: "Edit a download client (merges JSON with existing)",
8758
+ args: [
8759
+ { name: "id", description: "Download client ID", required: true, type: "number" },
8760
+ { name: "file", description: "JSON file with fields to update", required: true }
8761
+ ],
8762
+ run: async (c3, a2) => {
8763
+ const existing = unwrapData2(await c3.getDownloadClient(a2.id));
8764
+ return c3.updateDownloadClient(a2.id, { ...existing, ...readJsonInput2(a2.file) });
8765
+ }
8766
+ },
8767
+ {
8768
+ name: "delete",
8769
+ description: "Delete a download client",
8770
+ args: [{ name: "id", description: "Download client ID", required: true, type: "number" }],
8771
+ confirmMessage: "Are you sure you want to delete this download client?",
8772
+ run: (c3, a2) => c3.deleteDownloadClient(a2.id)
8773
+ },
8774
+ {
8775
+ name: "test",
8776
+ description: "Test all download clients",
8777
+ run: (c3) => c3.testAllDownloadClients()
7863
8778
  }
7864
8779
  ]
7865
8780
  },
7866
8781
  {
7867
- name: "profile",
7868
- description: "Manage quality profiles",
8782
+ name: "blocklist",
8783
+ description: "Manage blocked releases",
7869
8784
  actions: [
7870
8785
  {
7871
8786
  name: "list",
7872
- description: "List quality profiles",
7873
- columns: ["id", "name"],
7874
- run: (c3) => c3.getQualityProfiles()
8787
+ description: "List blocked releases",
8788
+ columns: ["id", "sourceTitle", "date"],
8789
+ run: (c3) => c3.getBlocklist()
8790
+ },
8791
+ {
8792
+ name: "delete",
8793
+ description: "Remove a release from the blocklist",
8794
+ args: [{ name: "id", description: "Blocklist item ID", required: true, type: "number" }],
8795
+ confirmMessage: "Are you sure you want to remove this blocklist entry?",
8796
+ run: (c3, a2) => c3.removeBlocklistItem(a2.id)
7875
8797
  }
7876
8798
  ]
7877
8799
  },
7878
8800
  {
7879
- name: "tag",
7880
- description: "Manage tags",
8801
+ name: "wanted",
8802
+ description: "View missing and cutoff unmet episodes",
7881
8803
  actions: [
7882
8804
  {
7883
- name: "list",
7884
- description: "List all tags",
7885
- columns: ["id", "label"],
7886
- run: (c3) => c3.getTags()
8805
+ name: "missing",
8806
+ description: "List episodes with missing files",
8807
+ columns: ["id", "title", "seasonNumber", "episodeNumber", "airDateUtc"],
8808
+ run: (c3) => c3.getWantedMissing()
8809
+ },
8810
+ {
8811
+ name: "cutoff",
8812
+ description: "List episodes below quality cutoff",
8813
+ columns: ["id", "title", "seasonNumber", "episodeNumber", "airDateUtc"],
8814
+ run: (c3) => c3.getWantedCutoff()
7887
8815
  }
7888
8816
  ]
7889
8817
  },
7890
8818
  {
7891
- name: "rootfolder",
7892
- description: "Manage root folders",
8819
+ name: "importlist",
8820
+ description: "Manage import lists",
7893
8821
  actions: [
7894
8822
  {
7895
8823
  name: "list",
7896
- description: "List root folders",
7897
- columns: ["id", "path", "freeSpace"],
7898
- run: (c3) => c3.getRootFolders()
8824
+ description: "List import lists",
8825
+ columns: ["id", "name", "implementation", "enable"],
8826
+ run: (c3) => c3.getImportLists()
8827
+ },
8828
+ {
8829
+ name: "get",
8830
+ description: "Get an import list by ID",
8831
+ args: [{ name: "id", description: "Import list ID", required: true, type: "number" }],
8832
+ run: (c3, a2) => c3.getImportList(a2.id)
8833
+ },
8834
+ {
8835
+ name: "add",
8836
+ description: "Add an import list from JSON file or stdin",
8837
+ args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
8838
+ run: async (c3, a2) => c3.addImportList(readJsonInput2(a2.file))
8839
+ },
8840
+ {
8841
+ name: "edit",
8842
+ description: "Edit an import list (merges JSON with existing)",
8843
+ args: [
8844
+ { name: "id", description: "Import list ID", required: true, type: "number" },
8845
+ { name: "file", description: "JSON file with fields to update", required: true }
8846
+ ],
8847
+ run: async (c3, a2) => {
8848
+ const existing = unwrapData2(await c3.getImportList(a2.id));
8849
+ return c3.updateImportList(a2.id, { ...existing, ...readJsonInput2(a2.file) });
8850
+ }
8851
+ },
8852
+ {
8853
+ name: "delete",
8854
+ description: "Delete an import list",
8855
+ args: [{ name: "id", description: "Import list ID", required: true, type: "number" }],
8856
+ confirmMessage: "Are you sure you want to delete this import list?",
8857
+ run: (c3, a2) => c3.deleteImportList(a2.id)
7899
8858
  }
7900
8859
  ]
7901
8860
  },
@@ -9649,6 +10608,14 @@ var getApiV1Album = (options) => (options?.client ?? client3).get({
9649
10608
  }],
9650
10609
  url: "/api/v1/queue/status",
9651
10610
  ...options
10611
+ }), deleteApiV1RootfolderById = (options) => (options.client ?? client3).delete({
10612
+ security: [{ name: "X-Api-Key", type: "apiKey" }, {
10613
+ in: "query",
10614
+ name: "apikey",
10615
+ type: "apiKey"
10616
+ }],
10617
+ url: "/api/v1/rootfolder/{id}",
10618
+ ...options
9652
10619
  }), getApiV1Rootfolder = (options) => (options?.client ?? client3).get({
9653
10620
  security: [{ name: "X-Api-Key", type: "apiKey" }, {
9654
10621
  in: "query",
@@ -9854,6 +10821,9 @@ class LidarrClient {
9854
10821
  body: { path }
9855
10822
  });
9856
10823
  }
10824
+ async deleteRootFolder(id) {
10825
+ return deleteApiV1RootfolderById({ path: { id } });
10826
+ }
9857
10827
  async addAlbum(album) {
9858
10828
  return postApiV1Album({ body: album });
9859
10829
  }
@@ -10409,6 +11379,28 @@ var init_lidarr3 = __esm(() => {
10409
11379
  name: "tag",
10410
11380
  description: "Manage tags",
10411
11381
  actions: [
11382
+ {
11383
+ name: "create",
11384
+ description: "Create a tag",
11385
+ args: [{ name: "label", description: "Tag label", required: true }],
11386
+ run: (c3, a2) => c3.addTag({ label: a2.label })
11387
+ },
11388
+ {
11389
+ name: "delete",
11390
+ description: "Delete a tag",
11391
+ args: [{ name: "id", description: "Tag ID", required: true, type: "number" }],
11392
+ confirmMessage: "Are you sure you want to delete this tag?",
11393
+ run: async (c3, a2) => {
11394
+ const tagResult = await c3.getTag(a2.id);
11395
+ if (tagResult?.error)
11396
+ return tagResult;
11397
+ const tag = tagResult?.data ?? tagResult;
11398
+ const deleteResult = await c3.deleteTag(a2.id);
11399
+ if (deleteResult?.error)
11400
+ return deleteResult;
11401
+ return { message: `Deleted tag: ${tag.label} (ID: ${tag.id})` };
11402
+ }
11403
+ },
10412
11404
  {
10413
11405
  name: "list",
10414
11406
  description: "List all tags",
@@ -10426,6 +11418,19 @@ var init_lidarr3 = __esm(() => {
10426
11418
  description: "List root folders",
10427
11419
  columns: ["id", "path", "freeSpace"],
10428
11420
  run: (c3) => c3.getRootFolders()
11421
+ },
11422
+ {
11423
+ name: "add",
11424
+ description: "Add a root folder",
11425
+ args: [{ name: "path", description: "Folder path", required: true }],
11426
+ run: (c3, a2) => c3.addRootFolder(a2.path)
11427
+ },
11428
+ {
11429
+ name: "delete",
11430
+ description: "Delete a root folder",
11431
+ args: [{ name: "id", description: "Root folder ID", required: true, type: "number" }],
11432
+ confirmMessage: "Are you sure you want to delete this root folder?",
11433
+ run: (c3, a2) => c3.deleteRootFolder(a2.id)
10429
11434
  }
10430
11435
  ]
10431
11436
  },
@@ -12163,6 +13168,14 @@ var getApiV1Author = (options) => (options?.client ?? client4).get({
12163
13168
  "Content-Type": "application/json",
12164
13169
  ...options?.headers
12165
13170
  }
13171
+ }), deleteApiV1RootfolderById2 = (options) => (options.client ?? client4).delete({
13172
+ security: [{ name: "X-Api-Key", type: "apiKey" }, {
13173
+ in: "query",
13174
+ name: "apikey",
13175
+ type: "apiKey"
13176
+ }],
13177
+ url: "/api/v1/rootfolder/{id}",
13178
+ ...options
12166
13179
  }), getApiV1SystemStatus2 = (options) => (options?.client ?? client4).get({
12167
13180
  security: [{ name: "X-Api-Key", type: "apiKey" }, {
12168
13181
  in: "query",
@@ -12348,6 +13361,9 @@ class ReadarrClient {
12348
13361
  body: { path }
12349
13362
  });
12350
13363
  }
13364
+ async deleteRootFolder(id) {
13365
+ return deleteApiV1RootfolderById2({ path: { id } });
13366
+ }
12351
13367
  async getHostConfig() {
12352
13368
  return getApiV1ConfigHost2();
12353
13369
  }
@@ -12880,6 +13896,28 @@ var init_readarr3 = __esm(() => {
12880
13896
  name: "tag",
12881
13897
  description: "Manage tags",
12882
13898
  actions: [
13899
+ {
13900
+ name: "create",
13901
+ description: "Create a tag",
13902
+ args: [{ name: "label", description: "Tag label", required: true }],
13903
+ run: (c3, a2) => c3.addTag({ label: a2.label })
13904
+ },
13905
+ {
13906
+ name: "delete",
13907
+ description: "Delete a tag",
13908
+ args: [{ name: "id", description: "Tag ID", required: true, type: "number" }],
13909
+ confirmMessage: "Are you sure you want to delete this tag?",
13910
+ run: async (c3, a2) => {
13911
+ const tagResult = await c3.getTag(a2.id);
13912
+ if (tagResult?.error)
13913
+ return tagResult;
13914
+ const tag = tagResult?.data ?? tagResult;
13915
+ const deleteResult = await c3.deleteTag(a2.id);
13916
+ if (deleteResult?.error)
13917
+ return deleteResult;
13918
+ return { message: `Deleted tag: ${tag.label} (ID: ${tag.id})` };
13919
+ }
13920
+ },
12883
13921
  {
12884
13922
  name: "list",
12885
13923
  description: "List all tags",
@@ -12897,6 +13935,19 @@ var init_readarr3 = __esm(() => {
12897
13935
  description: "List root folders",
12898
13936
  columns: ["id", "path", "freeSpace"],
12899
13937
  run: (c3) => c3.getRootFolders()
13938
+ },
13939
+ {
13940
+ name: "add",
13941
+ description: "Add a root folder",
13942
+ args: [{ name: "path", description: "Folder path", required: true }],
13943
+ run: (c3, a2) => c3.addRootFolder(a2.path)
13944
+ },
13945
+ {
13946
+ name: "delete",
13947
+ description: "Delete a root folder",
13948
+ args: [{ name: "id", description: "Root folder ID", required: true, type: "number" }],
13949
+ confirmMessage: "Are you sure you want to delete this root folder?",
13950
+ run: (c3, a2) => c3.deleteRootFolder(a2.id)
12900
13951
  }
12901
13952
  ]
12902
13953
  },
@@ -14066,6 +15117,14 @@ var deleteApiV1ApplicationsById = (options) => (options.client ?? client5).delet
14066
15117
  }],
14067
15118
  url: "/api/v1/indexer/testall",
14068
15119
  ...options
15120
+ }), getApiV1Indexerstats = (options) => (options?.client ?? client5).get({
15121
+ security: [{ name: "X-Api-Key", type: "apiKey" }, {
15122
+ in: "query",
15123
+ name: "apikey",
15124
+ type: "apiKey"
15125
+ }],
15126
+ url: "/api/v1/indexerstats",
15127
+ ...options
14069
15128
  }), getApiV1Log3 = (options) => (options?.client ?? client5).get({
14070
15129
  security: [{ name: "X-Api-Key", type: "apiKey" }, {
14071
15130
  in: "query",
@@ -14336,6 +15395,9 @@ class ProwlarrClient {
14336
15395
  async deleteIndexer(id) {
14337
15396
  return deleteApiV1IndexerById3({ path: { id } });
14338
15397
  }
15398
+ async getIndexerStats() {
15399
+ return getApiV1Indexerstats();
15400
+ }
14339
15401
  async getDownloadClients() {
14340
15402
  return getApiV1Downloadclient3();
14341
15403
  }
@@ -14530,9 +15592,18 @@ var exports_prowlarr3 = {};
14530
15592
  __export(exports_prowlarr3, {
14531
15593
  prowlarr: () => prowlarr
14532
15594
  });
15595
+ import { readFileSync as readFileSync4 } from "node:fs";
15596
+ function unwrapData3(result) {
15597
+ return result?.data ?? result;
15598
+ }
15599
+ function readJsonInput3(filePath) {
15600
+ const raw = filePath === "-" ? readFileSync4(0, "utf-8") : readFileSync4(filePath, "utf-8");
15601
+ return JSON.parse(raw);
15602
+ }
14533
15603
  var resources5, prowlarr;
14534
15604
  var init_prowlarr3 = __esm(() => {
14535
15605
  init_prowlarr2();
15606
+ init_prompt2();
14536
15607
  init_service();
14537
15608
  resources5 = [
14538
15609
  {
@@ -14551,12 +15622,39 @@ var init_prowlarr3 = __esm(() => {
14551
15622
  args: [{ name: "id", description: "Indexer ID", required: true, type: "number" }],
14552
15623
  run: (c3, a2) => c3.getIndexer(a2.id)
14553
15624
  },
15625
+ {
15626
+ name: "add",
15627
+ description: "Add an indexer from JSON file or stdin",
15628
+ args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
15629
+ run: async (c3, a2) => {
15630
+ const body = readJsonInput3(a2.file);
15631
+ return c3.addIndexer(body);
15632
+ }
15633
+ },
15634
+ {
15635
+ name: "edit",
15636
+ description: "Edit an indexer (merges JSON with existing)",
15637
+ args: [
15638
+ { name: "id", description: "Indexer ID", required: true, type: "number" },
15639
+ { name: "file", description: "JSON file with fields to update", required: true }
15640
+ ],
15641
+ run: async (c3, a2) => {
15642
+ const existing = unwrapData3(await c3.getIndexer(a2.id));
15643
+ const updates = readJsonInput3(a2.file);
15644
+ return c3.updateIndexer(a2.id, { ...existing, ...updates });
15645
+ }
15646
+ },
14554
15647
  {
14555
15648
  name: "delete",
14556
15649
  description: "Delete an indexer",
14557
15650
  args: [{ name: "id", description: "Indexer ID", required: true, type: "number" }],
14558
15651
  confirmMessage: "Are you sure you want to delete this indexer?",
14559
15652
  run: (c3, a2) => c3.deleteIndexer(a2.id)
15653
+ },
15654
+ {
15655
+ name: "test",
15656
+ description: "Test all indexers",
15657
+ run: (c3) => c3.testAllIndexers()
14560
15658
  }
14561
15659
  ]
14562
15660
  },
@@ -14567,9 +15665,12 @@ var init_prowlarr3 = __esm(() => {
14567
15665
  {
14568
15666
  name: "run",
14569
15667
  description: "Search across indexers",
14570
- args: [{ name: "query", description: "Search query", required: true }],
15668
+ args: [
15669
+ { name: "term", description: "Search term" },
15670
+ { name: "query", description: "Search query" }
15671
+ ],
14571
15672
  columns: ["indexer", "title", "size", "seeders"],
14572
- run: (c3, a2) => c3.search(a2.query)
15673
+ run: async (c3, a2) => c3.search(await promptIfMissing(a2.term ?? a2.query, "Search term:"))
14573
15674
  }
14574
15675
  ]
14575
15676
  },
@@ -14588,6 +15689,40 @@ var init_prowlarr3 = __esm(() => {
14588
15689
  description: "Get an application by ID",
14589
15690
  args: [{ name: "id", description: "Application ID", required: true, type: "number" }],
14590
15691
  run: (c3, a2) => c3.getApplication(a2.id)
15692
+ },
15693
+ {
15694
+ name: "add",
15695
+ description: "Add an application from JSON file or stdin",
15696
+ args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
15697
+ run: async (c3, a2) => {
15698
+ const body = readJsonInput3(a2.file);
15699
+ return c3.addApplication(body);
15700
+ }
15701
+ },
15702
+ {
15703
+ name: "edit",
15704
+ description: "Edit an application (merges JSON with existing)",
15705
+ args: [
15706
+ { name: "id", description: "Application ID", required: true, type: "number" },
15707
+ { name: "file", description: "JSON file with fields to update", required: true }
15708
+ ],
15709
+ run: async (c3, a2) => {
15710
+ const existing = unwrapData3(await c3.getApplication(a2.id));
15711
+ const updates = readJsonInput3(a2.file);
15712
+ return c3.updateApplication(a2.id, { ...existing, ...updates });
15713
+ }
15714
+ },
15715
+ {
15716
+ name: "delete",
15717
+ description: "Delete an application",
15718
+ args: [{ name: "id", description: "Application ID", required: true, type: "number" }],
15719
+ confirmMessage: "Are you sure you want to delete this application?",
15720
+ run: (c3, a2) => c3.deleteApplication(a2.id)
15721
+ },
15722
+ {
15723
+ name: "sync",
15724
+ description: "Trigger app indexer sync",
15725
+ run: (c3) => c3.runCommand({ name: "AppIndexerMapSync" })
14591
15726
  }
14592
15727
  ]
14593
15728
  },
@@ -14595,6 +15730,28 @@ var init_prowlarr3 = __esm(() => {
14595
15730
  name: "tag",
14596
15731
  description: "Manage tags",
14597
15732
  actions: [
15733
+ {
15734
+ name: "create",
15735
+ description: "Create a tag",
15736
+ args: [{ name: "label", description: "Tag label", required: true }],
15737
+ run: (c3, a2) => c3.addTag({ label: a2.label })
15738
+ },
15739
+ {
15740
+ name: "delete",
15741
+ description: "Delete a tag",
15742
+ args: [{ name: "id", description: "Tag ID", required: true, type: "number" }],
15743
+ confirmMessage: "Are you sure you want to delete this tag?",
15744
+ run: async (c3, a2) => {
15745
+ const tagResult = await c3.getTag(a2.id);
15746
+ if (tagResult?.error)
15747
+ return tagResult;
15748
+ const tag = tagResult?.data ?? tagResult;
15749
+ const deleteResult = await c3.deleteTag(a2.id);
15750
+ if (deleteResult?.error)
15751
+ return deleteResult;
15752
+ return { message: `Deleted tag: ${tag.label} (ID: ${tag.id})` };
15753
+ }
15754
+ },
14598
15755
  {
14599
15756
  name: "list",
14600
15757
  description: "List all tags",
@@ -14603,6 +15760,123 @@ var init_prowlarr3 = __esm(() => {
14603
15760
  }
14604
15761
  ]
14605
15762
  },
15763
+ {
15764
+ name: "indexerstats",
15765
+ description: "View indexer statistics",
15766
+ actions: [
15767
+ {
15768
+ name: "list",
15769
+ description: "Get indexer performance statistics",
15770
+ columns: [
15771
+ "indexerName",
15772
+ "numberOfQueries",
15773
+ "numberOfGrabs",
15774
+ "numberOfFailures",
15775
+ "averageResponseTime"
15776
+ ],
15777
+ run: async (c3) => {
15778
+ const result = unwrapData3(await c3.getIndexerStats());
15779
+ return result?.indexers ?? result;
15780
+ }
15781
+ }
15782
+ ]
15783
+ },
15784
+ {
15785
+ name: "notification",
15786
+ description: "Manage notifications",
15787
+ actions: [
15788
+ {
15789
+ name: "list",
15790
+ description: "List notification providers",
15791
+ columns: ["id", "name", "implementation"],
15792
+ run: (c3) => c3.getNotifications()
15793
+ },
15794
+ {
15795
+ name: "get",
15796
+ description: "Get a notification by ID",
15797
+ args: [{ name: "id", description: "Notification ID", required: true, type: "number" }],
15798
+ run: (c3, a2) => c3.getNotification(a2.id)
15799
+ },
15800
+ {
15801
+ name: "add",
15802
+ description: "Add a notification from JSON file or stdin",
15803
+ args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
15804
+ run: async (c3, a2) => c3.addNotification(readJsonInput3(a2.file))
15805
+ },
15806
+ {
15807
+ name: "edit",
15808
+ description: "Edit a notification (merges JSON with existing)",
15809
+ args: [
15810
+ { name: "id", description: "Notification ID", required: true, type: "number" },
15811
+ { name: "file", description: "JSON file with fields to update", required: true }
15812
+ ],
15813
+ run: async (c3, a2) => {
15814
+ const existing = unwrapData3(await c3.getNotification(a2.id));
15815
+ return c3.updateNotification(a2.id, { ...existing, ...readJsonInput3(a2.file) });
15816
+ }
15817
+ },
15818
+ {
15819
+ name: "delete",
15820
+ description: "Delete a notification",
15821
+ args: [{ name: "id", description: "Notification ID", required: true, type: "number" }],
15822
+ confirmMessage: "Are you sure you want to delete this notification?",
15823
+ run: (c3, a2) => c3.deleteNotification(a2.id)
15824
+ },
15825
+ {
15826
+ name: "test",
15827
+ description: "Test all notifications",
15828
+ run: (c3) => c3.testAllNotifications()
15829
+ }
15830
+ ]
15831
+ },
15832
+ {
15833
+ name: "downloadclient",
15834
+ description: "Manage download clients",
15835
+ actions: [
15836
+ {
15837
+ name: "list",
15838
+ description: "List download clients",
15839
+ columns: ["id", "name", "implementation", "enable"],
15840
+ run: (c3) => c3.getDownloadClients()
15841
+ },
15842
+ {
15843
+ name: "get",
15844
+ description: "Get a download client by ID",
15845
+ args: [{ name: "id", description: "Download client ID", required: true, type: "number" }],
15846
+ run: (c3, a2) => c3.getDownloadClient(a2.id)
15847
+ },
15848
+ {
15849
+ name: "add",
15850
+ description: "Add a download client from JSON file or stdin",
15851
+ args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
15852
+ run: async (c3, a2) => c3.addDownloadClient(readJsonInput3(a2.file))
15853
+ },
15854
+ {
15855
+ name: "edit",
15856
+ description: "Edit a download client (merges JSON with existing)",
15857
+ args: [
15858
+ { name: "id", description: "Download client ID", required: true, type: "number" },
15859
+ { name: "file", description: "JSON file with fields to update", required: true }
15860
+ ],
15861
+ run: async (c3, a2) => {
15862
+ const existing = unwrapData3(await c3.getDownloadClient(a2.id));
15863
+ return c3.updateDownloadClient(a2.id, { ...existing, ...readJsonInput3(a2.file) });
15864
+ }
15865
+ },
15866
+ {
15867
+ name: "delete",
15868
+ description: "Delete a download client",
15869
+ args: [{ name: "id", description: "Download client ID", required: true, type: "number" }],
15870
+ confirmMessage: "Are you sure you want to delete this download client?",
15871
+ run: (c3, a2) => c3.deleteDownloadClient(a2.id)
15872
+ },
15873
+ {
15874
+ name: "test",
15875
+ description: "Test all download clients",
15876
+ run: (c3) => c3.testAllDownloadClients()
15877
+ }
15878
+ ]
15879
+ },
14606
15880
  {
14607
15881
  name: "system",
14608
15882
  description: "System information",
@@ -16721,8 +17995,8 @@ var init_completions = __esm(() => {
16721
17995
 
16722
17996
  // src/cli/index.ts
16723
17997
  init_dist();
16724
- import { readFileSync as readFileSync2 } from "node:fs";
16725
- var { version } = JSON.parse(readFileSync2(new URL("../../package.json", import.meta.url), "utf-8"));
17998
+ import { readFileSync as readFileSync5 } from "node:fs";
17999
+ var { version } = JSON.parse(readFileSync5(new URL("../../package.json", import.meta.url), "utf-8"));
16726
18000
  var main = defineCommand({
16727
18001
  meta: {
16728
18002
  name: "tsarr",