tsarr 2.3.2 → 2.4.0

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,46 @@ var exports_radarr3 = {};
5130
5259
  __export(exports_radarr3, {
5131
5260
  radarr: () => radarr
5132
5261
  });
5262
+ function unwrapData(result) {
5263
+ return result?.data ?? result;
5264
+ }
5265
+ function parseBooleanArg(value, fallback) {
5266
+ if (value === undefined)
5267
+ return fallback;
5268
+ if (typeof value === "boolean")
5269
+ return value;
5270
+ if (typeof value === "string") {
5271
+ const normalized = value.trim().toLowerCase();
5272
+ if (normalized === "true")
5273
+ return true;
5274
+ if (normalized === "false")
5275
+ return false;
5276
+ }
5277
+ return Boolean(value);
5278
+ }
5279
+ function resolveQualityProfileId(profiles, profileId) {
5280
+ const profile = profiles.find((item) => item?.id === profileId);
5281
+ if (!profile) {
5282
+ throw new Error(`Quality profile ${profileId} was not found.`);
5283
+ }
5284
+ return profileId;
5285
+ }
5286
+ function resolveRootFolderPath(folders, rootFolderPath) {
5287
+ const folder = folders.find((item) => item?.path === rootFolderPath);
5288
+ if (!folder) {
5289
+ throw new Error(`Root folder "${rootFolderPath}" was not found.`);
5290
+ }
5291
+ return rootFolderPath;
5292
+ }
5293
+ async function findMovieByTmdbId(client2, tmdbId) {
5294
+ if (tmdbId === undefined)
5295
+ return;
5296
+ const movies = unwrapData(await client2.getMovies());
5297
+ return movies.find((movie) => movie?.tmdbId === tmdbId);
5298
+ }
5299
+ function getApiStatus(result) {
5300
+ return result?.error?.status ?? result?.response?.status;
5301
+ }
5133
5302
  var resources, radarr;
