tsarr 2.4.1 → 2.4.2

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
@@ -8089,6 +8089,7 @@ class SonarrClient {
8089
8089
  query.end = endDate;
8090
8090
  if (unmonitored !== undefined)
8091
8091
  query.unmonitored = unmonitored;
8092
+ query.includeSeries = true;
8092
8093
  return getApiV3Calendar2(Object.keys(query).length > 0 ? { query } : {});
8093
8094
  }
8094
8095
  async getCalendarFeed(pastDays, futureDays, tags) {
@@ -9968,6 +9969,14 @@ var getApiV1Album = (options) => (options?.client ?? client3).get({
9968
9969
  }],
9969
9970
  url: "/api/v1/customformat/schema",
9970
9971
  ...options
9972
+ }), getApiV1WantedCutoff = (options) => (options?.client ?? client3).get({
9973
+ security: [{ name: "X-Api-Key", type: "apiKey" }, {
9974
+ in: "query",
9975
+ name: "apikey",
9976
+ type: "apiKey"
9977
+ }],
9978
+ url: "/api/v1/wanted/cutoff",
9979
+ ...options
9971
9980
  }), getApiV1Diskspace = (options) => (options?.client ?? client3).get({
9972
9981
  security: [{ name: "X-Api-Key", type: "apiKey" }, {
9973
9982
  in: "query",
@@ -10376,6 +10385,14 @@ var getApiV1Album = (options) => (options?.client ?? client3).get({
10376
10385
  }],
10377
10386
  url: "/api/v1/config/metadataprovider",
10378
10387
  ...options
10388
+ }), getApiV1WantedMissing = (options) => (options?.client ?? client3).get({
10389
+ security: [{ name: "X-Api-Key", type: "apiKey" }, {
10390
+ in: "query",
10391
+ name: "apikey",
10392
+ type: "apiKey"
10393
+ }],
10394
+ url: "/api/v1/wanted/missing",
10395
+ ...options
10379
10396
  }), getApiV1ConfigNamingById = (options) => (options.client ?? client3).get({
10380
10397
  security: [{ name: "X-Api-Key", type: "apiKey" }, {
10381
10398
  in: "query",
@@ -10844,6 +10861,7 @@ class LidarrClient {
10844
10861
  query.end = end;
10845
10862
  if (unmonitored !== undefined)
10846
10863
  query.unmonitored = unmonitored;
10864
+ query.includeArtist = true;
10847
10865
  return getApiV1Calendar(Object.keys(query).length > 0 ? { query } : {});
10848
10866
  }
10849
10867
  async getCalendarFeed(pastDays, futureDays, tags) {
@@ -11201,6 +11219,34 @@ class LidarrClient {
11201
11219
  async removeBlocklistItemsBulk(ids) {
11202
11220
  return deleteApiV1BlocklistBulk({ body: { ids } });
11203
11221
  }
11222
+ async getWantedMissing(page, pageSize, sortKey, sortDirection, monitored) {
11223
+ const query = { includeArtist: true };
11224
+ if (page !== undefined)
11225
+ query.page = page;
11226
+ if (pageSize !== undefined)
11227
+ query.pageSize = pageSize;
11228
+ if (sortKey)
11229
+ query.sortKey = sortKey;
11230
+ if (sortDirection)
11231
+ query.sortDirection = sortDirection;
11232
+ if (monitored !== undefined)
11233
+ query.monitored = monitored;
11234
+ return getApiV1WantedMissing(Object.keys(query).length > 0 ? { query } : {});
11235
+ }
11236
+ async getWantedCutoff(page, pageSize, sortKey, sortDirection, monitored) {
11237
+ const query = { includeArtist: true };
11238
+ if (page !== undefined)
11239
+ query.page = page;
11240
+ if (pageSize !== undefined)
11241
+ query.pageSize = pageSize;
11242
+ if (sortKey)
11243
+ query.sortKey = sortKey;
11244
+ if (sortDirection)
11245
+ query.sortDirection = sortDirection;
11246
+ if (monitored !== undefined)
11247
+ query.monitored = monitored;
11248
+ return getApiV1WantedCutoff(Object.keys(query).length > 0 ? { query } : {});
11249
+ }
11204
11250
  updateConfig(newConfig) {
11205
11251
  const updatedConfig = { ...this.clientConfig.config, ...newConfig };
11206
11252
  this.clientConfig = createServarrClient(updatedConfig);
@@ -11222,6 +11268,38 @@ var exports_lidarr3 = {};
11222
11268
  __export(exports_lidarr3, {
11223
11269
  lidarr: () => lidarr
11224
11270
  });
11271
+ import { readFileSync as readFileSync4 } from "node:fs";
11272
+ function unwrapData3(result) {
11273
+ return result?.data ?? result;
11274
+ }
11275
+ function formatAlbumListItem(album) {
11276
+ return {
11277
+ ...album,
11278
+ artistName: album?.artistName ?? album?.artist?.artistName ?? "—"
11279
+ };
11280
+ }
11281
+ function formatQueueListItem(item) {
11282
+ return {
11283
+ ...item,
11284
+ artistName: item?.artistName ?? item?.artist?.artistName ?? "—"
11285
+ };
11286
+ }
11287
+ function formatHistoryListItem(item) {
11288
+ return {
11289
+ ...item,
11290
+ artistName: item?.artistName ?? item?.artist?.artistName ?? "—"
11291
+ };
11292
+ }
11293
+ function formatBlocklistItem(item) {
11294
+ return {
11295
+ ...item,
11296
+ artistName: item?.artistName ?? item?.artist?.artistName ?? "—"
11297
+ };
11298
+ }
11299
+ function readJsonInput3(filePath) {
11300
+ const raw = filePath === "-" ? readFileSync4(0, "utf-8") : readFileSync4(filePath, "utf-8");
11301
+ return JSON.parse(raw);
11302
+ }
11225
11303
  var resources3, lidarr;
11226
11304
  var init_lidarr3 = __esm(() => {
11227
11305
  init_lidarr2();
@@ -11257,31 +11335,28 @@ var init_lidarr3 = __esm(() => {
11257
11335
  description: "Search and add an artist",
11258
11336
  args: [{ name: "term", description: "Search term", required: true }],
11259
11337
  run: async (c3, a2) => {
11260
- const searchResult = await c3.searchArtists(a2.term);
11261
- const results = searchResult?.data ?? searchResult;
11338
+ const results = unwrapData3(await c3.searchArtists(a2.term));
11262
11339
  if (!Array.isArray(results) || results.length === 0) {
11263
11340
  throw new Error("No artists found.");
11264
11341
  }
11265
- const artistId = await promptSelect("Select an artist:", results.map((ar) => ({
11266
- label: ar.artistName,
11267
- value: String(ar.foreignArtistId)
11342
+ const artistId = await promptSelect("Select an artist:", results.map((artist2) => ({
11343
+ label: artist2.artistName,
11344
+ value: String(artist2.foreignArtistId)
11268
11345
  })));
11269
- const artist = results.find((ar) => String(ar.foreignArtistId) === artistId);
11346
+ const artist = results.find((item) => String(item.foreignArtistId) === artistId);
11270
11347
  if (!artist) {
11271
11348
  throw new Error("Selected artist was not found in the search results.");
11272
11349
  }
11273
- const profilesResult = await c3.getQualityProfiles();
11274
- const profiles = profilesResult?.data ?? profilesResult;
11350
+ const profiles = unwrapData3(await c3.getQualityProfiles());
11275
11351
  if (!Array.isArray(profiles) || profiles.length === 0) {
11276
11352
  throw new Error("No quality profiles found. Configure one in Lidarr first.");
11277
11353
  }
11278
- const profileId = await promptSelect("Select quality profile:", profiles.map((p) => ({ label: p.name, value: String(p.id) })));
11279
- const foldersResult = await c3.getRootFolders();
11280
- const folders = foldersResult?.data ?? foldersResult;
11354
+ const profileId = await promptSelect("Select quality profile:", profiles.map((profile) => ({ label: profile.name, value: String(profile.id) })));
11355
+ const folders = unwrapData3(await c3.getRootFolders());
11281
11356
  if (!Array.isArray(folders) || folders.length === 0) {
11282
11357
  throw new Error("No root folders found. Configure one in Lidarr first.");
11283
11358
  }
11284
- const rootFolderPath = await promptSelect("Select root folder:", folders.map((f3) => ({ label: f3.path, value: f3.path })));
11359
+ const rootFolderPath = await promptSelect("Select root folder:", folders.map((folder) => ({ label: folder.path, value: folder.path })));
11285
11360
  const confirmed = await promptConfirm(`Add "${artist.artistName}"?`, !!a2.yes);
11286
11361
  if (!confirmed)
11287
11362
  throw new Error("Cancelled.");
@@ -11304,15 +11379,16 @@ var init_lidarr3 = __esm(() => {
11304
11379
  { name: "tags", description: "Comma-separated tag IDs" }
11305
11380
  ],
11306
11381
  run: async (c3, a2) => {
11307
- const result = await c3.getArtist(a2.id);
11308
- const artist = result?.data ?? result;
11382
+ const artist = unwrapData3(await c3.getArtist(a2.id));
11309
11383
  const updates = { ...artist };
11310
11384
  if (a2.monitored !== undefined)
11311
11385
  updates.monitored = a2.monitored === "true";
11312
- if (a2["quality-profile-id"] !== undefined)
11386
+ if (a2["quality-profile-id"] !== undefined) {
11313
11387
  updates.qualityProfileId = Number(a2["quality-profile-id"]);
11314
- if (a2.tags !== undefined)
11315
- updates.tags = a2.tags.split(",").map((t2) => Number(t2.trim()));
11388
+ }
11389
+ if (a2.tags !== undefined) {
11390
+ updates.tags = a2.tags.split(",").map((tag) => Number(tag.trim()));
11391
+ }
11316
11392
  return c3.updateArtist(a2.id, updates);
11317
11393
  }
11318
11394
  },
@@ -11344,22 +11420,56 @@ var init_lidarr3 = __esm(() => {
11344
11420
  {
11345
11421
  name: "list",
11346
11422
  description: "List all albums",
11347
- columns: ["id", "title", "artistId", "monitored"],
11348
- run: (c3) => c3.getAlbums()
11423
+ columns: ["id", "artistName", "title", "monitored"],
11424
+ run: async (c3) => {
11425
+ const albums = unwrapData3(await c3.getAlbums());
11426
+ return albums.map(formatAlbumListItem);
11427
+ }
11349
11428
  },
11350
11429
  {
11351
11430
  name: "get",
11352
11431
  description: "Get an album by ID",
11353
11432
  args: [{ name: "id", description: "Album ID", required: true, type: "number" }],
11354
- run: (c3, a2) => c3.getAlbum(a2.id)
11433
+ run: async (c3, a2) => {
11434
+ const album = unwrapData3(await c3.getAlbum(a2.id));
11435
+ return formatAlbumListItem(album);
11436
+ }
11355
11437
  },
11356
11438
  {
11357
11439
  name: "search",
11358
11440
  description: "Search for albums",
11359
11441
  args: [{ name: "term", description: "Search term", required: true }],
11360
- columns: ["foreignAlbumId", "title", "artistId"],
11442
+ columns: ["foreignAlbumId", "artistName", "title", "monitored"],
11361
11443
  idField: "foreignAlbumId",
11362
- run: (c3, a2) => c3.searchAlbums(a2.term)
11444
+ run: async (c3, a2) => {
11445
+ const albums = unwrapData3(await c3.searchAlbums(a2.term));
11446
+ return albums.map(formatAlbumListItem);
11447
+ }
11448
+ },
11449
+ {
11450
+ name: "add",
11451
+ description: "Add an album from JSON file or stdin",
11452
+ args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
11453
+ run: async (c3, a2) => c3.addAlbum(readJsonInput3(a2.file))
11454
+ },
11455
+ {
11456
+ name: "edit",
11457
+ description: "Edit an album (merges JSON with existing)",
11458
+ args: [
11459
+ { name: "id", description: "Album ID", required: true, type: "number" },
11460
+ { name: "file", description: "JSON file with fields to update", required: true }
11461
+ ],
11462
+ run: async (c3, a2) => {
11463
+ const existing = unwrapData3(await c3.getAlbum(a2.id));
11464
+ return c3.updateAlbum(a2.id, { ...existing, ...readJsonInput3(a2.file) });
11465
+ }
11466
+ },
11467
+ {
11468
+ name: "delete",
11469
+ description: "Delete an album",
11470
+ args: [{ name: "id", description: "Album ID", required: true, type: "number" }],
11471
+ confirmMessage: "Are you sure you want to delete this album?",
11472
+ run: (c3, a2) => c3.deleteAlbum(a2.id)
11363
11473
  }
11364
11474
  ]
11365
11475
  },
@@ -11372,6 +11482,12 @@ var init_lidarr3 = __esm(() => {
11372
11482
  description: "List quality profiles",
11373
11483
  columns: ["id", "name"],
11374
11484
  run: (c3) => c3.getQualityProfiles()
11485
+ },
11486
+ {
11487
+ name: "get",
11488
+ description: "Get a quality profile by ID",
11489
+ args: [{ name: "id", description: "Profile ID", required: true, type: "number" }],
11490
+ run: (c3, a2) => c3.getQualityProfile(a2.id)
11375
11491
  }
11376
11492
  ]
11377
11493
  },
@@ -11394,7 +11510,7 @@ var init_lidarr3 = __esm(() => {
11394
11510
  const tagResult = await c3.getTag(a2.id);
11395
11511
  if (tagResult?.error)
11396
11512
  return tagResult;
11397
- const tag = tagResult?.data ?? tagResult;
11513
+ const tag = unwrapData3(tagResult);
11398
11514
  const deleteResult = await c3.deleteTag(a2.id);
11399
11515
  if (deleteResult?.error)
11400
11516
  return deleteResult;
@@ -11410,50 +11526,298 @@ var init_lidarr3 = __esm(() => {
11410
11526
  ]
11411
11527
  },
11412
11528
  {
11413
- name: "rootfolder",
11414
- description: "Manage root folders",
11529
+ name: "queue",
11530
+ description: "Manage download queue",
11415
11531
  actions: [
11416
11532
  {
11417
11533
  name: "list",
11418
- description: "List root folders",
11419
- columns: ["id", "path", "freeSpace"],
11420
- run: (c3) => c3.getRootFolders()
11534
+ description: "List queue items",
11535
+ columns: ["id", "artistName", "title", "status", "sizeleft", "timeleft"],
11536
+ run: async (c3) => {
11537
+ const items = unwrapData3(await c3.getQueue());
11538
+ return items.map(formatQueueListItem);
11539
+ }
11540
+ },
11541
+ {
11542
+ name: "status",
11543
+ description: "Get queue status",
11544
+ run: (c3) => c3.getQueueStatus()
11545
+ },
11546
+ {
11547
+ name: "delete",
11548
+ description: "Remove an item from the queue",
11549
+ args: [
11550
+ { name: "id", description: "Queue item ID", required: true, type: "number" },
11551
+ { name: "blocklist", description: "Add to blocklist", type: "boolean" },
11552
+ {
11553
+ name: "remove-from-client",
11554
+ description: "Remove from download client",
11555
+ type: "boolean"
11556
+ }
11557
+ ],
11558
+ confirmMessage: "Are you sure you want to remove this queue item?",
11559
+ run: (c3, a2) => c3.removeQueueItem(a2.id, a2["remove-from-client"], a2.blocklist)
11560
+ },
11561
+ {
11562
+ name: "grab",
11563
+ description: "Force download a queue item",
11564
+ args: [{ name: "id", description: "Queue item ID", required: true, type: "number" }],
11565
+ run: (c3, a2) => c3.grabQueueItem(a2.id)
11566
+ }
11567
+ ]
11568
+ },
11569
+ {
11570
+ name: "history",
11571
+ description: "View history",
11572
+ actions: [
11573
+ {
11574
+ name: "list",
11575
+ description: "List recent history",
11576
+ args: [
11577
+ { name: "since", description: "Start date (ISO 8601, e.g. 2024-01-01)" },
11578
+ { name: "until", description: "End date (ISO 8601, e.g. 2024-12-31)" }
11579
+ ],
11580
+ columns: ["id", "artistName", "sourceTitle", "eventType", "date"],
11581
+ run: async (c3, a2) => {
11582
+ const items = a2.since ? unwrapData3(await c3.getHistorySince(a2.since)) : unwrapData3(await c3.getHistory());
11583
+ const filtered = a2.until ? items.filter((item) => new Date(item.date) <= new Date(a2.until)) : items;
11584
+ return filtered.map(formatHistoryListItem);
11585
+ }
11586
+ }
11587
+ ]
11588
+ },
11589
+ {
11590
+ name: "calendar",
11591
+ description: "View upcoming releases",
11592
+ actions: [
11593
+ {
11594
+ name: "list",
11595
+ description: "List upcoming album releases",
11596
+ args: [
11597
+ { name: "start", description: "Start date (ISO 8601)" },
11598
+ { name: "end", description: "End date (ISO 8601)" },
11599
+ { name: "unmonitored", description: "Include unmonitored", type: "boolean" }
11600
+ ],
11601
+ columns: ["id", "artistName", "title", "releaseDate"],
11602
+ run: async (c3, a2) => {
11603
+ const albums = unwrapData3(await c3.getCalendar(a2.start, a2.end, a2.unmonitored));
11604
+ return albums.map(formatAlbumListItem);
11605
+ }
11606
+ }
11607
+ ]
11608
+ },
11609
+ {
11610
+ name: "notification",
11611
+ description: "Manage notifications",
11612
+ actions: [
11613
+ {
11614
+ name: "list",
11615
+ description: "List notification providers",
11616
+ columns: ["id", "name", "implementation"],
11617
+ run: (c3) => c3.getNotifications()
11618
+ },
11619
+ {
11620
+ name: "get",
11621
+ description: "Get a notification by ID",
11622
+ args: [{ name: "id", description: "Notification ID", required: true, type: "number" }],
11623
+ run: (c3, a2) => c3.getNotification(a2.id)
11421
11624
  },
11422
11625
  {
11423
11626
  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)
11627
+ description: "Add a notification from JSON file or stdin",
11628
+ args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
11629
+ run: async (c3, a2) => c3.addNotification(readJsonInput3(a2.file))
11630
+ },
11631
+ {
11632
+ name: "edit",
11633
+ description: "Edit a notification (merges JSON with existing)",
11634
+ args: [
11635
+ { name: "id", description: "Notification ID", required: true, type: "number" },
11636
+ { name: "file", description: "JSON file with fields to update", required: true }
11637
+ ],
11638
+ run: async (c3, a2) => {
11639
+ const existing = unwrapData3(await c3.getNotification(a2.id));
11640
+ return c3.updateNotification(a2.id, { ...existing, ...readJsonInput3(a2.file) });
11641
+ }
11427
11642
  },
11428
11643
  {
11429
11644
  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)
11645
+ description: "Delete a notification",
11646
+ args: [{ name: "id", description: "Notification ID", required: true, type: "number" }],
11647
+ confirmMessage: "Are you sure you want to delete this notification?",
11648
+ run: (c3, a2) => c3.deleteNotification(a2.id)
11649
+ },
11650
+ {
11651
+ name: "test",
11652
+ description: "Test all notifications",
11653
+ run: (c3) => c3.testAllNotifications()
11434
11654
  }
11435
11655
  ]
11436
11656
  },
11437
11657
  {
11438
- name: "system",
11439
- description: "System information",
11658
+ name: "downloadclient",
11659
+ description: "Manage download clients",
11440
11660
  actions: [
11441
11661
  {
11442
- name: "status",
11443
- description: "Get system status",
11444
- run: (c3) => c3.getSystemStatus()
11662
+ name: "list",
11663
+ description: "List download clients",
11664
+ columns: ["id", "name", "implementation", "enable"],
11665
+ run: (c3) => c3.getDownloadClients()
11445
11666
  },
11446
11667
  {
11447
- name: "health",
11448
- description: "Get health check results",
11449
- columns: ["type", "message", "source"],
11450
- run: (c3) => c3.getHealth()
11668
+ name: "get",
11669
+ description: "Get a download client by ID",
11670
+ args: [{ name: "id", description: "Download client ID", required: true, type: "number" }],
11671
+ run: (c3, a2) => c3.getDownloadClient(a2.id)
11672
+ },
11673
+ {
11674
+ name: "add",
11675
+ description: "Add a download client from JSON file or stdin",
11676
+ args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
11677
+ run: async (c3, a2) => c3.addDownloadClient(readJsonInput3(a2.file))
11678
+ },
11679
+ {
11680
+ name: "edit",
11681
+ description: "Edit a download client (merges JSON with existing)",
11682
+ args: [
11683
+ { name: "id", description: "Download client ID", required: true, type: "number" },
11684
+ { name: "file", description: "JSON file with fields to update", required: true }
11685
+ ],
11686
+ run: async (c3, a2) => {
11687
+ const existing = unwrapData3(await c3.getDownloadClient(a2.id));
11688
+ return c3.updateDownloadClient(a2.id, { ...existing, ...readJsonInput3(a2.file) });
11689
+ }
11690
+ },
11691
+ {
11692
+ name: "delete",
11693
+ description: "Delete a download client",
11694
+ args: [{ name: "id", description: "Download client ID", required: true, type: "number" }],
11695
+ confirmMessage: "Are you sure you want to delete this download client?",
11696
+ run: (c3, a2) => c3.deleteDownloadClient(a2.id)
11697
+ },
11698
+ {
11699
+ name: "test",
11700
+ description: "Test all download clients",
11701
+ run: (c3) => c3.testAllDownloadClients()
11451
11702
  }
11452
11703
  ]
11453
- }
11454
- ];
11455
- lidarr = buildServiceCommand("lidarr", "Manage Lidarr (Music)", (config) => new LidarrClient(config), resources3);
11456
- });
11704
+ },
11705
+ {
11706
+ name: "blocklist",
11707
+ description: "View blocked releases",
11708
+ actions: [
11709
+ {
11710
+ name: "list",
11711
+ description: "List blocked releases",
11712
+ columns: ["id", "artistName", "sourceTitle", "date"],
11713
+ run: async (c3) => {
11714
+ const items = unwrapData3(await c3.getBlocklist());
11715
+ return items.map(formatBlocklistItem);
11716
+ }
11717
+ },
11718
+ {
11719
+ name: "delete",
11720
+ description: "Remove a release from the blocklist",
11721
+ args: [{ name: "id", description: "Blocklist item ID", required: true, type: "number" }],
11722
+ confirmMessage: "Are you sure you want to remove this blocklist entry?",
11723
+ run: (c3, a2) => c3.removeBlocklistItem(a2.id)
11724
+ }
11725
+ ]
11726
+ },
11727
+ {
11728
+ name: "wanted",
11729
+ description: "View missing and cutoff unmet albums",
11730
+ actions: [
11731
+ {
11732
+ name: "missing",
11733
+ description: "List albums with missing tracks",
11734
+ columns: ["id", "artistName", "title", "releaseDate"],
11735
+ run: async (c3) => {
11736
+ const albums = unwrapData3(await c3.getWantedMissing());
11737
+ return albums.map(formatAlbumListItem);
11738
+ }
11739
+ },
11740
+ {
11741
+ name: "cutoff",
11742
+ description: "List albums below quality cutoff",
11743
+ columns: ["id", "artistName", "title", "releaseDate"],
11744
+ run: async (c3) => {
11745
+ const albums = unwrapData3(await c3.getWantedCutoff());
11746
+ return albums.map(formatAlbumListItem);
11747
+ }
11748
+ }
11749
+ ]
11750
+ },
11751
+ {
11752
+ name: "importlist",
11753
+ description: "Manage import lists",
11754
+ actions: [
11755
+ {
11756
+ name: "list",
11757
+ description: "List import lists",
11758
+ columns: ["id", "name", "implementation", "enable"],
11759
+ run: (c3) => c3.getImportLists()
11760
+ },
11761
+ {
11762
+ name: "get",
11763
+ description: "Get an import list by ID",
11764
+ args: [{ name: "id", description: "Import list ID", required: true, type: "number" }],
11765
+ run: (c3, a2) => c3.getImportList(a2.id)
11766
+ },
11767
+ {
11768
+ name: "delete",
11769
+ description: "Delete an import list",
11770
+ args: [{ name: "id", description: "Import list ID", required: true, type: "number" }],
11771
+ confirmMessage: "Are you sure you want to delete this import list?",
11772
+ run: (c3, a2) => c3.deleteImportList(a2.id)
11773
+ }
11774
+ ]
11775
+ },
11776
+ {
11777
+ name: "rootfolder",
11778
+ description: "Manage root folders",
11779
+ actions: [
11780
+ {
11781
+ name: "list",
11782
+ description: "List root folders",
11783
+ columns: ["id", "path", "freeSpace"],
11784
+ run: (c3) => c3.getRootFolders()
11785
+ },
11786
+ {
11787
+ name: "add",
11788
+ description: "Add a root folder",
11789
+ args: [{ name: "path", description: "Folder path", required: true }],
11790
+ run: (c3, a2) => c3.addRootFolder(a2.path)
11791
+ },
11792
+ {
11793
+ name: "delete",
11794
+ description: "Delete a root folder",
11795
+ args: [{ name: "id", description: "Root folder ID", required: true, type: "number" }],
11796
+ confirmMessage: "Are you sure you want to delete this root folder?",
11797
+ run: (c3, a2) => c3.deleteRootFolder(a2.id)
11798
+ }
11799
+ ]
11800
+ },
11801
+ {
11802
+ name: "system",
11803
+ description: "System information",
11804
+ actions: [
11805
+ {
11806
+ name: "status",
11807
+ description: "Get system status",
11808
+ run: (c3) => c3.getSystemStatus()
11809
+ },
11810
+ {
11811
+ name: "health",
11812
+ description: "Get health check results",
11813
+ columns: ["type", "message", "source"],
11814
+ run: (c3) => c3.getHealth()
11815
+ }
11816
+ ]
11817
+ }
11818
+ ];
11819
+ lidarr = buildServiceCommand("lidarr", "Manage Lidarr (Music)", (config) => new LidarrClient(config), resources3);
11820
+ });
11457
11821
 
11458
11822
  // src/generated/readarr/core/bodySerializer.gen.ts
11459
11823
  var jsonBodySerializer4;
@@ -12428,6 +12792,22 @@ var getApiV1Author = (options) => (options?.client ?? client4).get({
12428
12792
  }],
12429
12793
  url: "/api/v1/book/lookup",
12430
12794
  ...options
12795
+ }), getApiV1Calendar2 = (options) => (options?.client ?? client4).get({
12796
+ security: [{ name: "X-Api-Key", type: "apiKey" }, {
12797
+ in: "query",
12798
+ name: "apikey",
12799
+ type: "apiKey"
12800
+ }],
12801
+ url: "/api/v1/calendar",
12802
+ ...options
12803
+ }), getFeedV1CalendarReadarrIcs = (options) => (options?.client ?? client4).get({
12804
+ security: [{ name: "X-Api-Key", type: "apiKey" }, {
12805
+ in: "query",
12806
+ name: "apikey",
12807
+ type: "apiKey"
12808
+ }],
12809
+ url: "/feed/v1/calendar/readarr.ics",
12810
+ ...options
12431
12811
  }), getApiV1Command2 = (options) => (options?.client ?? client4).get({
12432
12812
  security: [{ name: "X-Api-Key", type: "apiKey" }, {
12433
12813
  in: "query",
@@ -12504,6 +12884,14 @@ var getApiV1Author = (options) => (options?.client ?? client4).get({
12504
12884
  }],
12505
12885
  url: "/api/v1/customformat/schema",
12506
12886
  ...options
12887
+ }), getApiV1WantedCutoff2 = (options) => (options?.client ?? client4).get({
12888
+ security: [{ name: "X-Api-Key", type: "apiKey" }, {
12889
+ in: "query",
12890
+ name: "apikey",
12891
+ type: "apiKey"
12892
+ }],
12893
+ url: "/api/v1/wanted/cutoff",
12894
+ ...options
12507
12895
  }), getApiV1ConfigDevelopment = (options) => (options?.client ?? client4).get({
12508
12896
  security: [{ name: "X-Api-Key", type: "apiKey" }, {
12509
12897
  in: "query",
@@ -12916,6 +13304,14 @@ var getApiV1Author = (options) => (options?.client ?? client4).get({
12916
13304
  "Content-Type": "application/json",
12917
13305
  ...options.headers
12918
13306
  }
13307
+ }), getApiV1WantedMissing2 = (options) => (options?.client ?? client4).get({
13308
+ security: [{ name: "X-Api-Key", type: "apiKey" }, {
13309
+ in: "query",
13310
+ name: "apikey",
13311
+ type: "apiKey"
13312
+ }],
13313
+ url: "/api/v1/wanted/missing",
13314
+ ...options
12919
13315
  }), getApiV1ConfigNaming2 = (options) => (options?.client ?? client4).get({
12920
13316
  security: [{ name: "X-Api-Key", type: "apiKey" }, {
12921
13317
  in: "query",
@@ -13487,6 +13883,26 @@ class ReadarrClient {
13487
13883
  async searchBooks(term) {
13488
13884
  return getApiV1BookLookup({ query: { term } });
13489
13885
  }
13886
+ async getCalendar(start, end, unmonitored) {
13887
+ const query = { includeAuthor: true };
13888
+ if (start)
13889
+ query.start = start;
13890
+ if (end)
13891
+ query.end = end;
13892
+ if (unmonitored !== undefined)
13893
+ query.unmonitored = unmonitored;
13894
+ return getApiV1Calendar2(Object.keys(query).length > 0 ? { query } : {});
13895
+ }
13896
+ async getCalendarFeed(pastDays, futureDays, tagList) {
13897
+ const query = {};
13898
+ if (pastDays !== undefined)
13899
+ query.pastDays = pastDays;
13900
+ if (futureDays !== undefined)
13901
+ query.futureDays = futureDays;
13902
+ if (tagList)
13903
+ query.tagList = tagList;
13904
+ return getFeedV1CalendarReadarrIcs(Object.keys(query).length > 0 ? { query } : {});
13905
+ }
13490
13906
  async getQualityProfiles() {
13491
13907
  return getApiV1Qualityprofile2();
13492
13908
  }
@@ -13723,6 +14139,34 @@ class ReadarrClient {
13723
14139
  async removeBlocklistItemsBulk(ids) {
13724
14140
  return deleteApiV1BlocklistBulk2({ body: { ids } });
13725
14141
  }
14142
+ async getWantedMissing(page, pageSize, sortKey, sortDirection, monitored) {
14143
+ const query = { includeAuthor: true };
14144
+ if (page !== undefined)
14145
+ query.page = page;
14146
+ if (pageSize !== undefined)
14147
+ query.pageSize = pageSize;
14148
+ if (sortKey)
14149
+ query.sortKey = sortKey;
14150
+ if (sortDirection)
14151
+ query.sortDirection = sortDirection;
14152
+ if (monitored !== undefined)
14153
+ query.monitored = monitored;
14154
+ return getApiV1WantedMissing2(Object.keys(query).length > 0 ? { query } : {});
14155
+ }
14156
+ async getWantedCutoff(page, pageSize, sortKey, sortDirection, monitored) {
14157
+ const query = { includeAuthor: true };
14158
+ if (page !== undefined)
14159
+ query.page = page;
14160
+ if (pageSize !== undefined)
14161
+ query.pageSize = pageSize;
14162
+ if (sortKey)
14163
+ query.sortKey = sortKey;
14164
+ if (sortDirection)
14165
+ query.sortDirection = sortDirection;
14166
+ if (monitored !== undefined)
14167
+ query.monitored = monitored;
14168
+ return getApiV1WantedCutoff2(Object.keys(query).length > 0 ? { query } : {});
14169
+ }
13726
14170
  updateConfig(newConfig) {
13727
14171
  const updatedConfig = { ...this.clientConfig.config, ...newConfig };
13728
14172
  this.clientConfig = createServarrClient(updatedConfig);
@@ -13744,6 +14188,38 @@ var exports_readarr3 = {};
13744
14188
  __export(exports_readarr3, {
13745
14189
  readarr: () => readarr
13746
14190
  });
14191
+ import { readFileSync as readFileSync5 } from "node:fs";
14192
+ function unwrapData4(result) {
14193
+ return result?.data ?? result;
14194
+ }
14195
+ function formatBookListItem(book) {
14196
+ return {
14197
+ ...book,
14198
+ authorName: book?.authorName ?? book?.authorTitle ?? book?.author?.authorName ?? "—"
14199
+ };
14200
+ }
14201
+ function formatQueueListItem2(item) {
14202
+ return {
14203
+ ...item,
14204
+ authorName: item?.authorName ?? item?.authorTitle ?? item?.author?.authorName ?? "—"
14205
+ };
14206
+ }
14207
+ function formatHistoryListItem2(item) {
14208
+ return {
14209
+ ...item,
14210
+ authorName: item?.authorName ?? item?.authorTitle ?? item?.author?.authorName ?? "—"
14211
+ };
14212
+ }
14213
+ function formatBlocklistItem2(item) {
14214
+ return {
14215
+ ...item,
14216
+ authorName: item?.authorName ?? item?.authorTitle ?? item?.author?.authorName ?? "—"
14217
+ };
14218
+ }
14219
+ function readJsonInput4(filePath) {
14220
+ const raw = filePath === "-" ? readFileSync5(0, "utf-8") : readFileSync5(filePath, "utf-8");
14221
+ return JSON.parse(raw);
14222
+ }
13747
14223
  var resources4, readarr;
13748
14224
  var init_readarr3 = __esm(() => {
13749
14225
  init_readarr2();
@@ -13751,178 +14227,469 @@ var init_readarr3 = __esm(() => {
13751
14227
  init_service();
13752
14228
  resources4 = [
13753
14229
  {
13754
- name: "author",
13755
- description: "Manage authors",
14230
+ name: "author",
14231
+ description: "Manage authors",
14232
+ actions: [
14233
+ {
14234
+ name: "list",
14235
+ description: "List all authors",
14236
+ columns: ["id", "authorName", "monitored", "qualityProfileId"],
14237
+ run: (c3) => c3.getAuthors()
14238
+ },
14239
+ {
14240
+ name: "get",
14241
+ description: "Get an author by ID",
14242
+ args: [{ name: "id", description: "Author ID", required: true, type: "number" }],
14243
+ run: (c3, a2) => c3.getAuthor(a2.id)
14244
+ },
14245
+ {
14246
+ name: "search",
14247
+ description: "Search for authors",
14248
+ args: [{ name: "term", description: "Search term", required: true }],
14249
+ columns: ["foreignAuthorId", "authorName", "overview"],
14250
+ idField: "foreignAuthorId",
14251
+ run: (c3, a2) => c3.searchAuthors(a2.term)
14252
+ },
14253
+ {
14254
+ name: "add",
14255
+ description: "Search and add an author",
14256
+ args: [{ name: "term", description: "Search term", required: true }],
14257
+ run: async (c3, a2) => {
14258
+ const results = unwrapData4(await c3.searchAuthors(a2.term));
14259
+ if (!Array.isArray(results) || results.length === 0) {
14260
+ throw new Error("No authors found.");
14261
+ }
14262
+ const authorId = await promptSelect("Select an author:", results.map((author2) => ({
14263
+ label: author2.authorName,
14264
+ value: String(author2.foreignAuthorId)
14265
+ })));
14266
+ const author = results.find((item) => String(item.foreignAuthorId) === authorId);
14267
+ if (!author) {
14268
+ throw new Error("Selected author was not found in the search results.");
14269
+ }
14270
+ const profiles = unwrapData4(await c3.getQualityProfiles());
14271
+ if (!Array.isArray(profiles) || profiles.length === 0) {
14272
+ throw new Error("No quality profiles found. Configure one in Readarr first.");
14273
+ }
14274
+ const profileId = await promptSelect("Select quality profile:", profiles.map((profile) => ({ label: profile.name, value: String(profile.id) })));
14275
+ const folders = unwrapData4(await c3.getRootFolders());
14276
+ if (!Array.isArray(folders) || folders.length === 0) {
14277
+ throw new Error("No root folders found. Configure one in Readarr first.");
14278
+ }
14279
+ const rootFolderPath = await promptSelect("Select root folder:", folders.map((folder) => ({ label: folder.path, value: folder.path })));
14280
+ const confirmed = await promptConfirm(`Add "${author.authorName}"?`, !!a2.yes);
14281
+ if (!confirmed)
14282
+ throw new Error("Cancelled.");
14283
+ return c3.addAuthor({
14284
+ ...author,
14285
+ qualityProfileId: Number(profileId),
14286
+ rootFolderPath,
14287
+ monitored: true,
14288
+ addOptions: { searchForMissingBooks: true }
14289
+ });
14290
+ }
14291
+ },
14292
+ {
14293
+ name: "edit",
14294
+ description: "Edit an author",
14295
+ args: [
14296
+ { name: "id", description: "Author ID", required: true, type: "number" },
14297
+ { name: "monitored", description: "Set monitored (true/false)" },
14298
+ { name: "quality-profile-id", description: "Quality profile ID", type: "number" },
14299
+ { name: "tags", description: "Comma-separated tag IDs" }
14300
+ ],
14301
+ run: async (c3, a2) => {
14302
+ const author = unwrapData4(await c3.getAuthor(a2.id));
14303
+ const updates = { ...author };
14304
+ if (a2.monitored !== undefined)
14305
+ updates.monitored = a2.monitored === "true";
14306
+ if (a2["quality-profile-id"] !== undefined) {
14307
+ updates.qualityProfileId = Number(a2["quality-profile-id"]);
14308
+ }
14309
+ if (a2.tags !== undefined) {
14310
+ updates.tags = a2.tags.split(",").map((tag) => Number(tag.trim()));
14311
+ }
14312
+ return c3.updateAuthor(a2.id, updates);
14313
+ }
14314
+ },
14315
+ {
14316
+ name: "refresh",
14317
+ description: "Refresh author metadata",
14318
+ args: [{ name: "id", description: "Author ID", required: true, type: "number" }],
14319
+ run: (c3, a2) => c3.runCommand({ name: "RefreshAuthor", authorId: a2.id })
14320
+ },
14321
+ {
14322
+ name: "manual-search",
14323
+ description: "Trigger a manual search for releases",
14324
+ args: [{ name: "id", description: "Author ID", required: true, type: "number" }],
14325
+ run: (c3, a2) => c3.runCommand({ name: "AuthorSearch", authorId: a2.id })
14326
+ },
14327
+ {
14328
+ name: "delete",
14329
+ description: "Delete an author",
14330
+ args: [{ name: "id", description: "Author ID", required: true, type: "number" }],
14331
+ confirmMessage: "Are you sure you want to delete this author?",
14332
+ run: (c3, a2) => c3.deleteAuthor(a2.id)
14333
+ }
14334
+ ]
14335
+ },
14336
+ {
14337
+ name: "book",
14338
+ description: "Manage books",
14339
+ actions: [
14340
+ {
14341
+ name: "list",
14342
+ description: "List all books",
14343
+ columns: ["id", "authorName", "title", "monitored"],
14344
+ run: async (c3) => {
14345
+ const books = unwrapData4(await c3.getBooks());
14346
+ return books.map(formatBookListItem);
14347
+ }
14348
+ },
14349
+ {
14350
+ name: "get",
14351
+ description: "Get a book by ID",
14352
+ args: [{ name: "id", description: "Book ID", required: true, type: "number" }],
14353
+ run: async (c3, a2) => {
14354
+ const book = unwrapData4(await c3.getBook(a2.id));
14355
+ return formatBookListItem(book);
14356
+ }
14357
+ },
14358
+ {
14359
+ name: "search",
14360
+ description: "Search for books",
14361
+ args: [{ name: "term", description: "Search term", required: true }],
14362
+ columns: ["foreignBookId", "authorName", "title", "monitored"],
14363
+ idField: "foreignBookId",
14364
+ run: async (c3, a2) => {
14365
+ const books = unwrapData4(await c3.searchBooks(a2.term));
14366
+ return books.map(formatBookListItem);
14367
+ }
14368
+ },
14369
+ {
14370
+ name: "add",
14371
+ description: "Add a book from JSON file or stdin",
14372
+ args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
14373
+ run: async (c3, a2) => c3.addBook(readJsonInput4(a2.file))
14374
+ },
14375
+ {
14376
+ name: "edit",
14377
+ description: "Edit a book (merges JSON with existing)",
14378
+ args: [
14379
+ { name: "id", description: "Book ID", required: true, type: "number" },
14380
+ { name: "file", description: "JSON file with fields to update", required: true }
14381
+ ],
14382
+ run: async (c3, a2) => {
14383
+ const existing = unwrapData4(await c3.getBook(a2.id));
14384
+ return c3.updateBook(a2.id, { ...existing, ...readJsonInput4(a2.file) });
14385
+ }
14386
+ },
14387
+ {
14388
+ name: "delete",
14389
+ description: "Delete a book",
14390
+ args: [{ name: "id", description: "Book ID", required: true, type: "number" }],
14391
+ confirmMessage: "Are you sure you want to delete this book?",
14392
+ run: (c3, a2) => c3.deleteBook(a2.id)
14393
+ }
14394
+ ]
14395
+ },
14396
+ {
14397
+ name: "profile",
14398
+ description: "Manage quality profiles",
14399
+ actions: [
14400
+ {
14401
+ name: "list",
14402
+ description: "List quality profiles",
14403
+ columns: ["id", "name"],
14404
+ run: (c3) => c3.getQualityProfiles()
14405
+ },
14406
+ {
14407
+ name: "get",
14408
+ description: "Get a quality profile by ID",
14409
+ args: [{ name: "id", description: "Profile ID", required: true, type: "number" }],
14410
+ run: (c3, a2) => c3.getQualityProfile(a2.id)
14411
+ }
14412
+ ]
14413
+ },
14414
+ {
14415
+ name: "tag",
14416
+ description: "Manage tags",
14417
+ actions: [
14418
+ {
14419
+ name: "create",
14420
+ description: "Create a tag",
14421
+ args: [{ name: "label", description: "Tag label", required: true }],
14422
+ run: (c3, a2) => c3.addTag({ label: a2.label })
14423
+ },
14424
+ {
14425
+ name: "delete",
14426
+ description: "Delete a tag",
14427
+ args: [{ name: "id", description: "Tag ID", required: true, type: "number" }],
14428
+ confirmMessage: "Are you sure you want to delete this tag?",
14429
+ run: async (c3, a2) => {
14430
+ const tagResult = await c3.getTag(a2.id);
14431
+ if (tagResult?.error)
14432
+ return tagResult;
14433
+ const tag = unwrapData4(tagResult);
14434
+ const deleteResult = await c3.deleteTag(a2.id);
14435
+ if (deleteResult?.error)
14436
+ return deleteResult;
14437
+ return { message: `Deleted tag: ${tag.label} (ID: ${tag.id})` };
14438
+ }
14439
+ },
14440
+ {
14441
+ name: "list",
14442
+ description: "List all tags",
14443
+ columns: ["id", "label"],
14444
+ run: (c3) => c3.getTags()
14445
+ }
14446
+ ]
14447
+ },
14448
+ {
14449
+ name: "queue",
14450
+ description: "Manage download queue",
14451
+ actions: [
14452
+ {
14453
+ name: "list",
14454
+ description: "List queue items",
14455
+ columns: ["id", "authorName", "title", "status", "sizeleft", "timeleft"],
14456
+ run: async (c3) => {
14457
+ const items = unwrapData4(await c3.getQueue());
14458
+ return items.map(formatQueueListItem2);
14459
+ }
14460
+ },
14461
+ {
14462
+ name: "status",
14463
+ description: "Get queue status",
14464
+ run: (c3) => c3.getQueueStatus()
14465
+ },
14466
+ {
14467
+ name: "delete",
14468
+ description: "Remove an item from the queue",
14469
+ args: [
14470
+ { name: "id", description: "Queue item ID", required: true, type: "number" },
14471
+ { name: "blocklist", description: "Add to blocklist", type: "boolean" },
14472
+ {
14473
+ name: "remove-from-client",
14474
+ description: "Remove from download client",
14475
+ type: "boolean"
14476
+ }
14477
+ ],
14478
+ confirmMessage: "Are you sure you want to remove this queue item?",
14479
+ run: (c3, a2) => c3.removeQueueItem(a2.id, a2["remove-from-client"], a2.blocklist)
14480
+ },
14481
+ {
14482
+ name: "grab",
14483
+ description: "Force download a queue item",
14484
+ args: [{ name: "id", description: "Queue item ID", required: true, type: "number" }],
14485
+ run: (c3, a2) => c3.grabQueueItem(a2.id)
14486
+ }
14487
+ ]
14488
+ },
14489
+ {
14490
+ name: "history",
14491
+ description: "View history",
14492
+ actions: [
14493
+ {
14494
+ name: "list",
14495
+ description: "List recent history",
14496
+ args: [
14497
+ { name: "since", description: "Start date (ISO 8601, e.g. 2024-01-01)" },
14498
+ { name: "until", description: "End date (ISO 8601, e.g. 2024-12-31)" }
14499
+ ],
14500
+ columns: ["id", "authorName", "sourceTitle", "eventType", "date"],
14501
+ run: async (c3, a2) => {
14502
+ const items = a2.since ? unwrapData4(await c3.getHistorySince(a2.since)) : unwrapData4(await c3.getHistory());
14503
+ const filtered = a2.until ? items.filter((item) => new Date(item.date) <= new Date(a2.until)) : items;
14504
+ return filtered.map(formatHistoryListItem2);
14505
+ }
14506
+ }
14507
+ ]
14508
+ },
14509
+ {
14510
+ name: "calendar",
14511
+ description: "View upcoming releases",
13756
14512
  actions: [
13757
14513
  {
13758
14514
  name: "list",
13759
- description: "List all authors",
13760
- columns: ["id", "authorName", "monitored", "qualityProfileId"],
13761
- run: (c3) => c3.getAuthors()
13762
- },
14515
+ description: "List upcoming book releases",
14516
+ args: [
14517
+ { name: "start", description: "Start date (ISO 8601)" },
14518
+ { name: "end", description: "End date (ISO 8601)" },
14519
+ { name: "unmonitored", description: "Include unmonitored", type: "boolean" }
14520
+ ],
14521
+ columns: ["id", "authorName", "title", "releaseDate"],
14522
+ run: async (c3, a2) => {
14523
+ const books = unwrapData4(await c3.getCalendar(a2.start, a2.end, a2.unmonitored));
14524
+ return books.map(formatBookListItem);
14525
+ }
14526
+ }
14527
+ ]
14528
+ },
14529
+ {
14530
+ name: "notification",
14531
+ description: "Manage notifications",
14532
+ actions: [
13763
14533
  {
13764
- name: "get",
13765
- description: "Get an author by ID",
13766
- args: [{ name: "id", description: "Author ID", required: true, type: "number" }],
13767
- run: (c3, a2) => c3.getAuthor(a2.id)
14534
+ name: "list",
14535
+ description: "List notification providers",
14536
+ columns: ["id", "name", "implementation"],
14537
+ run: (c3) => c3.getNotifications()
13768
14538
  },
13769
14539
  {
13770
- name: "search",
13771
- description: "Search for authors",
13772
- args: [{ name: "term", description: "Search term", required: true }],
13773
- columns: ["foreignAuthorId", "authorName", "overview"],
13774
- run: (c3, a2) => c3.searchAuthors(a2.term)
14540
+ name: "get",
14541
+ description: "Get a notification by ID",
14542
+ args: [{ name: "id", description: "Notification ID", required: true, type: "number" }],
14543
+ run: (c3, a2) => c3.getNotification(a2.id)
13775
14544
  },
13776
14545
  {
13777
14546
  name: "add",
13778
- description: "Search and add an author",
13779
- args: [{ name: "term", description: "Search term", required: true }],
13780
- run: async (c3, a2) => {
13781
- const searchResult = await c3.searchAuthors(a2.term);
13782
- const results = searchResult?.data ?? searchResult;
13783
- if (!Array.isArray(results) || results.length === 0) {
13784
- throw new Error("No authors found.");
13785
- }
13786
- const authorId = await promptSelect("Select an author:", results.map((au) => ({
13787
- label: au.authorName,
13788
- value: String(au.foreignAuthorId)
13789
- })));
13790
- const author = results.find((au) => String(au.foreignAuthorId) === authorId);
13791
- const profilesResult = await c3.getQualityProfiles();
13792
- const profiles = profilesResult?.data ?? profilesResult;
13793
- if (!Array.isArray(profiles) || profiles.length === 0) {
13794
- throw new Error("No quality profiles found. Configure one in Readarr first.");
13795
- }
13796
- const profileId = await promptSelect("Select quality profile:", profiles.map((p) => ({ label: p.name, value: String(p.id) })));
13797
- const foldersResult = await c3.getRootFolders();
13798
- const folders = foldersResult?.data ?? foldersResult;
13799
- if (!Array.isArray(folders) || folders.length === 0) {
13800
- throw new Error("No root folders found. Configure one in Readarr first.");
13801
- }
13802
- const rootFolderPath = await promptSelect("Select root folder:", folders.map((f3) => ({ label: f3.path, value: f3.path })));
13803
- const confirmed = await promptConfirm(`Add "${author.authorName}"?`, !!a2.yes);
13804
- if (!confirmed)
13805
- throw new Error("Cancelled.");
13806
- return c3.addAuthor({
13807
- ...author,
13808
- qualityProfileId: Number(profileId),
13809
- rootFolderPath,
13810
- monitored: true,
13811
- addOptions: { searchForMissingBooks: true }
13812
- });
13813
- }
14547
+ description: "Add a notification from JSON file or stdin",
14548
+ args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
14549
+ run: async (c3, a2) => c3.addNotification(readJsonInput4(a2.file))
13814
14550
  },
13815
14551
  {
13816
14552
  name: "edit",
13817
- description: "Edit an author",
14553
+ description: "Edit a notification (merges JSON with existing)",
13818
14554
  args: [
13819
- { name: "id", description: "Author ID", required: true, type: "number" },
13820
- { name: "monitored", description: "Set monitored (true/false)" },
13821
- { name: "quality-profile-id", description: "Quality profile ID", type: "number" },
13822
- { name: "tags", description: "Comma-separated tag IDs" }
14555
+ { name: "id", description: "Notification ID", required: true, type: "number" },
14556
+ { name: "file", description: "JSON file with fields to update", required: true }
13823
14557
  ],
13824
14558
  run: async (c3, a2) => {
13825
- const result = await c3.getAuthor(a2.id);
13826
- const author = result?.data ?? result;
13827
- const updates = { ...author };
13828
- if (a2.monitored !== undefined)
13829
- updates.monitored = a2.monitored === "true";
13830
- if (a2["quality-profile-id"] !== undefined)
13831
- updates.qualityProfileId = Number(a2["quality-profile-id"]);
13832
- if (a2.tags !== undefined)
13833
- updates.tags = a2.tags.split(",").map((t2) => Number(t2.trim()));
13834
- return c3.updateAuthor(a2.id, updates);
14559
+ const existing = unwrapData4(await c3.getNotification(a2.id));
14560
+ return c3.updateNotification(a2.id, { ...existing, ...readJsonInput4(a2.file) });
13835
14561
  }
13836
14562
  },
13837
14563
  {
13838
- name: "refresh",
13839
- description: "Refresh author metadata",
13840
- args: [{ name: "id", description: "Author ID", required: true, type: "number" }],
13841
- run: (c3, a2) => c3.runCommand({ name: "RefreshAuthor", authorId: a2.id })
13842
- },
13843
- {
13844
- name: "manual-search",
13845
- description: "Trigger a manual search for releases",
13846
- args: [{ name: "id", description: "Author ID", required: true, type: "number" }],
13847
- run: (c3, a2) => c3.runCommand({ name: "AuthorSearch", authorId: a2.id })
14564
+ name: "delete",
14565
+ description: "Delete a notification",
14566
+ args: [{ name: "id", description: "Notification ID", required: true, type: "number" }],
14567
+ confirmMessage: "Are you sure you want to delete this notification?",
14568
+ run: (c3, a2) => c3.deleteNotification(a2.id)
13848
14569
  },
13849
14570
  {
13850
- name: "delete",
13851
- description: "Delete an author",
13852
- args: [{ name: "id", description: "Author ID", required: true, type: "number" }],
13853
- confirmMessage: "Are you sure you want to delete this author?",
13854
- run: (c3, a2) => c3.deleteAuthor(a2.id)
14571
+ name: "test",
14572
+ description: "Test all notifications",
14573
+ run: (c3) => c3.testAllNotifications()
13855
14574
  }
13856
14575
  ]
13857
14576
  },
13858
14577
  {
13859
- name: "book",
13860
- description: "Manage books",
14578
+ name: "downloadclient",
14579
+ description: "Manage download clients",
13861
14580
  actions: [
13862
14581
  {
13863
14582
  name: "list",
13864
- description: "List all books",
13865
- columns: ["id", "title", "authorId", "monitored"],
13866
- run: (c3) => c3.getBooks()
14583
+ description: "List download clients",
14584
+ columns: ["id", "name", "implementation", "enable"],
14585
+ run: (c3) => c3.getDownloadClients()
13867
14586
  },
13868
14587
  {
13869
14588
  name: "get",
13870
- description: "Get a book by ID",
13871
- args: [{ name: "id", description: "Book ID", required: true, type: "number" }],
13872
- run: (c3, a2) => c3.getBook(a2.id)
14589
+ description: "Get a download client by ID",
14590
+ args: [{ name: "id", description: "Download client ID", required: true, type: "number" }],
14591
+ run: (c3, a2) => c3.getDownloadClient(a2.id)
13873
14592
  },
13874
14593
  {
13875
- name: "search",
13876
- description: "Search for books",
13877
- args: [{ name: "term", description: "Search term", required: true }],
13878
- columns: ["foreignBookId", "title", "authorId"],
13879
- run: (c3, a2) => c3.searchBooks(a2.term)
14594
+ name: "add",
14595
+ description: "Add a download client from JSON file or stdin",
14596
+ args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
14597
+ run: async (c3, a2) => c3.addDownloadClient(readJsonInput4(a2.file))
14598
+ },
14599
+ {
14600
+ name: "edit",
14601
+ description: "Edit a download client (merges JSON with existing)",
14602
+ args: [
14603
+ { name: "id", description: "Download client ID", required: true, type: "number" },
14604
+ { name: "file", description: "JSON file with fields to update", required: true }
14605
+ ],
14606
+ run: async (c3, a2) => {
14607
+ const existing = unwrapData4(await c3.getDownloadClient(a2.id));
14608
+ return c3.updateDownloadClient(a2.id, { ...existing, ...readJsonInput4(a2.file) });
14609
+ }
14610
+ },
14611
+ {
14612
+ name: "delete",
14613
+ description: "Delete a download client",
14614
+ args: [{ name: "id", description: "Download client ID", required: true, type: "number" }],
14615
+ confirmMessage: "Are you sure you want to delete this download client?",
14616
+ run: (c3, a2) => c3.deleteDownloadClient(a2.id)
14617
+ },
14618
+ {
14619
+ name: "test",
14620
+ description: "Test all download clients",
14621
+ run: (c3) => c3.testAllDownloadClients()
13880
14622
  }
13881
14623
  ]
13882
14624
  },
13883
14625
  {
13884
- name: "profile",
13885
- description: "Manage quality profiles",
14626
+ name: "blocklist",
14627
+ description: "View blocked releases",
13886
14628
  actions: [
13887
14629
  {
13888
14630
  name: "list",
13889
- description: "List quality profiles",
13890
- columns: ["id", "name"],
13891
- run: (c3) => c3.getQualityProfiles()
14631
+ description: "List blocked releases",
14632
+ columns: ["id", "authorName", "sourceTitle", "date"],
14633
+ run: async (c3) => {
14634
+ const items = unwrapData4(await c3.getBlocklist());
14635
+ return items.map(formatBlocklistItem2);
14636
+ }
14637
+ },
14638
+ {
14639
+ name: "delete",
14640
+ description: "Remove a release from the blocklist",
14641
+ args: [{ name: "id", description: "Blocklist item ID", required: true, type: "number" }],
14642
+ confirmMessage: "Are you sure you want to remove this blocklist entry?",
14643
+ run: (c3, a2) => c3.removeBlocklistItem(a2.id)
13892
14644
  }
13893
14645
  ]
13894
14646
  },
13895
14647
  {
13896
- name: "tag",
13897
- description: "Manage tags",
14648
+ name: "wanted",
14649
+ description: "View missing and cutoff unmet books",
13898
14650
  actions: [
13899
14651
  {
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 })
14652
+ name: "missing",
14653
+ description: "List books with missing files",
14654
+ columns: ["id", "authorName", "title", "releaseDate"],
14655
+ run: async (c3) => {
14656
+ const books = unwrapData4(await c3.getWantedMissing());
14657
+ return books.map(formatBookListItem);
14658
+ }
13904
14659
  },
13905
14660
  {
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})` };
14661
+ name: "cutoff",
14662
+ description: "List books below quality cutoff",
14663
+ columns: ["id", "authorName", "title", "releaseDate"],
14664
+ run: async (c3) => {
14665
+ const books = unwrapData4(await c3.getWantedCutoff());
14666
+ return books.map(formatBookListItem);
13919
14667
  }
13920
- },
14668
+ }
14669
+ ]
14670
+ },
14671
+ {
14672
+ name: "importlist",
14673
+ description: "Manage import lists",
14674
+ actions: [
13921
14675
  {
13922
14676
  name: "list",
13923
- description: "List all tags",
13924
- columns: ["id", "label"],
13925
- run: (c3) => c3.getTags()
14677
+ description: "List import lists",
14678
+ columns: ["id", "name", "implementation", "enable"],
14679
+ run: (c3) => c3.getImportLists()
14680
+ },
14681
+ {
14682
+ name: "get",
14683
+ description: "Get an import list by ID",
14684
+ args: [{ name: "id", description: "Import list ID", required: true, type: "number" }],
14685
+ run: (c3, a2) => c3.getImportList(a2.id)
14686
+ },
14687
+ {
14688
+ name: "delete",
14689
+ description: "Delete an import list",
14690
+ args: [{ name: "id", description: "Import list ID", required: true, type: "number" }],
14691
+ confirmMessage: "Are you sure you want to delete this import list?",
14692
+ run: (c3, a2) => c3.deleteImportList(a2.id)
13926
14693
  }
13927
14694
  ]
13928
14695
  },
@@ -15592,14 +16359,53 @@ var exports_prowlarr3 = {};
15592
16359
  __export(exports_prowlarr3, {
15593
16360
  prowlarr: () => prowlarr
15594
16361
  });
15595
- import { readFileSync as readFileSync4 } from "node:fs";
15596
- function unwrapData3(result) {
16362
+ import { readFileSync as readFileSync6 } from "node:fs";
16363
+ function unwrapData5(result) {
15597
16364
  return result?.data ?? result;
15598
16365
  }
15599
- function readJsonInput3(filePath) {
15600
- const raw = filePath === "-" ? readFileSync4(0, "utf-8") : readFileSync4(filePath, "utf-8");
16366
+ function readJsonInput5(filePath) {
16367
+ const raw = filePath === "-" ? readFileSync6(0, "utf-8") : readFileSync6(filePath, "utf-8");
15601
16368
  return JSON.parse(raw);
15602
16369
  }
16370
+ async function runIndexerTest(client6, indexer) {
16371
+ const result = await client6.testIndexer(indexer);
16372
+ if (result?.error) {
16373
+ const error = result.error;
16374
+ const response = result.response;
16375
+ const status = error?.status ?? response?.status;
16376
+ return {
16377
+ id: indexer?.id,
16378
+ name: indexer?.name ?? "Unknown indexer",
16379
+ status: "fail",
16380
+ message: error?.title ?? error?.message ?? `API error (${status ?? "unknown"})`
16381
+ };
16382
+ }
16383
+ const data = unwrapData5(result);
16384
+ return {
16385
+ id: indexer?.id,
16386
+ name: indexer?.name ?? "Unknown indexer",
16387
+ status: "ok",
16388
+ message: extractIndexerTestMessage(data)
16389
+ };
16390
+ }
16391
+ function extractIndexerTestMessage(data) {
16392
+ if (typeof data === "string" && data.trim() !== "") {
16393
+ return data;
16394
+ }
16395
+ if (Array.isArray(data) && data.length > 0) {
16396
+ const first = data[0];
16397
+ if (typeof first === "string" && first.trim() !== "") {
16398
+ return first;
16399
+ }
16400
+ if (typeof first?.message === "string" && first.message.trim() !== "") {
16401
+ return first.message;
16402
+ }
16403
+ }
16404
+ if (typeof data?.message === "string" && data.message.trim() !== "") {
16405
+ return data.message;
16406
+ }
16407
+ return "Test completed";
16408
+ }
15603
16409
  var resources5, prowlarr;
15604
16410
  var init_prowlarr3 = __esm(() => {
15605
16411
  init_prowlarr2();
@@ -15627,7 +16433,7 @@ var init_prowlarr3 = __esm(() => {
15627
16433
  description: "Add an indexer from JSON file or stdin",
15628
16434
  args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
15629
16435
  run: async (c3, a2) => {
15630
- const body = readJsonInput3(a2.file);
16436
+ const body = readJsonInput5(a2.file);
15631
16437
  return c3.addIndexer(body);
15632
16438
  }
15633
16439
  },
@@ -15639,8 +16445,8 @@ var init_prowlarr3 = __esm(() => {
15639
16445
  { name: "file", description: "JSON file with fields to update", required: true }
15640
16446
  ],
15641
16447
  run: async (c3, a2) => {
15642
- const existing = unwrapData3(await c3.getIndexer(a2.id));
15643
- const updates = readJsonInput3(a2.file);
16448
+ const existing = unwrapData5(await c3.getIndexer(a2.id));
16449
+ const updates = readJsonInput5(a2.file);
15644
16450
  return c3.updateIndexer(a2.id, { ...existing, ...updates });
15645
16451
  }
15646
16452
  },
@@ -15653,8 +16459,17 @@ var init_prowlarr3 = __esm(() => {
15653
16459
  },
15654
16460
  {
15655
16461
  name: "test",
15656
- description: "Test all indexers",
15657
- run: (c3) => c3.testAllIndexers()
16462
+ description: "Test one indexer or all configured indexers",
16463
+ args: [{ name: "id", description: "Indexer ID", type: "number" }],
16464
+ columns: ["id", "name", "status", "message"],
16465
+ run: async (c3, a2) => {
16466
+ const indexers = a2.id ? [unwrapData5(await c3.getIndexer(a2.id))] : unwrapData5(await c3.getIndexers());
16467
+ const results = [];
16468
+ for (const indexer of indexers) {
16469
+ results.push(await runIndexerTest(c3, indexer));
16470
+ }
16471
+ return a2.id ? results[0] : results;
16472
+ }
15658
16473
  }
15659
16474
  ]
15660
16475
  },
@@ -15695,7 +16510,7 @@ var init_prowlarr3 = __esm(() => {
15695
16510
  description: "Add an application from JSON file or stdin",
15696
16511
  args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
15697
16512
  run: async (c3, a2) => {
15698
- const body = readJsonInput3(a2.file);
16513
+ const body = readJsonInput5(a2.file);
15699
16514
  return c3.addApplication(body);
15700
16515
  }
15701
16516
  },
@@ -15707,8 +16522,8 @@ var init_prowlarr3 = __esm(() => {
15707
16522
  { name: "file", description: "JSON file with fields to update", required: true }
15708
16523
  ],
15709
16524
  run: async (c3, a2) => {
15710
- const existing = unwrapData3(await c3.getApplication(a2.id));
15711
- const updates = readJsonInput3(a2.file);
16525
+ const existing = unwrapData5(await c3.getApplication(a2.id));
16526
+ const updates = readJsonInput5(a2.file);
15712
16527
  return c3.updateApplication(a2.id, { ...existing, ...updates });
15713
16528
  }
15714
16529
  },
@@ -15775,7 +16590,7 @@ var init_prowlarr3 = __esm(() => {
15775
16590
  "averageResponseTime"
15776
16591
  ],
15777
16592
  run: async (c3) => {
15778
- const result = unwrapData3(await c3.getIndexerStats());
16593
+ const result = unwrapData5(await c3.getIndexerStats());
15779
16594
  return result?.indexers ?? result;
15780
16595
  }
15781
16596
  }
@@ -15801,7 +16616,7 @@ var init_prowlarr3 = __esm(() => {
15801
16616
  name: "add",
15802
16617
  description: "Add a notification from JSON file or stdin",
15803
16618
  args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
15804
- run: async (c3, a2) => c3.addNotification(readJsonInput3(a2.file))
16619
+ run: async (c3, a2) => c3.addNotification(readJsonInput5(a2.file))
15805
16620
  },
15806
16621
  {
15807
16622
  name: "edit",
@@ -15811,8 +16626,8 @@ var init_prowlarr3 = __esm(() => {
15811
16626
  { name: "file", description: "JSON file with fields to update", required: true }
15812
16627
  ],
15813
16628
  run: async (c3, a2) => {
15814
- const existing = unwrapData3(await c3.getNotification(a2.id));
15815
- return c3.updateNotification(a2.id, { ...existing, ...readJsonInput3(a2.file) });
16629
+ const existing = unwrapData5(await c3.getNotification(a2.id));
16630
+ return c3.updateNotification(a2.id, { ...existing, ...readJsonInput5(a2.file) });
15816
16631
  }
15817
16632
  },
15818
16633
  {
@@ -15849,7 +16664,7 @@ var init_prowlarr3 = __esm(() => {
15849
16664
  name: "add",
15850
16665
  description: "Add a download client from JSON file or stdin",
15851
16666
  args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
15852
- run: async (c3, a2) => c3.addDownloadClient(readJsonInput3(a2.file))
16667
+ run: async (c3, a2) => c3.addDownloadClient(readJsonInput5(a2.file))
15853
16668
  },
15854
16669
  {
15855
16670
  name: "edit",
@@ -15859,8 +16674,8 @@ var init_prowlarr3 = __esm(() => {
15859
16674
  { name: "file", description: "JSON file with fields to update", required: true }
15860
16675
  ],
15861
16676
  run: async (c3, a2) => {
15862
- const existing = unwrapData3(await c3.getDownloadClient(a2.id));
15863
- return c3.updateDownloadClient(a2.id, { ...existing, ...readJsonInput3(a2.file) });
16677
+ const existing = unwrapData5(await c3.getDownloadClient(a2.id));
16678
+ return c3.updateDownloadClient(a2.id, { ...existing, ...readJsonInput5(a2.file) });
15864
16679
  }
15865
16680
  },
15866
16681
  {
@@ -17006,6 +17821,12 @@ function getBazarrApiBaseUrl(baseUrl) {
17006
17821
  const normalizedBaseUrl = baseUrl.replace(/\/$/, "");
17007
17822
  return normalizedBaseUrl.endsWith("/api") ? normalizedBaseUrl : `${normalizedBaseUrl}/api`;
17008
17823
  }
17824
+ function getBazarrHeaders(config) {
17825
+ return {
17826
+ "Content-Type": "application/json",
17827
+ ...config.config.headers ?? {}
17828
+ };
17829
+ }
17009
17830
 
17010
17831
  class BazarrClient {
17011
17832
  clientConfig;
@@ -17013,7 +17834,8 @@ class BazarrClient {
17013
17834
  this.clientConfig = createServarrClient(config);
17014
17835
  client6.setConfig({
17015
17836
  baseUrl: getBazarrApiBaseUrl(this.clientConfig.getBaseUrl()),
17016
- headers: this.clientConfig.getHeaders()
17837
+ headers: getBazarrHeaders(this.clientConfig),
17838
+ auth: this.clientConfig.config.apiKey
17017
17839
  });
17018
17840
  }
17019
17841
  async getSystemStatus() {
@@ -17353,7 +18175,8 @@ class BazarrClient {
17353
18175
  this.clientConfig = createServarrClient(updatedConfig);
17354
18176
  client6.setConfig({
17355
18177
  baseUrl: getBazarrApiBaseUrl(this.clientConfig.getBaseUrl()),
17356
- headers: this.clientConfig.getHeaders()
18178
+ headers: getBazarrHeaders(this.clientConfig),
18179
+ auth: this.clientConfig.config.apiKey
17357
18180
  });
17358
18181
  return this.clientConfig.config;
17359
18182
  }
@@ -17995,8 +18818,8 @@ var init_completions = __esm(() => {
17995
18818
 
17996
18819
  // src/cli/index.ts
17997
18820
  init_dist();
17998
- import { readFileSync as readFileSync5 } from "node:fs";
17999
- var { version } = JSON.parse(readFileSync5(new URL("../../package.json", import.meta.url), "utf-8"));
18821
+ import { readFileSync as readFileSync7 } from "node:fs";
18822
+ var { version } = JSON.parse(readFileSync7(new URL("../../package.json", import.meta.url), "utf-8"));
18000
18823
  var main = defineCommand({
18001
18824
  meta: {
18002
18825
  name: "tsarr",