5134
5303
  var init_radarr3 = __esm(() => {
5135
5304
  init_radarr2();
@@ -5163,40 +5332,68 @@ var init_radarr3 = __esm(() => {
5163
5332
  {
5164
5333
  name: "add",
5165
5334
  description: "Search and add a movie",
5166
- args: [{ name: "term", description: "Search term", required: true }],
5335
+ args: [
5336
+ { name: "term", description: "Search term" },
5337
+ { name: "tmdb-id", description: "TMDB ID", type: "number" },
5338
+ { name: "quality-profile-id", description: "Quality profile ID", type: "number" },
5339
+ { name: "root-folder", description: "Root folder path" },
5340
+ { name: "monitored", description: "Set monitored (true/false)" }
5341
+ ],
5167
5342
  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.");
5343
+ let movie;
5344
+ if (a2["tmdb-id"] !== undefined) {
5345
+ const lookupResult = await c3.lookupMovieByTmdbId(a2["tmdb-id"]);
5346
+ const lookup = unwrapData(lookupResult);
5347
+ const matches = Array.isArray(lookup) ? lookup : [lookup];
5348
+ movie = matches.find((m2) => m2?.tmdbId === a2["tmdb-id"]) ?? matches[0];
5349
+ if (!movie) {
5350
+ throw new Error(`No movie found for TMDB ID ${a2["tmdb-id"]}.`);
5351
+ }
5352
+ } else {
5353
+ const term = await promptIfMissing(a2.term, "Search term:");
5354
+ const searchResult = await c3.searchMovies(term);
5355
+ const results = unwrapData(searchResult);
5356
+ if (!Array.isArray(results) || results.length === 0) {
5357
+ throw new Error("No movies found.");
5358
+ }
5359
+ const movieId = await promptSelect("Select a movie:", results.map((m2) => ({
5360
+ label: `${m2.title} (${m2.year})`,
5361
+ value: String(m2.tmdbId)
5362
+ })));
5363
+ movie = results.find((m2) => String(m2.tmdbId) === movieId);
5364
+ if (!movie) {
5365
+ throw new Error("Selected movie was not found in the search results.");
5366
+ }
5177
5367
  }
5178
5368
  const profilesResult = await c3.getQualityProfiles();
5179
- const profiles = profilesResult?.data ?? profilesResult;
5369
+ const profiles = unwrapData(profilesResult);
5180
5370
  if (!Array.isArray(profiles) || profiles.length === 0) {
5181
5371
  throw new Error("No quality profiles found. Configure one in Radarr first.");
5182
5372
  }
5183
- const profileId = await promptSelect("Select quality profile:", profiles.map((p) => ({ label: p.name, value: String(p.id) })));
5373
+ 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
5374
  const foldersResult = await c3.getRootFolders();
5185
- const folders = foldersResult?.data ?? foldersResult;
5375
+ const folders = unwrapData(foldersResult);
5186
5376
  if (!Array.isArray(folders) || folders.length === 0) {
5187
5377
  throw new Error("No root folders found. Configure one in Radarr first.");
5188
5378
  }
5189
- const rootFolderPath = await promptSelect("Select root folder:", folders.map((f3) => ({ label: f3.path, value: f3.path })));
5379
+ 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
5380
  const confirmed = await promptConfirm(`Add "${movie.title} (${movie.year})"?`, !!a2.yes);
5191
5381
  if (!confirmed)
5192
5382
  throw new Error("Cancelled.");
5193
- return c3.addMovie({
5383
+ const addResult = await c3.addMovie({
5194
5384
  ...movie,
5195
- qualityProfileId: Number(profileId),
5385
+ qualityProfileId: profileId,
5196
5386
  rootFolderPath,
5197
- monitored: true,
5387
+ monitored: parseBooleanArg(a2.monitored, true),
5198
5388
  addOptions: { searchForMovie: true }
5199
5389
  });
5390
+ if (addResult?.error && getApiStatus(addResult) === 400) {
5391
+ const existingMovie = await findMovieByTmdbId(c3, movie.tmdbId);
5392
+ if (existingMovie) {
5393
+ throw new Error(`${existingMovie.title} is already in your library (ID: ${existingMovie.id})`);
5394
+ }
5395
+ }
5396
+ return addResult;
5200
5397
  }
5201
5398
  },
5202
5399
  {
@@ -5236,9 +5433,29 @@ var init_radarr3 = __esm(() => {
5236
5433
  {
5237
5434
  name: "delete",
5238
5435
  description: "Delete a movie",
5239
- args: [{ name: "id", description: "Movie ID", required: true, type: "number" }],
5436
+ args: [
5437
+ { name: "id", description: "Movie ID", required: true, type: "number" },
5438
+ { name: "delete-files", description: "Delete movie files", type: "boolean" },
5439
+ {
5440
+ name: "add-import-exclusion",
5441
+ description: "Add import exclusion after delete",
5442
+ type: "boolean"
5443
+ }
5444
+ ],
5240
5445
  confirmMessage: "Are you sure you want to delete this movie?",
5241
- run: (c3, a2) => c3.deleteMovie(a2.id)
5446
+ run: async (c3, a2) => {
5447
+ const movieResult = await c3.getMovie(a2.id);
5448
+ if (movieResult?.error)
5449
+ return movieResult;
5450
+ const movie = unwrapData(movieResult);
5451
+ const deleteResult = await c3.deleteMovie(a2.id, {
5452
+ deleteFiles: a2["delete-files"],
5453
+ addImportExclusion: a2["add-import-exclusion"]
5454
+ });
5455
+ if (deleteResult?.error)
5456
+ return deleteResult;
5457
+ return { message: `Deleted: ${movie.title} (ID: ${movie.id})` };
5458
+ }
5242
5459
  }
5243
5460
  ]
5244
5461
  },
@@ -5264,6 +5481,28 @@ var init_radarr3 = __esm(() => {
5264
5481
  name: "tag",
5265
5482
  description: "Manage tags",
5266
5483
  actions: [
5484
+ {
5485
+ name: "create",
5486
+ description: "Create a tag",
5487
+ args: [{ name: "label", description: "Tag label", required: true }],
5488
+ run: (c3, a2) => c3.addTag({ label: a2.label })
5489
+ },
5490
+ {
5491
+ name: "delete",
5492
+ description: "Delete a tag",
5493
+ args: [{ name: "id", description: "Tag ID", required: true, type: "number" }],
5494
+ confirmMessage: "Are you sure you want to delete this tag?",
5495
+ run: async (c3, a2) => {
5496
+ const tagResult = await c3.getTag(a2.id);
5497
+ if (tagResult?.error)
5498
+ return tagResult;
5499
+ const tag = unwrapData(tagResult);
5500
+ const deleteResult = await c3.deleteTag(a2.id);
5501
+ if (deleteResult?.error)
5502
+ return deleteResult;
5503
+ return { message: `Deleted tag: ${tag.label} (ID: ${tag.id})` };
5504
+ }
5505
+ },
5267
5506
  {
5268
5507
  name: "list",
5269
5508
  description: "List all tags",
@@ -5286,6 +5525,27 @@ var init_radarr3 = __esm(() => {
5286
5525
  name: "status",
5287
5526
  description: "Get queue status",
5288
5527
  run: (c3) => c3.getQueueStatus()
5528
+ },
5529
+ {
5530
+ name: "delete",
5531
+ description: "Remove an item from the queue",
5532
+ args: [
5533
+ { name: "id", description: "Queue item ID", required: true, type: "number" },
5534
+ { name: "blocklist", description: "Add to blocklist", type: "boolean" },
5535
+ {
5536
+ name: "remove-from-client",
5537
+ description: "Remove from download client",
5538
+ type: "boolean"
5539
+ }
5540
+ ],
5541
+ confirmMessage: "Are you sure you want to remove this queue item?",
5542
+ run: (c3, a2) => c3.removeQueueItem(a2.id, a2["remove-from-client"], a2.blocklist)
5543
+ },
5544
+ {
5545
+ name: "grab",
5546
+ description: "Force download a queue item",
5547
+ args: [{ name: "id", description: "Queue item ID", required: true, type: "number" }],
5548
+ run: (c3, a2) => c3.grabQueueItem(a2.id)
5289
5549
  }
5290
5550
  ]
5291
5551
  },
@@ -5298,6 +5558,19 @@ var init_radarr3 = __esm(() => {
5298
5558
  description: "List root folders",
5299
5559
  columns: ["id", "path", "freeSpace"],
5300
5560
  run: (c3) => c3.getRootFolders()
5561
+ },
5562
+ {
5563
+ name: "add",
5564
+ description: "Add a root folder",
5565
+ args: [{ name: "path", description: "Folder path", required: true }],
5566
+ run: (c3, a2) => c3.addRootFolder(a2.path)
5567
+ },
5568
+ {
5569
+ name: "delete",
5570
+ description: "Delete a root folder",
5571
+ args: [{ name: "id", description: "Root folder ID", required: true, type: "number" }],
5572
+ confirmMessage: "Are you sure you want to delete this root folder?",
5573
+ run: (c3, a2) => c3.deleteRootFolder(a2.id)
5301
5574
  }
5302
5575
  ]
5303
5576
  },
@@ -5325,8 +5598,168 @@ var init_radarr3 = __esm(() => {
5325
5598
  {
5326
5599
  name: "list",
5327
5600
  description: "List recent history",
5601
+ args: [
5602
+ { name: "since", description: "Start date (ISO 8601, e.g. 2024-01-01)" },
5603
+ { name: "until", description: "End date (ISO 8601, e.g. 2024-12-31)" }
5604
+ ],
5328
5605
  columns: ["id", "eventType", "sourceTitle", "date"],
5329
- run: (c3) => c3.getHistory()
5606
+ run: async (c3, a2) => {
5607
+ if (a2.since) {
5608
+ const result2 = await c3.getHistorySince(a2.since);
5609
+ const items2 = unwrapData(result2);
5610
+ if (a2.until) {
5611
+ const untilDate = new Date(a2.until);
5612
+ return items2.filter((item) => new Date(item.date) <= untilDate);
5613
+ }
5614
+ return items2;
5615
+ }
5616
+ const result = await c3.getHistory();
5617
+ const items = unwrapData(result);
5618
+ if (a2.until) {
5619
+ const untilDate = new Date(a2.until);
5620
+ return items.filter((item) => new Date(item.date) <= untilDate);
5621
+ }
5622
+ return items;
5623
+ }
5624
+ }
5625
+ ]
5626
+ },
5627
+ {
5628
+ name: "calendar",
5629
+ description: "View upcoming releases",
5630
+ actions: [
5631
+ {
5632
+ name: "list",
5633
+ description: "List upcoming movie releases",
5634
+ args: [
5635
+ { name: "start", description: "Start date (ISO 8601)" },
5636
+ { name: "end", description: "End date (ISO 8601)" },
5637
+ { name: "unmonitored", description: "Include unmonitored", type: "boolean" }
5638
+ ],
5639
+ columns: ["id", "title", "year", "inCinemas", "digitalRelease", "physicalRelease"],
5640
+ run: (c3, a2) => c3.getCalendar(a2.start, a2.end, a2.unmonitored)
5641
+ }
5642
+ ]
5643
+ },
5644
+ {
5645
+ name: "notification",
5646
+ description: "Manage notifications",
5647
+ actions: [
5648
+ {
5649
+ name: "list",
5650
+ description: "List notification providers",
5651
+ columns: ["id", "name", "implementation"],
5652
+ run: (c3) => c3.getNotifications()
5653
+ },
5654
+ {
5655
+ name: "get",
5656
+ description: "Get a notification by ID",
5657
+ args: [{ name: "id", description: "Notification ID", required: true, type: "number" }],
5658
+ run: (c3, a2) => c3.getNotification(a2.id)
5659
+ },
5660
+ {
5661
+ name: "delete",
5662
+ description: "Delete a notification",
5663
+ args: [{ name: "id", description: "Notification ID", required: true, type: "number" }],
5664
+ confirmMessage: "Are you sure you want to delete this notification?",
5665
+ run: (c3, a2) => c3.deleteNotification(a2.id)
5666
+ },
5667
+ {
5668
+ name: "test",
5669
+ description: "Test all notifications",
5670
+ run: (c3) => c3.testAllNotifications()
5671
+ }
5672
+ ]
5673
+ },
5674
+ {
5675
+ name: "downloadclient",
5676
+ description: "Manage download clients",
5677
+ actions: [
5678
+ {
5679
+ name: "list",
5680
+ description: "List download clients",
5681
+ columns: ["id", "name", "implementation", "enable"],
5682
+ run: (c3) => c3.getDownloadClients()
5683
+ },
5684
+ {
5685
+ name: "get",
5686
+ description: "Get a download client by ID",
5687
+ args: [{ name: "id", description: "Download client ID", required: true, type: "number" }],
5688
+ run: (c3, a2) => c3.getDownloadClient(a2.id)
5689
+ },
5690
+ {
5691
+ name: "delete",
5692
+ description: "Delete a download client",
5693
+ args: [{ name: "id", description: "Download client ID", required: true, type: "number" }],
5694
+ confirmMessage: "Are you sure you want to delete this download client?",
5695
+ run: (c3, a2) => c3.deleteDownloadClient(a2.id)
5696
+ },
5697
+ {
5698
+ name: "test",
5699
+ description: "Test all download clients",
5700
+ run: (c3) => c3.testAllDownloadClients()
5701
+ }
5702
+ ]
5703
+ },
5704
+ {
5705
+ name: "blocklist",
5706
+ description: "Manage blocked releases",
5707
+ actions: [
5708
+ {
5709
+ name: "list",
5710
+ description: "List blocked releases",
5711
+ columns: ["id", "sourceTitle", "date"],
5712
+ run: (c3) => c3.getBlocklist()
5713
+ },
5714
+ {
5715
+ name: "delete",
5716
+ description: "Remove a release from the blocklist",
5717
+ args: [{ name: "id", description: "Blocklist item ID", required: true, type: "number" }],
5718
+ confirmMessage: "Are you sure you want to remove this blocklist entry?",
5719
+ run: (c3, a2) => c3.removeBlocklistItem(a2.id)
5720
+ }
5721
+ ]
5722
+ },
5723
+ {
5724
+ name: "wanted",
5725
+ description: "View missing and cutoff unmet movies",
5726
+ actions: [
5727
+ {
5728
+ name: "missing",
5729
+ description: "List movies with missing files",
5730
+ columns: ["id", "title", "year", "monitored"],
5731
+ run: (c3) => c3.getWantedMissing()
5732
+ },
5733
+ {
5734
+ name: "cutoff",
5735
+ description: "List movies below quality cutoff",
5736
+ columns: ["id", "title", "year", "monitored"],
5737
+ run: (c3) => c3.getWantedCutoff()
5738
+ }
5739
+ ]
5740
+ },
5741
+ {
5742
+ name: "importlist",
5743
+ description: "Manage import lists",
5744
+ actions: [
5745
+ {
5746
+ name: "list",
5747
+ description: "List import lists",
5748
+ columns: ["id", "name", "implementation", "enable"],
5749
+ run: (c3) => c3.getImportLists()
5750
+ },
5751
+ {
5752
+ name: "get",
5753
+ description: "Get an import list by ID",
5754
+ args: [{ name: "id", description: "Import list ID", required: true, type: "number" }],
5755
+ run: (c3, a2) => c3.getImportList(a2.id)
5756
+ },
5757
+ {
5758
+ name: "delete",
5759
+ description: "Delete an import list",
5760
+ args: [{ name: "id", description: "Import list ID", required: true, type: "number" }],
5761
+ confirmMessage: "Are you sure you want to delete this import list?",
5762
+ run: (c3, a2) => c3.deleteImportList(a2.id)
5330
5763
  }
5331
5764
  ]
5332
5765
  },
@@ -7261,8 +7694,11 @@ class SonarrClient {
7261
7694
  async updateSeries(id, series) {
7262
7695
  return putApiV3SeriesById({ path: { id }, body: series });
7263
7696
  }
7264
- async deleteSeries(id) {
7265
- return deleteApiV3SeriesById({ path: { id } });
7697
+ async deleteSeries(id, options) {
7698
+ return deleteApiV3SeriesById({
7699
+ path: { id },
7700
+ ...options ? { query: options } : {}
7701
+ });
7266
7702
  }
7267
7703
  async getSeriesFolder(id) {
7268
7704
  return getApiV3SeriesByIdFolder({ path: { id } });
@@ -7385,8 +7821,13 @@ class SonarrClient {
7385
7821
  async getTagDetailById(id) {
7386
7822
  return getApiV3TagDetailById2({ path: { id } });
7387
7823
  }
7388
- async getEpisodes() {
7389
- return getApiV3Episode();
7824
+ async getEpisodes(seriesId, episodeIds) {
7825
+ const query = {};
7826
+ if (seriesId !== undefined)
7827
+ query.seriesId = seriesId;
7828
+ if (episodeIds !== undefined)
7829
+ query.episodeIds = episodeIds;
7830
+ return getApiV3Episode(Object.keys(query).length > 0 ? { query } : {});
7390
7831
  }
7391
7832
  async getEpisode(id) {
7392
7833
  return getApiV3EpisodeById({ path: { id } });
@@ -7600,7 +8041,7 @@ class SonarrClient {
7600
8041
  query.tags = tags;
7601
8042
  return getFeedV3CalendarSonarrIcs(Object.keys(query).length > 0 ? { query } : {});
7602
8043
  }
7603
- async getQueue(page, pageSize, sortKey, sortDirection, includeUnknownSeriesItems) {
8044
+ async getQueue(page, pageSize, sortKey, sortDirection, includeUnknownSeriesItems, seriesId) {
7604
8045
  const query = {};
7605
8046
  if (page !== undefined)
7606
8047
  query.page = page;
@@ -7612,6 +8053,8 @@ class SonarrClient {
7612
8053
  query.sortDirection = sortDirection;
7613
8054
  if (includeUnknownSeriesItems !== undefined)
7614
8055
  query.includeUnknownSeriesItems = includeUnknownSeriesItems;
8056
+ if (seriesId !== undefined)
8057
+ query.seriesIds = [seriesId];
7615
8058
  return getApiV3Queue2(Object.keys(query).length > 0 ? { query } : {});
7616
8059
  }
7617
8060
  async removeQueueItem(id, removeFromClient, blocklist) {
@@ -7734,6 +8177,65 @@ var exports_sonarr3 = {};
7734
8177
  __export(exports_sonarr3, {
7735
8178
  sonarr: () => sonarr
7736
8179
  });
8180
+ function unwrapData2(result) {
8181
+ return result?.data ?? result;
8182
+ }
8183
+ function parseBooleanArg2(value, fallback) {
8184
+ if (value === undefined)
8185
+ return fallback;
8186
+ if (typeof value === "boolean")
8187
+ return value;
8188
+ if (typeof value === "string") {
8189
+ const normalized = value.trim().toLowerCase();
8190
+ if (normalized === "true")
8191
+ return true;
8192
+ if (normalized === "false")
8193
+ return false;
8194
+ }
8195
+ return Boolean(value);
8196
+ }
8197
+ function resolveQualityProfileId2(profiles, profileId) {
8198
+ const profile = profiles.find((item) => item?.id === profileId);
8199
+ if (!profile) {
8200
+ throw new Error(`Quality profile ${profileId} was not found.`);
8201
+ }
8202
+ return profileId;
8203
+ }
8204
+ function resolveRootFolderPath2(folders, rootFolderPath) {
8205
+ const folder = folders.find((item) => item?.path === rootFolderPath);
8206
+ if (!folder) {
8207
+ throw new Error(`Root folder "${rootFolderPath}" was not found.`);
8208
+ }
8209
+ return rootFolderPath;
8210
+ }
8211
+ function formatSeriesListItem(series) {
8212
+ const seasons = Array.isArray(series?.seasons) ? series.seasons.filter((season) => season?.seasonNumber !== 0) : [];
8213
+ const statistics = series?.statistics ?? {};
8214
+ return {
8215
+ ...series,
8216
+ seasonCount: seasons.length,
8217
+ episodeCount: statistics.episodeCount !== undefined ? `${statistics.episodeFileCount ?? 0}/${statistics.episodeCount}` : "—",
8218
+ network: series?.network,
8219
+ status: series?.status
8220
+ };
8221
+ }
8222
+ async function lookupSeriesByTvdbId(client3, tvdbId) {
8223
+ const tvdbSearch = unwrapData2(await client3.searchSeries(`tvdb:${tvdbId}`));
8224
+ const exactTvdbMatch = tvdbSearch.find((series) => series?.tvdbId === tvdbId);
8225
+ if (exactTvdbMatch)
8226
+ return exactTvdbMatch;
8227
+ const fallbackSearch = unwrapData2(await client3.searchSeries(String(tvdbId)));
8228
+ return fallbackSearch.find((series) => series?.tvdbId === tvdbId);
8229
+ }
8230
+ async function findSeriesByTvdbId(client3, tvdbId) {
8231
+ if (tvdbId === undefined)
8232
+ return;
8233
+ const series = unwrapData2(await client3.getSeries());
8234
+ return series.find((item) => item?.tvdbId === tvdbId);
8235
+ }
8236
+ function getApiStatus2(result) {
8237
+ return result?.error?.status ?? result?.response?.status;
8238
+ }
7737
8239
  var resources2, sonarr;
7738
8240
  var init_sonarr3 = __esm(() => {
7739
8241
  init_sonarr2();
@@ -7747,8 +8249,20 @@ var init_sonarr3 = __esm(() => {
7747
8249
  {
7748
8250
  name: "list",
7749
8251
  description: "List all series",
7750
- columns: ["id", "title", "year", "monitored", "seasonCount"],
7751
- run: (c3) => c3.getSeries()
8252
+ columns: [
8253
+ "id",
8254
+ "title",
8255
+ "year",
8256
+ "monitored",
8257
+ "seasonCount",
8258
+ "episodeCount",
8259
+ "network",
8260
+ "status"
8261
+ ],
8262
+ run: async (c3) => {
8263
+ const series = unwrapData2(await c3.getSeries());
8264
+ return series.map(formatSeriesListItem);
8265
+ }
7752
8266
  },
7753
8267
  {
7754
8268
  name: "get",
@@ -7766,40 +8280,65 @@ var init_sonarr3 = __esm(() => {
7766
8280
  {
7767
8281
  name: "add",
7768
8282
  description: "Search and add a series",
7769
- args: [{ name: "term", description: "Search term", required: true }],
8283
+ args: [
8284
+ { name: "term", description: "Search term" },
8285
+ { name: "tvdb-id", description: "TVDB ID", type: "number" },
8286
+ { name: "quality-profile-id", description: "Quality profile ID", type: "number" },
8287
+ { name: "root-folder", description: "Root folder path" },
8288
+ { name: "monitored", description: "Set monitored (true/false)" }
8289
+ ],
7770
8290
  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.");
8291
+ let series;
8292
+ if (a2["tvdb-id"] !== undefined) {
8293
+ series = await lookupSeriesByTvdbId(c3, a2["tvdb-id"]);
8294
+ if (!series) {
8295
+ throw new Error(`No series found for TVDB ID ${a2["tvdb-id"]}.`);
8296
+ }
8297
+ } else {
8298
+ const term = await promptIfMissing(a2.term, "Search term:");
8299
+ const searchResult = await c3.searchSeries(term);
8300
+ const results = unwrapData2(searchResult);
8301
+ if (!Array.isArray(results) || results.length === 0) {
8302
+ throw new Error("No series found.");
8303
+ }
8304
+ const seriesId = await promptSelect("Select a series:", results.map((s2) => ({
8305
+ label: `${s2.title} (${s2.year})`,
8306
+ value: String(s2.tvdbId)
8307
+ })));
8308
+ series = results.find((s2) => String(s2.tvdbId) === seriesId);
8309
+ if (!series) {
8310
+ throw new Error("Selected series was not found in the search results.");
8311
+ }
7780
8312
  }
7781
8313
  const profilesResult = await c3.getQualityProfiles();
7782
- const profiles = profilesResult?.data ?? profilesResult;
8314
+ const profiles = unwrapData2(profilesResult);
7783
8315
  if (!Array.isArray(profiles) || profiles.length === 0) {
7784
8316
  throw new Error("No quality profiles found. Configure one in Sonarr first.");
7785
8317
  }
7786
- const profileId = await promptSelect("Select quality profile:", profiles.map((p) => ({ label: p.name, value: String(p.id) })));
8318
+ 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
8319
  const foldersResult = await c3.getRootFolders();
7788
- const folders = foldersResult?.data ?? foldersResult;
8320
+ const folders = unwrapData2(foldersResult);
7789
8321
  if (!Array.isArray(folders) || folders.length === 0) {
7790
8322
  throw new Error("No root folders found. Configure one in Sonarr first.");
7791
8323
  }
7792
- const rootFolderPath = await promptSelect("Select root folder:", folders.map((f3) => ({ label: f3.path, value: f3.path })));
8324
+ 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
8325
  const confirmed = await promptConfirm(`Add "${series.title} (${series.year})"?`, !!a2.yes);
7794
8326
  if (!confirmed)
7795
8327
  throw new Error("Cancelled.");
7796
- return c3.addSeries({
8328
+ const addResult = await c3.addSeries({
7797
8329
  ...series,
7798
- qualityProfileId: Number(profileId),
8330
+ qualityProfileId: profileId,
7799
8331
  rootFolderPath,
7800
- monitored: true,
8332
+ monitored: parseBooleanArg2(a2.monitored, true),
7801
8333
  addOptions: { searchForMissingEpisodes: true }
7802
8334
  });
8335
+ if (addResult?.error && getApiStatus2(addResult) === 400) {
8336
+ const existingSeries = await findSeriesByTvdbId(c3, series.tvdbId);
8337
+ if (existingSeries) {
8338
+ throw new Error(`${existingSeries.title} is already in your library (ID: ${existingSeries.id})`);
8339
+ }
8340
+ }
8341
+ return addResult;
7803
8342
  }
7804
8343
  },
7805
8344
  {
@@ -7839,9 +8378,29 @@ var init_sonarr3 = __esm(() => {
7839
8378
  {
7840
8379
  name: "delete",
7841
8380
  description: "Delete a series",
7842
- args: [{ name: "id", description: "Series ID", required: true, type: "number" }],
8381
+ args: [
8382
+ { name: "id", description: "Series ID", required: true, type: "number" },
8383
+ { name: "delete-files", description: "Delete series files", type: "boolean" },
8384
+ {
8385
+ name: "add-import-list-exclusion",
8386
+ description: "Add import list exclusion after delete",
8387
+ type: "boolean"
8388
+ }
8389
+ ],
7843
8390
  confirmMessage: "Are you sure you want to delete this series?",
7844
- run: (c3, a2) => c3.deleteSeries(a2.id)
8391
+ run: async (c3, a2) => {
8392
+ const seriesResult = await c3.getSeriesById(a2.id);
8393
+ if (seriesResult?.error)
8394
+ return seriesResult;
8395
+ const series = unwrapData2(seriesResult);
8396
+ const deleteResult = await c3.deleteSeries(a2.id, {
8397
+ deleteFiles: a2["delete-files"],
8398
+ addImportListExclusion: a2["add-import-list-exclusion"]
8399
+ });
8400
+ if (deleteResult?.error)
8401
+ return deleteResult;
8402
+ return { message: `Deleted: ${series.title} (ID: ${series.id})` };
8403
+ }
7845
8404
  }
7846
8405
  ]
7847
8406
  },
@@ -7852,14 +8411,21 @@ var init_sonarr3 = __esm(() => {
7852
8411
  {
7853
8412
  name: "list",
7854
8413
  description: "List all episodes",
8414
+ args: [{ name: "series-id", description: "Series ID", required: true, type: "number" }],
7855
8415
  columns: ["id", "title", "seasonNumber", "episodeNumber", "hasFile"],
7856
- run: (c3) => c3.getEpisodes()
8416
+ run: (c3, a2) => c3.getEpisodes(a2["series-id"])
7857
8417
  },
7858
8418
  {
7859
8419
  name: "get",
7860
8420
  description: "Get an episode by ID",
7861
8421
  args: [{ name: "id", description: "Episode ID", required: true, type: "number" }],
7862
8422
  run: (c3, a2) => c3.getEpisode(a2.id)
8423
+ },
8424
+ {
8425
+ name: "search",
8426
+ description: "Trigger a search for an episode",
8427
+ args: [{ name: "id", description: "Episode ID", required: true, type: "number" }],
8428
+ run: (c3, a2) => c3.runCommand({ name: "EpisodeSearch", episodeIds: [a2.id] })
7863
8429
  }
7864
8430
  ]
7865
8431
  },
@@ -7879,6 +8445,28 @@ var init_sonarr3 = __esm(() => {
7879
8445
  name: "tag",
7880
8446
  description: "Manage tags",
7881
8447
  actions: [
8448
+ {
8449
+ name: "create",
8450
+ description: "Create a tag",
8451
+ args: [{ name: "label", description: "Tag label", required: true }],
8452
+ run: (c3, a2) => c3.addTag({ label: a2.label })
8453
+ },
8454
+ {
8455
+ name: "delete",
8456
+ description: "Delete a tag",
8457
+ args: [{ name: "id", description: "Tag ID", required: true, type: "number" }],
8458
+ confirmMessage: "Are you sure you want to delete this tag?",
8459
+ run: async (c3, a2) => {
8460
+ const tagResult = await c3.getTag(a2.id);
8461
+ if (tagResult?.error)
8462
+ return tagResult;
8463
+ const tag = unwrapData2(tagResult);
8464
+ const deleteResult = await c3.deleteTag(a2.id);
8465
+ if (deleteResult?.error)
8466
+ return deleteResult;
8467
+ return { message: `Deleted tag: ${tag.label} (ID: ${tag.id})` };
8468
+ }
8469
+ },
7882
8470
  {
7883
8471
  name: "list",
7884
8472
  description: "List all tags",
@@ -7887,6 +8475,79 @@ var init_sonarr3 = __esm(() => {
7887
8475
  }
7888
8476
  ]
7889
8477
  },
8478
+ {
8479
+ name: "queue",
8480
+ description: "Manage download queue",
8481
+ actions: [
8482
+ {
8483
+ name: "list",
8484
+ description: "List queue items",
8485
+ args: [{ name: "series-id", description: "Series ID", type: "number" }],
8486
+ columns: ["id", "title", "status", "sizeleft", "timeleft"],
8487
+ run: (c3, a2) => c3.getQueue(undefined, undefined, undefined, undefined, undefined, a2["series-id"])
8488
+ },
8489
+ {
8490
+ name: "status",
8491
+ description: "Get queue status",
8492
+ run: (c3) => c3.getQueueStatus()
8493
+ },
8494
+ {
8495
+ name: "delete",
8496
+ description: "Remove an item from the queue",
8497
+ args: [
8498
+ { name: "id", description: "Queue item ID", required: true, type: "number" },
8499
+ { name: "blocklist", description: "Add to blocklist", type: "boolean" },
8500
+ {
8501
+ name: "remove-from-client",
8502
+ description: "Remove from download client",
8503
+ type: "boolean"
8504
+ }
8505
+ ],
8506
+ confirmMessage: "Are you sure you want to remove this queue item?",
8507
+ run: (c3, a2) => c3.removeQueueItem(a2.id, a2["remove-from-client"], a2.blocklist)
8508
+ },
8509
+ {
8510
+ name: "grab",
8511
+ description: "Force download a queue item",
8512
+ args: [{ name: "id", description: "Queue item ID", required: true, type: "number" }],
8513
+ run: (c3, a2) => c3.grabQueueItem(a2.id)
8514
+ }
8515
+ ]
8516
+ },
8517
+ {
8518
+ name: "history",
8519
+ description: "View history",
8520
+ actions: [
8521
+ {
8522
+ name: "list",
8523
+ description: "List recent history",
8524
+ args: [
8525
+ { name: "series-id", description: "Series ID", type: "number" },
8526
+ { name: "since", description: "Start date (ISO 8601, e.g. 2024-01-01)" },
8527
+ { name: "until", description: "End date (ISO 8601, e.g. 2024-12-31)" }
8528
+ ],
8529
+ columns: ["id", "eventType", "sourceTitle", "date"],
8530
+ run: async (c3, a2) => {
8531
+ if (a2.since) {
8532
+ const result2 = await c3.getHistorySince(a2.since, a2["series-id"]);
8533
+ const items2 = unwrapData2(result2);
8534
+ if (a2.until) {
8535
+ const untilDate = new Date(a2.until);
8536
+ return items2.filter((item) => new Date(item.date) <= untilDate);
8537
+ }
8538
+ return items2;
8539
+ }
8540
+ const result = await c3.getHistory(undefined, undefined, undefined, undefined, a2["series-id"]);
8541
+ const items = unwrapData2(result);
8542
+ if (a2.until) {
8543
+ const untilDate = new Date(a2.until);
8544
+ return items.filter((item) => new Date(item.date) <= untilDate);
8545
+ }
8546
+ return items;
8547
+ }
8548
+ }
8549
+ ]
8550
+ },
7890
8551
  {
7891
8552
  name: "rootfolder",
7892
8553
  description: "Manage root folders",
@@ -7896,6 +8557,158 @@ var init_sonarr3 = __esm(() => {
7896
8557
  description: "List root folders",
7897
8558
  columns: ["id", "path", "freeSpace"],
7898
8559
  run: (c3) => c3.getRootFolders()
8560
+ },
8561
+ {
8562
+ name: "add",
8563
+ description: "Add a root folder",
8564
+ args: [{ name: "path", description: "Folder path", required: true }],
8565
+ run: (c3, a2) => c3.addRootFolder(a2.path)
8566
+ },
8567
+ {
8568
+ name: "delete",
8569
+ description: "Delete a root folder",
8570
+ args: [{ name: "id", description: "Root folder ID", required: true, type: "number" }],
8571
+ confirmMessage: "Are you sure you want to delete this root folder?",
8572
+ run: (c3, a2) => c3.deleteRootFolder(a2.id)
8573
+ }
8574
+ ]
8575
+ },
8576
+ {
8577
+ name: "calendar",
8578
+ description: "View upcoming releases",
8579
+ actions: [
8580
+ {
8581
+ name: "list",
8582
+ description: "List upcoming episode releases",
8583
+ args: [
8584
+ { name: "start", description: "Start date (ISO 8601)" },
8585
+ { name: "end", description: "End date (ISO 8601)" },
8586
+ { name: "unmonitored", description: "Include unmonitored", type: "boolean" }
8587
+ ],
8588
+ columns: ["id", "seriesTitle", "title", "seasonNumber", "episodeNumber", "airDateUtc"],
8589
+ run: (c3, a2) => c3.getCalendar(a2.start, a2.end, a2.unmonitored)
8590
+ }
8591
+ ]
8592
+ },
8593
+ {
8594
+ name: "notification",
8595
+ description: "Manage notifications",
8596
+ actions: [
8597
+ {
8598
+ name: "list",
8599
+ description: "List notification providers",
8600
+ columns: ["id", "name", "implementation"],
8601
+ run: (c3) => c3.getNotifications()
8602
+ },
8603
+ {
8604
+ name: "get",
8605
+ description: "Get a notification by ID",
8606
+ args: [{ name: "id", description: "Notification ID", required: true, type: "number" }],
8607
+ run: (c3, a2) => c3.getNotification(a2.id)
8608
+ },
8609
+ {
8610
+ name: "delete",
8611
+ description: "Delete a notification",
8612
+ args: [{ name: "id", description: "Notification ID", required: true, type: "number" }],
8613
+ confirmMessage: "Are you sure you want to delete this notification?",
8614
+ run: (c3, a2) => c3.deleteNotification(a2.id)
8615
+ },
8616
+ {
8617
+ name: "test",
8618
+ description: "Test all notifications",
8619
+ run: (c3) => c3.testAllNotifications()
8620
+ }
8621
+ ]
8622
+ },
8623
+ {
8624
+ name: "downloadclient",
8625
+ description: "Manage download clients",
8626
+ actions: [
8627
+ {
8628
+ name: "list",
8629
+ description: "List download clients",
8630
+ columns: ["id", "name", "implementation", "enable"],
8631
+ run: (c3) => c3.getDownloadClients()
8632
+ },
8633
+ {
8634
+ name: "get",
8635
+ description: "Get a download client by ID",
8636
+ args: [{ name: "id", description: "Download client ID", required: true, type: "number" }],
8637
+ run: (c3, a2) => c3.getDownloadClient(a2.id)
8638
+ },
8639
+ {
8640
+ name: "delete",
8641
+ description: "Delete a download client",
8642
+ args: [{ name: "id", description: "Download client ID", required: true, type: "number" }],
8643
+ confirmMessage: "Are you sure you want to delete this download client?",
8644
+ run: (c3, a2) => c3.deleteDownloadClient(a2.id)
8645
+ },
8646
+ {
8647
+ name: "test",
8648
+ description: "Test all download clients",
8649
+ run: (c3) => c3.testAllDownloadClients()
8650
+ }
8651
+ ]
8652
+ },
8653
+ {
8654
+ name: "blocklist",
8655
+ description: "Manage blocked releases",
8656
+ actions: [
8657
+ {
8658
+ name: "list",
8659
+ description: "List blocked releases",
8660
+ columns: ["id", "sourceTitle", "date"],
8661
+ run: (c3) => c3.getBlocklist()
8662
+ },
8663
+ {
8664
+ name: "delete",
8665
+ description: "Remove a release from the blocklist",
8666
+ args: [{ name: "id", description: "Blocklist item ID", required: true, type: "number" }],
8667
+ confirmMessage: "Are you sure you want to remove this blocklist entry?",
8668
+ run: (c3, a2) => c3.removeBlocklistItem(a2.id)
8669
+ }
8670
+ ]
8671
+ },
8672
+ {
8673
+ name: "wanted",
8674
+ description: "View missing and cutoff unmet episodes",
8675
+ actions: [
8676
+ {
8677
+ name: "missing",
8678
+ description: "List episodes with missing files",
8679
+ columns: ["id", "title", "seasonNumber", "episodeNumber", "airDateUtc"],
8680
+ run: (c3) => c3.getWantedMissing()
8681
+ },
8682
+ {
8683
+ name: "cutoff",
8684
+ description: "List episodes below quality cutoff",
8685
+ columns: ["id", "title", "seasonNumber", "episodeNumber", "airDateUtc"],
8686
+ run: (c3) => c3.getWantedCutoff()
8687
+ }
8688
+ ]
8689
+ },
8690
+ {
8691
+ name: "importlist",
8692
+ description: "Manage import lists",
8693
+ actions: [
8694
+ {
8695
+ name: "list",
8696
+ description: "List import lists",
8697
+ columns: ["id", "name", "implementation", "enable"],
8698
+ run: (c3) => c3.getImportLists()
8699
+ },
8700
+ {
8701
+ name: "get",
8702
+ description: "Get an import list by ID",
8703
+ args: [{ name: "id", description: "Import list ID", required: true, type: "number" }],
8704
+ run: (c3, a2) => c3.getImportList(a2.id)
8705
+ },
8706
+ {
8707
+ name: "delete",
8708
+ description: "Delete an import list",
8709
+ args: [{ name: "id", description: "Import list ID", required: true, type: "number" }],
8710
+ confirmMessage: "Are you sure you want to delete this import list?",
8711
+ run: (c3, a2) => c3.deleteImportList(a2.id)
7899
8712
  }
7900
8713
  ]
7901
8714
  },
@@ -9649,6 +10462,14 @@ var getApiV1Album = (options) => (options?.client ?? client3).get({
9649
10462
  }],
9650
10463
  url: "/api/v1/queue/status",
9651
10464
  ...options
10465
+ }), deleteApiV1RootfolderById = (options) => (options.client ?? client3).delete({
10466
+ security: [{ name: "X-Api-Key", type: "apiKey" }, {
10467
+ in: "query",
10468
+ name: "apikey",
10469
+ type: "apiKey"
10470
+ }],
10471
+ url: "/api/v1/rootfolder/{id}",
10472
+ ...options
9652
10473
  }), getApiV1Rootfolder = (options) => (options?.client ?? client3).get({
9653
10474
  security: [{ name: "X-Api-Key", type: "apiKey" }, {
9654
10475
  in: "query",
@@ -9854,6 +10675,9 @@ class LidarrClient {
9854
10675
  body: { path }
9855
10676
  });
9856
10677
  }
10678
+ async deleteRootFolder(id) {
10679
+ return deleteApiV1RootfolderById({ path: { id } });
10680
+ }
9857
10681
  async addAlbum(album) {
9858
10682
  return postApiV1Album({ body: album });
9859
10683
  }
@@ -10409,6 +11233,28 @@ var init_lidarr3 = __esm(() => {
10409
11233
  name: "tag",
10410
11234
  description: "Manage tags",
10411
11235
  actions: [
11236
+ {
11237
+ name: "create",
11238
+ description: "Create a tag",
11239
+ args: [{ name: "label", description: "Tag label", required: true }],
11240
+ run: (c3, a2) => c3.addTag({ label: a2.label })
11241
+ },
11242
+ {
11243
+ name: "delete",
11244
+ description: "Delete a tag",
11245
+ args: [{ name: "id", description: "Tag ID", required: true, type: "number" }],
11246
+ confirmMessage: "Are you sure you want to delete this tag?",
11247
+ run: async (c3, a2) => {
11248
+ const tagResult = await c3.getTag(a2.id);
11249
+ if (tagResult?.error)
11250
+ return tagResult;
11251
+ const tag = tagResult?.data ?? tagResult;
11252
+ const deleteResult = await c3.deleteTag(a2.id);
11253
+ if (deleteResult?.error)
11254
+ return deleteResult;
11255
+ return { message: `Deleted tag: ${tag.label} (ID: ${tag.id})` };
11256
+ }
11257
+ },
10412
11258
  {
10413
11259
  name: "list",
10414
11260
  description: "List all tags",
@@ -10426,6 +11272,19 @@ var init_lidarr3 = __esm(() => {
10426
11272
  description: "List root folders",
10427
11273
  columns: ["id", "path", "freeSpace"],
10428
11274
  run: (c3) => c3.getRootFolders()
11275
+ },
11276
+ {
11277
+ name: "add",
11278
+ description: "Add a root folder",
11279
+ args: [{ name: "path", description: "Folder path", required: true }],
11280
+ run: (c3, a2) => c3.addRootFolder(a2.path)
11281
+ },
11282
+ {
11283
+ name: "delete",
11284
+ description: "Delete a root folder",
11285
+ args: [{ name: "id", description: "Root folder ID", required: true, type: "number" }],
11286
+ confirmMessage: "Are you sure you want to delete this root folder?",
11287
+ run: (c3, a2) => c3.deleteRootFolder(a2.id)
10429
11288
  }
10430
11289
  ]
10431
11290
  },
@@ -12163,6 +13022,14 @@ var getApiV1Author = (options) => (options?.client ?? client4).get({
12163
13022
  "Content-Type": "application/json",
12164
13023
  ...options?.headers
12165
13024
  }
13025
+ }), deleteApiV1RootfolderById2 = (options) => (options.client ?? client4).delete({
13026
+ security: [{ name: "X-Api-Key", type: "apiKey" }, {
13027
+ in: "query",
13028
+ name: "apikey",
13029
+ type: "apiKey"
13030
+ }],
13031
+ url: "/api/v1/rootfolder/{id}",
13032
+ ...options
12166
13033
  }), getApiV1SystemStatus2 = (options) => (options?.client ?? client4).get({
12167
13034
  security: [{ name: "X-Api-Key", type: "apiKey" }, {
12168
13035
  in: "query",
@@ -12348,6 +13215,9 @@ class ReadarrClient {
12348
13215
  body: { path }
12349
13216
  });
12350
13217
  }
13218
+ async deleteRootFolder(id) {
13219
+ return deleteApiV1RootfolderById2({ path: { id } });
13220
+ }
12351
13221
  async getHostConfig() {
12352
13222
  return getApiV1ConfigHost2();
12353
13223
  }
@@ -12880,6 +13750,28 @@ var init_readarr3 = __esm(() => {
12880
13750
  name: "tag",
12881
13751
  description: "Manage tags",
12882
13752
  actions: [
13753
+ {
13754
+ name: "create",
13755
+ description: "Create a tag",
13756
+ args: [{ name: "label", description: "Tag label", required: true }],
13757
+ run: (c3, a2) => c3.addTag({ label: a2.label })
13758
+ },
13759
+ {
13760
+ name: "delete",
13761
+ description: "Delete a tag",
13762
+ args: [{ name: "id", description: "Tag ID", required: true, type: "number" }],
13763
+ confirmMessage: "Are you sure you want to delete this tag?",
13764
+ run: async (c3, a2) => {
13765
+ const tagResult = await c3.getTag(a2.id);
13766
+ if (tagResult?.error)
13767
+ return tagResult;
13768
+ const tag = tagResult?.data ?? tagResult;
13769
+ const deleteResult = await c3.deleteTag(a2.id);
13770
+ if (deleteResult?.error)
13771
+ return deleteResult;
13772
+ return { message: `Deleted tag: ${tag.label} (ID: ${tag.id})` };
13773
+ }
13774
+ },
12883
13775
  {
12884
13776
  name: "list",
12885
13777
  description: "List all tags",
@@ -12897,6 +13789,19 @@ var init_readarr3 = __esm(() => {
12897
13789
  description: "List root folders",
12898
13790
  columns: ["id", "path", "freeSpace"],
12899
13791
  run: (c3) => c3.getRootFolders()
13792
+ },
13793
+ {
13794
+ name: "add",
13795
+ description: "Add a root folder",
13796
+ args: [{ name: "path", description: "Folder path", required: true }],
13797
+ run: (c3, a2) => c3.addRootFolder(a2.path)
13798
+ },
13799
+ {
13800
+ name: "delete",
13801
+ description: "Delete a root folder",
13802
+ args: [{ name: "id", description: "Root folder ID", required: true, type: "number" }],
13803
+ confirmMessage: "Are you sure you want to delete this root folder?",
13804
+ run: (c3, a2) => c3.deleteRootFolder(a2.id)
12900
13805
  }
12901
13806
  ]
12902
13807
  },
@@ -14066,6 +14971,14 @@ var deleteApiV1ApplicationsById = (options) => (options.client ?? client5).delet
14066
14971
  }],
14067
14972
  url: "/api/v1/indexer/testall",
14068
14973
  ...options
14974
+ }), getApiV1Indexerstats = (options) => (options?.client ?? client5).get({
14975
+ security: [{ name: "X-Api-Key", type: "apiKey" }, {
14976
+ in: "query",
14977
+ name: "apikey",
14978
+ type: "apiKey"
14979
+ }],
14980
+ url: "/api/v1/indexerstats",
14981
+ ...options
14069
14982
  }), getApiV1Log3 = (options) => (options?.client ?? client5).get({
14070
14983
  security: [{ name: "X-Api-Key", type: "apiKey" }, {
14071
14984
  in: "query",
@@ -14336,6 +15249,9 @@ class ProwlarrClient {
14336
15249
  async deleteIndexer(id) {
14337
15250
  return deleteApiV1IndexerById3({ path: { id } });
14338
15251
  }
15252
+ async getIndexerStats() {
15253
+ return getApiV1Indexerstats();
15254
+ }
14339
15255
  async getDownloadClients() {
14340
15256
  return getApiV1Downloadclient3();
14341
15257
  }
@@ -14533,6 +15449,7 @@ __export(exports_prowlarr3, {
14533
15449
  var resources5, prowlarr;
14534
15450
  var init_prowlarr3 = __esm(() => {
14535
15451
  init_prowlarr2();
15452
+ init_prompt2();
14536
15453
  init_service();
14537
15454
  resources5 = [
14538
15455
  {
@@ -14557,6 +15474,11 @@ var init_prowlarr3 = __esm(() => {
14557
15474
  args: [{ name: "id", description: "Indexer ID", required: true, type: "number" }],
14558
15475
  confirmMessage: "Are you sure you want to delete this indexer?",
14559
15476
  run: (c3, a2) => c3.deleteIndexer(a2.id)
15477
+ },
15478
+ {
15479
+ name: "test",
15480
+ description: "Test all indexers",
15481
+ run: (c3) => c3.testAllIndexers()
14560
15482
  }
14561
15483
  ]
14562
15484
  },
@@ -14567,9 +15489,12 @@ var init_prowlarr3 = __esm(() => {
14567
15489
  {
14568
15490
  name: "run",
14569
15491
  description: "Search across indexers",
14570
- args: [{ name: "query", description: "Search query", required: true }],
15492
+ args: [
15493
+ { name: "term", description: "Search term" },
15494
+ { name: "query", description: "Search query" }
15495
+ ],
14571
15496
  columns: ["indexer", "title", "size", "seeders"],
14572
- run: (c3, a2) => c3.search(a2.query)
15497
+ run: async (c3, a2) => c3.search(await promptIfMissing(a2.term ?? a2.query, "Search term:"))
14573
15498
  }
14574
15499
  ]
14575
15500
  },
@@ -14588,6 +15513,18 @@ var init_prowlarr3 = __esm(() => {
14588
15513
  description: "Get an application by ID",
14589
15514
  args: [{ name: "id", description: "Application ID", required: true, type: "number" }],
14590
15515
  run: (c3, a2) => c3.getApplication(a2.id)
15516
+ },
15517
+ {
15518
+ name: "delete",
15519
+ description: "Delete an application",
15520
+ args: [{ name: "id", description: "Application ID", required: true, type: "number" }],
15521
+ confirmMessage: "Are you sure you want to delete this application?",
15522
+ run: (c3, a2) => c3.deleteApplication(a2.id)
15523
+ },
15524
+ {
15525
+ name: "sync",
15526
+ description: "Trigger app indexer sync",
15527
+ run: (c3) => c3.runCommand({ name: "AppIndexerMapSync" })
14591
15528
  }
14592
15529
  ]
14593
15530
  },
@@ -14595,6 +15532,28 @@ var init_prowlarr3 = __esm(() => {
14595
15532
  name: "tag",
14596
15533
  description: "Manage tags",
14597
15534
  actions: [
15535
+ {
15536
+ name: "create",
15537
+ description: "Create a tag",
15538
+ args: [{ name: "label", description: "Tag label", required: true }],
15539
+ run: (c3, a2) => c3.addTag({ label: a2.label })
15540
+ },
15541
+ {
15542
+ name: "delete",
15543
+ description: "Delete a tag",
15544
+ args: [{ name: "id", description: "Tag ID", required: true, type: "number" }],
15545
+ confirmMessage: "Are you sure you want to delete this tag?",
15546
+ run: async (c3, a2) => {
15547
+ const tagResult = await c3.getTag(a2.id);
15548
+ if (tagResult?.error)
15549
+ return tagResult;
15550
+ const tag = tagResult?.data ?? tagResult;
15551
+ const deleteResult = await c3.deleteTag(a2.id);
15552
+ if (deleteResult?.error)
15553
+ return deleteResult;
15554
+ return { message: `Deleted tag: ${tag.label} (ID: ${tag.id})` };
15555
+ }
15556
+ },
14598
15557
  {
14599
15558
  name: "list",
14600
15559
  description: "List all tags",
@@ -14603,6 +15562,77 @@ var init_prowlarr3 = __esm(() => {
14603
15562
  }
14604
15563
  ]
14605
15564
  },
15565
+ {
15566
+ name: "indexerstats",
15567
+ description: "View indexer statistics",
15568
+ actions: [
15569
+ {
15570
+ name: "list",
15571
+ description: "Get indexer performance statistics",
15572
+ run: (c3) => c3.getIndexerStats()
15573
+ }
15574
+ ]
15575
+ },
15576
+ {
15577
+ name: "notification",
15578
+ description: "Manage notifications",
15579
+ actions: [
15580
+ {
15581
+ name: "list",
15582
+ description: "List notification providers",
15583
+ columns: ["id", "name", "implementation"],
15584
+ run: (c3) => c3.getNotifications()
15585
+ },
15586
+ {
15587
+ name: "get",
15588
+ description: "Get a notification by ID",
15589
+ args: [{ name: "id", description: "Notification ID", required: true, type: "number" }],
15590
+ run: (c3, a2) => c3.getNotification(a2.id)
15591
+ },
15592
+ {
15593
+ name: "delete",
15594
+ description: "Delete a notification",
15595
+ args: [{ name: "id", description: "Notification ID", required: true, type: "number" }],
15596
+ confirmMessage: "Are you sure you want to delete this notification?",
15597
+ run: (c3, a2) => c3.deleteNotification(a2.id)
15598
+ },
15599
+ {
15600
+ name: "test",
15601
+ description: "Test all notifications",
15602
+ run: (c3) => c3.testAllNotifications()
15603
+ }
15604
+ ]
15605
+ },
15606
+ {
15607
+ name: "downloadclient",
15608
+ description: "Manage download clients",
15609
+ actions: [
15610
+ {
15611
+ name: "list",
15612
+ description: "List download clients",
15613
+ columns: ["id", "name", "implementation", "enable"],
15614
+ run: (c3) => c3.getDownloadClients()
15615
+ },
15616
+ {
15617
+ name: "get",
15618
+ description: "Get a download client by ID",
15619
+ args: [{ name: "id", description: "Download client ID", required: true, type: "number" }],
15620
+ run: (c3, a2) => c3.getDownloadClient(a2.id)
15621
+ },
15622
+ {
15623
+ name: "delete",
15624
+ description: "Delete a download client",
15625
+ args: [{ name: "id", description: "Download client ID", required: true, type: "number" }],
15626
+ confirmMessage: "Are you sure you want to delete this download client?",
15627
+ run: (c3, a2) => c3.deleteDownloadClient(a2.id)
15628
+ },
15629
+ {
15630
+ name: "test",
15631
+ description: "Test all download clients",
15632
+ run: (c3) => c3.testAllDownloadClients()
15633
+ }
15634
+ ]
15635
+ },
14606
15636
  {
14607
15637
  name: "system",
14608
15638
  description: "System information",