tsarr 2.7.5 → 2.8.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.
Files changed (50) hide show
  1. package/README.md +4 -1
  2. package/dist/cli/commands/config.d.ts.map +1 -1
  3. package/dist/cli/commands/doctor.d.ts.map +1 -1
  4. package/dist/cli/commands/lidarr.d.ts.map +1 -1
  5. package/dist/cli/commands/prowlarr.d.ts.map +1 -1
  6. package/dist/cli/commands/qbit.d.ts +1 -1
  7. package/dist/cli/commands/qbit.d.ts.map +1 -1
  8. package/dist/cli/commands/radarr.d.ts +1 -1
  9. package/dist/cli/commands/radarr.d.ts.map +1 -1
  10. package/dist/cli/commands/readarr.d.ts.map +1 -1
  11. package/dist/cli/commands/seerr.d.ts +1 -1
  12. package/dist/cli/commands/seerr.d.ts.map +1 -1
  13. package/dist/cli/commands/service.d.ts +6 -0
  14. package/dist/cli/commands/service.d.ts.map +1 -1
  15. package/dist/cli/commands/sonarr.d.ts +1 -1
  16. package/dist/cli/commands/sonarr.d.ts.map +1 -1
  17. package/dist/cli/completions.d.ts.map +1 -1
  18. package/dist/cli/config.d.ts +9 -3
  19. package/dist/cli/config.d.ts.map +1 -1
  20. package/dist/cli/index.js +461 -408
  21. package/dist/clients/bazarr.d.ts +3 -2
  22. package/dist/clients/bazarr.d.ts.map +1 -1
  23. package/dist/clients/lidarr.d.ts +4 -3
  24. package/dist/clients/lidarr.d.ts.map +1 -1
  25. package/dist/clients/prowlarr.d.ts +4 -3
  26. package/dist/clients/prowlarr.d.ts.map +1 -1
  27. package/dist/clients/qbittorrent.d.ts +3 -2
  28. package/dist/clients/qbittorrent.d.ts.map +1 -1
  29. package/dist/clients/radarr.d.ts +4 -3
  30. package/dist/clients/radarr.d.ts.map +1 -1
  31. package/dist/clients/readarr.d.ts +4 -3
  32. package/dist/clients/readarr.d.ts.map +1 -1
  33. package/dist/clients/seerr.d.ts +4 -3
  34. package/dist/clients/seerr.d.ts.map +1 -1
  35. package/dist/clients/sonarr.d.ts +4 -3
  36. package/dist/clients/sonarr.d.ts.map +1 -1
  37. package/dist/core/client.d.ts +1 -1
  38. package/dist/core/client.d.ts.map +1 -1
  39. package/dist/core/index.d.ts +3 -4
  40. package/dist/core/index.d.ts.map +1 -1
  41. package/dist/core/types.d.ts +0 -5
  42. package/dist/core/types.d.ts.map +1 -1
  43. package/dist/index.d.ts +17 -18
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +1 -41
  46. package/dist/tsarr-2.8.0.tgz +0 -0
  47. package/package.json +2 -2
  48. package/dist/core/response.d.ts +0 -13
  49. package/dist/core/response.d.ts.map +0 -1
  50. package/dist/tsarr-2.7.5.tgz +0 -0
package/dist/cli/index.js CHANGED
@@ -564,7 +564,7 @@ var init_dist = __esm(() => {
564
564
  });
565
565
 
566
566
  // src/core/errors.ts
567
- var TsarrError, ApiKeyError, ConnectionError, NotFoundError;
567
+ var TsarrError, ApiKeyError, ConnectionError;
568
568
  var init_errors = __esm(() => {
569
569
  TsarrError = class TsarrError extends Error {
570
570
  code;
@@ -590,12 +590,6 @@ var init_errors = __esm(() => {
590
590
  this.name = "ConnectionError";
591
591
  }
592
592
  };
593
- NotFoundError = class NotFoundError extends TsarrError {
594
- constructor(resource) {
595
- super(`Resource not found: ${resource}`, "NOT_FOUND", 404);
596
- this.name = "NotFoundError";
597
- }
598
- };
599
593
  });
600
594
 
601
595
  // src/core/client.ts
@@ -4862,6 +4856,7 @@ function normalizeServiceConfig(service) {
4862
4856
  const normalized = {
4863
4857
  baseUrl: service.baseUrl ?? "",
4864
4858
  apiKey: service.apiKey ?? "",
4859
+ ...service.name ? { name: service.name } : {},
4865
4860
  ...service.apiKeyFile ? { apiKeyFile: service.apiKeyFile } : {},
4866
4861
  ...service.username ? { username: service.username } : {},
4867
4862
  ...service.password ? { password: service.password } : {}
@@ -4872,21 +4867,29 @@ function normalizeServiceConfig(service) {
4872
4867
  }
4873
4868
  return normalized;
4874
4869
  }
4870
+ function normalizeServiceEntry(entry) {
4871
+ if (Array.isArray(entry)) {
4872
+ return entry.map((item) => normalizeServiceConfig(item)).filter((item) => item != null);
4873
+ }
4874
+ const normalized = normalizeServiceConfig(entry);
4875
+ return normalized ? [normalized] : [];
4876
+ }
4875
4877
  function normalizeConfig(config) {
4876
- const services = Object.fromEntries(Object.entries(config.services ?? {}).map(([name, service]) => [name, normalizeServiceConfig(service)]).filter(([, service]) => service != null));
4877
- return {
4878
- ...config,
4879
- ...Object.keys(services).length > 0 ? { services } : {}
4880
- };
4878
+ const services = Object.fromEntries(Object.entries(config.services ?? {}).map(([name, entry]) => [
4879
+ name,
4880
+ normalizeServiceEntry(entry)
4881
+ ]).filter(([, instances]) => instances.length > 0));
4882
+ const result = {};
4883
+ if (Object.keys(services).length > 0)
4884
+ result.services = services;
4885
+ if (config.defaults)
4886
+ result.defaults = config.defaults;
4887
+ return result;
4881
4888
  }
4882
4889
  function readJsonFile(path) {
4883
4890
  if (!existsSync(path))
4884
4891
  return {};
4885
- try {
4886
- return normalizeConfig(JSON.parse(readFileSync(path, "utf-8")));
4887
- } catch {
4888
- return {};
4889
- }
4892
+ return normalizeConfig(JSON.parse(readFileSync(path, "utf-8")));
4890
4893
  }
4891
4894
  function getEnvConfig() {
4892
4895
  const services = {};
@@ -4912,7 +4915,7 @@ function getEnvConfig() {
4912
4915
  if (timeout)
4913
4916
  partial.timeout = Number(timeout);
4914
4917
  if (Object.keys(partial).length > 0) {
4915
- services[service] = partial;
4918
+ services[service] = [partial];
4916
4919
  }
4917
4920
  }
4918
4921
  return Object.keys(services).length ? { services } : {};
@@ -4929,29 +4932,64 @@ function findLocalConfigPath() {
4929
4932
  dir = parent;
4930
4933
  }
4931
4934
  }
4935
+ function isArrayEntry(raw) {
4936
+ return Array.isArray(raw);
4937
+ }
4932
4938
  function loadConfig() {
4933
- const global = readJsonFile(GLOBAL_CONFIG_PATH);
4939
+ const globalRaw = readJsonFile(GLOBAL_CONFIG_PATH);
4934
4940
  const localPath = findLocalConfigPath();
4935
- const local = localPath ? readJsonFile(localPath) : {};
4941
+ const localRaw = localPath ? readJsonFile(localPath) : {};
4936
4942
  const env2 = getEnvConfig();
4943
+ let globalDiskRaw = {};
4944
+ let localDiskRaw = {};
4945
+ try {
4946
+ if (existsSync(GLOBAL_CONFIG_PATH)) {
4947
+ globalDiskRaw = JSON.parse(readFileSync(GLOBAL_CONFIG_PATH, "utf-8"));
4948
+ }
4949
+ } catch {}
4950
+ try {
4951
+ if (localPath && existsSync(localPath)) {
4952
+ localDiskRaw = JSON.parse(readFileSync(localPath, "utf-8"));
4953
+ }
4954
+ } catch {}
4937
4955
  const allServiceNames = new Set([
4938
- ...Object.keys(global.services ?? {}),
4939
- ...Object.keys(local.services ?? {}),
4956
+ ...Object.keys(globalRaw.services ?? {}),
4957
+ ...Object.keys(localRaw.services ?? {}),
4940
4958
  ...Object.keys(env2.services ?? {})
4941
4959
  ]);
4942
4960
  const services = {};
4943
4961
  for (const name of allServiceNames) {
4944
- services[name] = {
4945
- ...global.services?.[name],
4946
- ...local.services?.[name],
4947
- ...env2.services?.[name]
4948
- };
4962
+ const globalInstances = globalRaw.services?.[name] ?? [];
4963
+ const localInstances = localRaw.services?.[name] ?? [];
4964
+ const envInstances = env2.services?.[name] ?? [];
4965
+ const globalIsArray = isArrayEntry(globalDiskRaw.services?.[name]);
4966
+ const localIsArray = isArrayEntry(localDiskRaw.services?.[name]);
4967
+ if (globalIsArray || localIsArray) {
4968
+ const base = localInstances.length > 0 ? localInstances : globalInstances;
4969
+ if (envInstances.length > 0 && base.length > 0) {
4970
+ base[0] = { ...base[0], ...envInstances[0] };
4971
+ } else if (envInstances.length > 0) {
4972
+ services[name] = envInstances;
4973
+ continue;
4974
+ }
4975
+ services[name] = base;
4976
+ } else {
4977
+ const globalObj = globalInstances[0];
4978
+ const localObj = localInstances[0];
4979
+ const envObj = envInstances[0];
4980
+ const merged2 = {
4981
+ ...globalObj,
4982
+ ...localObj,
4983
+ ...envObj
4984
+ };
4985
+ services[name] = [merged2];
4986
+ }
4949
4987
  }
4950
4988
  const merged = {
4951
4989
  services,
4952
4990
  defaults: {
4953
- ...global.defaults,
4954
- ...local.defaults
4991
+ ...globalRaw.defaults,
4992
+ ...localRaw.defaults
4955
4993
  }
4956
4994
  };
4957
4995
  return merged;
@@ -4962,16 +5000,20 @@ function resolveConfigRelativePath(filePath, configPath) {
4962
5000
  }
4963
5001
  return resolve(dirname(configPath), filePath);
4964
5002
  }
4965
- function getResolvedApiKeyFilePath(serviceName, service, localPath, local, global) {
4966
- if (!service.apiKeyFile)
5003
+ function getResolvedApiKeyFilePath(serviceName, instance, localPath, local, global) {
5004
+ if (!instance.apiKeyFile)
4967
5005
  return;
4968
- if (local.services?.[serviceName]?.apiKeyFile) {
4969
- return resolveConfigRelativePath(service.apiKeyFile, localPath);
5006
+ const localInstances = local.services?.[serviceName] ?? [];
5007
+ const localMatch = localInstances.find((i2) => i2.apiKeyFile && (instance.name ? i2.name === instance.name : true));
5008
+ if (localMatch) {
5009
+ return resolveConfigRelativePath(instance.apiKeyFile, localPath);
4970
5010
  }
4971
- if (global.services?.[serviceName]?.apiKeyFile) {
4972
- return resolveConfigRelativePath(service.apiKeyFile, GLOBAL_CONFIG_PATH);
5011
+ const globalInstances = global.services?.[serviceName] ?? [];
5012
+ const globalMatch = globalInstances.find((i2) => i2.apiKeyFile && (instance.name ? i2.name === instance.name : true));
5013
+ if (globalMatch) {
5014
+ return resolveConfigRelativePath(instance.apiKeyFile, GLOBAL_CONFIG_PATH);
4973
5015
  }
4974
- return service.apiKeyFile;
5016
+ return instance.apiKeyFile;
4975
5017
  }
4976
5018
  function readApiKeyFile(filePath) {
4977
5019
  if (!existsSync(filePath)) {
@@ -4983,75 +5025,132 @@ function readApiKeyFile(filePath) {
4983
5025
  throw new Error(`Failed to read API key file: ${filePath}: ${err instanceof Error ? err.message : String(err)}`);
4984
5026
  }
4985
5027
  }
4986
- function getServiceConfig(serviceName) {
5028
+ function getServiceInstances(serviceName) {
5029
+ const config = loadConfig();
5030
+ return config.services[serviceName] ?? [];
5031
+ }
5032
+ function resolveInstance(instances, instanceName) {
5033
+ if (!instances.length)
5034
+ return;
5035
+ if (!instanceName)
5036
+ return instances[0];
5037
+ return instances.find((i2) => i2.name?.toLowerCase() === instanceName.toLowerCase());
5038
+ }
5039
+ function getServiceConfig(serviceName, instanceName) {
4987
5040
  const global = readJsonFile(GLOBAL_CONFIG_PATH);
4988
5041
  const localPath = findLocalConfigPath();
4989
5042
  const local = localPath ? readJsonFile(localPath) : {};
4990
5043
  const config = loadConfig();
4991
- const service = config.services[serviceName];
4992
- if (!service?.baseUrl)
5044
+ const instances = config.services[serviceName] ?? [];
5045
+ const instance = resolveInstance(instances, instanceName);
5046
+ if (!instance?.baseUrl)
4993
5047
  return null;
4994
5048
  if (serviceName === "qbittorrent") {
4995
- if (!service.username || !service.password)
5049
+ if (!instance.username || !instance.password)
4996
5050
  return null;
4997
5051
  return {
4998
- baseUrl: service.baseUrl,
4999
- username: service.username,
5000
- password: service.password,
5001
- ...service.timeout ? { timeout: service.timeout } : {}
5052
+ baseUrl: instance.baseUrl,
5053
+ username: instance.username,
5054
+ password: instance.password,
5055
+ ...instance.timeout ? { timeout: instance.timeout } : {}
5002
5056
  };
5003
5057
  }
5004
- let apiKey = service.apiKey;
5005
- const apiKeyFilePath = getResolvedApiKeyFilePath(serviceName, service, localPath, local, global);
5058
+ let apiKey = instance.apiKey;
5059
+ const apiKeyFilePath = getResolvedApiKeyFilePath(serviceName, instance, localPath, local, global);
5006
5060
  if (!apiKey && apiKeyFilePath) {
5007
5061
  apiKey = readApiKeyFile(apiKeyFilePath);
5008
5062
  }
5009
5063
  if (!apiKey)
5010
5064
  return null;
5011
5065
  return {
5012
- baseUrl: service.baseUrl,
5066
+ baseUrl: instance.baseUrl,
5013
5067
  apiKey,
5014
- ...service.timeout ? { timeout: service.timeout } : {}
5068
+ ...instance.timeout ? { timeout: instance.timeout } : {}
5015
5069
  };
5016
5070
  }
5071
+ function serializeForDisk(config) {
5072
+ const services = {};
5073
+ for (const [name, instances] of Object.entries(config.services)) {
5074
+ if (instances.length === 1 && !instances[0].name) {
5075
+ const { name: _name, ...rest } = instances[0];
5076
+ services[name] = rest;
5077
+ } else {
5078
+ services[name] = instances;
5079
+ }
5080
+ }
5081
+ return { services, defaults: config.defaults };
5082
+ }
5017
5083
  function saveGlobalConfig(config) {
5018
5084
  mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true });
5019
- writeFileSync(GLOBAL_CONFIG_PATH, `${JSON.stringify(config, null, 2)}
5085
+ writeFileSync(GLOBAL_CONFIG_PATH, `${JSON.stringify(serializeForDisk(config), null, 2)}
5020
5086
  `);
5021
5087
  }
5022
5088
  function saveLocalConfig(config) {
5023
5089
  const existingPath = findLocalConfigPath();
5024
5090
  const targetPath = existingPath ?? join(process.cwd(), LOCAL_CONFIG_NAME);
5025
- writeFileSync(targetPath, `${JSON.stringify(config, null, 2)}
5091
+ writeFileSync(targetPath, `${JSON.stringify(serializeForDisk(config), null, 2)}
5026
5092
  `);
5027
5093
  }
5094
+ function resolveArrayPath(instances, segment) {
5095
+ const byName = instances.find((i2) => i2.name === segment);
5096
+ if (byName)
5097
+ return byName;
5098
+ const idx = Number(segment);
5099
+ if (Number.isInteger(idx) && idx >= 0 && idx < instances.length)
5100
+ return instances[idx];
5101
+ return;
5102
+ }
5028
5103
  function getConfigValue(key) {
5029
5104
  const config = loadConfig();
5030
5105
  const parts = key.split(".");
5031
5106
  let current = config;
5032
- for (const part of parts) {
5107
+ for (let i2 = 0;i2 < parts.length; i2++) {
5033
5108
  if (current == null || typeof current !== "object")
5034
5109
  return;
5035
- current = current[part];
5110
+ if (Array.isArray(current)) {
5111
+ const resolved = resolveArrayPath(current, parts[i2]);
5112
+ if (resolved) {
5113
+ current = resolved;
5114
+ continue;
5115
+ }
5116
+ current = current[0]?.[parts[i2]];
5117
+ } else {
5118
+ current = current[parts[i2]];
5119
+ }
5036
5120
  }
5037
5121
  return current != null ? String(current) : undefined;
5038
5122
  }
5039
5123
  function setConfigValue(key, value, global = true) {
5040
5124
  const configPath = global ? GLOBAL_CONFIG_PATH : findLocalConfigPath() ?? join(process.cwd(), LOCAL_CONFIG_NAME);
5041
- const config = readJsonFile(configPath);
5125
+ const raw = existsSync(configPath) ? JSON.parse(readFileSync(configPath, "utf-8")) : {};
5042
5126
  const parts = key.split(".");
5043
- let current = config;
5127
+ let current = raw;
5044
5128
  for (let i2 = 0;i2 < parts.length - 1; i2++) {
5045
- if (current[parts[i2]] == null || typeof current[parts[i2]] !== "object") {
5046
- current[parts[i2]] = {};
5129
+ const part = parts[i2];
5130
+ if (Array.isArray(current[part])) {
5131
+ const nextPart = parts[i2 + 1];
5132
+ const resolved = resolveArrayPath(current[part], nextPart);
5133
+ if (resolved) {
5134
+ current = resolved;
5135
+ i2++;
5136
+ continue;
5137
+ }
5138
+ current = current[part][0];
5139
+ continue;
5047
5140
  }
5048
- current = current[parts[i2]];
5141
+ if (current[part] == null || typeof current[part] !== "object") {
5142
+ current[part] = {};
5143
+ }
5144
+ current = current[part];
5049
5145
  }
5050
5146
  current[parts[parts.length - 1]] = parseConfigValue(key, value);
5051
5147
  if (global) {
5052
- saveGlobalConfig(config);
5148
+ mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true });
5149
+ writeFileSync(configPath, `${JSON.stringify(raw, null, 2)}
5150
+ `);
5053
5151
  } else {
5054
- saveLocalConfig(config);
5152
+ writeFileSync(configPath, `${JSON.stringify(raw, null, 2)}
5153
+ `);
5055
5154
  }
5056
5155
  }
5057
5156
  function parseConfigValue(key, value) {
@@ -5293,14 +5392,10 @@ function formatBytes(bytes) {
5293
5392
  function formatDate(value) {
5294
5393
  if (typeof value !== "string" && !(value instanceof Date))
5295
5394
  return String(value);
5296
- try {
5297
- const d2 = new Date(value);
5298
- if (Number.isNaN(d2.getTime()))
5299
- return String(value);
5300
- return d2.toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" });
5301
- } catch {
5395
+ const d2 = new Date(value);
5396
+ if (Number.isNaN(d2.getTime()))
5302
5397
  return String(value);
5303
- }
5398
+ return d2.toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" });
5304
5399
  }
5305
5400
  function stripAnsi3(str) {
5306
5401
  return str.replace(ANSI_PATTERN, "");
@@ -5322,6 +5417,7 @@ var init_output = __esm(() => {
5322
5417
  });
5323
5418
 
5324
5419
  // src/cli/commands/service.ts
5420
+ import { readFileSync as readFileSync2 } from "node:fs";
5325
5421
  function limitResults(results, limit) {
5326
5422
  if (limit === undefined)
5327
5423
  return results;
@@ -5352,6 +5448,11 @@ function buildServiceCommand(serviceName, description, clientFactory, resources)
5352
5448
  description: "Cherry-pick fields (comma-separated, JSON mode)"
5353
5449
  },
5354
5450
  yes: { type: "boolean", alias: "y", description: "Skip confirmation prompts" },
5451
+ instance: {
5452
+ type: "string",
5453
+ alias: "i",
5454
+ description: "Instance name (for multi-instance services)"
5455
+ },
5355
5456
  ...(action.args ?? []).reduce((acc, arg) => {
5356
5457
  acc[arg.name] = {
5357
5458
  type: arg.type === "boolean" ? "boolean" : arg.type === "number" ? "string" : "string",
@@ -5362,10 +5463,17 @@ function buildServiceCommand(serviceName, description, clientFactory, resources)
5362
5463
  }, {})
5363
5464
  },
5364
5465
  async run({ args }) {
5365
- const config = getServiceConfig(serviceName);
5466
+ const instanceName = args.instance;
5467
+ const config = getServiceConfig(serviceName, instanceName);
5366
5468
  if (!config) {
5367
- const envHint = serviceName === "qbittorrent" ? `TSARR_QBITTORRENT_URL, TSARR_QBITTORRENT_USERNAME, and TSARR_QBITTORRENT_PASSWORD` : `TSARR_${serviceName.toUpperCase()}_URL and TSARR_${serviceName.toUpperCase()}_API_KEY`;
5368
- consola.error(`${serviceName} is not configured. Run \`tsarr config init\` or set ${envHint} environment variables.`);
5469
+ if (instanceName) {
5470
+ const available = getServiceInstances(serviceName).map((i2) => i2.name).filter(Boolean);
5471
+ const hint = available.length ? ` Available: ${available.join(", ")}` : "";
5472
+ consola.error(`${serviceName} instance '${instanceName}' is not configured.${hint}`);
5473
+ } else {
5474
+ const envHint = serviceName === "qbittorrent" ? `TSARR_QBITTORRENT_URL, TSARR_QBITTORRENT_USERNAME, and TSARR_QBITTORRENT_PASSWORD` : `TSARR_${serviceName.toUpperCase()}_URL and TSARR_${serviceName.toUpperCase()}_API_KEY`;
5475
+ consola.error(`${serviceName} is not configured. Run \`tsarr config init\` or set ${envHint} environment variables.`);
5476
+ }
5369
5477
  process.exit(1);
5370
5478
  }
5371
5479
  try {
@@ -5393,7 +5501,7 @@ function buildServiceCommand(serviceName, description, clientFactory, resources)
5393
5501
  const noHeader = process.argv.includes("--no-header") || !!args.noHeader;
5394
5502
  const dryRun = !!(args["dry-run"] ?? args.dryRun ?? process.argv.includes("--dry-run"));
5395
5503
  if (dryRun) {
5396
- formatOutput(buildDryRunPreview(format, serviceName, resource.name, action.name, resolvedArgs), {
5504
+ formatOutput(buildDryRunPreview(format, serviceName, resource.name, action.name, resolvedArgs, instanceName), {
5397
5505
  format,
5398
5506
  noHeader
5399
5507
  });
@@ -5474,6 +5582,44 @@ Run \`tsarr config init\` or set TSARR_${serviceName.toUpperCase()}_API_KEY`;
5474
5582
  subCommands
5475
5583
  });
5476
5584
  }
5585
+ function unwrapData(result) {
5586
+ return result?.data ?? result;
5587
+ }
5588
+ function readJsonInput(filePath) {
5589
+ const raw = filePath === "-" ? readFileSync2(0, "utf-8") : readFileSync2(filePath, "utf-8");
5590
+ return JSON.parse(raw);
5591
+ }
5592
+ function parseBooleanArg(value, fallback) {
5593
+ if (value === undefined)
5594
+ return fallback;
5595
+ if (typeof value === "boolean")
5596
+ return value;
5597
+ if (typeof value === "string") {
5598
+ const normalized = value.trim().toLowerCase();
5599
+ if (normalized === "true")
5600
+ return true;
5601
+ if (normalized === "false")
5602
+ return false;
5603
+ }
5604
+ return Boolean(value);
5605
+ }
5606
+ function resolveQualityProfileId(profiles, profileId) {
5607
+ const profile = profiles.find((item) => item?.id === profileId);
5608
+ if (!profile) {
5609
+ throw new Error(`Quality profile ${profileId} was not found.`);
5610
+ }
5611
+ return profileId;
5612
+ }
5613
+ function resolveRootFolderPath(folders, rootFolderPath) {
5614
+ const folder = folders.find((item) => item?.path === rootFolderPath);
5615
+ if (!folder) {
5616
+ throw new Error(`Root folder "${rootFolderPath}" was not found.`);
5617
+ }
5618
+ return rootFolderPath;
5619
+ }
5620
+ function getApiStatus(result) {
5621
+ return result?.error?.status ?? result?.response?.status;
5622
+ }
5477
5623
  function coerceBooleanArg(value) {
5478
5624
  if (typeof value === "boolean")
5479
5625
  return value;
@@ -5486,19 +5632,21 @@ function coerceBooleanArg(value) {
5486
5632
  }
5487
5633
  return Boolean(value);
5488
5634
  }
5489
- function buildDryRunPreview(format, serviceName, resourceName, actionName, args) {
5490
- 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"));
5635
+ function buildDryRunPreview(format, serviceName, resourceName, actionName, args, instanceName) {
5636
+ 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" && key !== "instance"));
5637
+ const serviceLabel = instanceName ? `${serviceName}[${instanceName}]` : serviceName;
5491
5638
  if (format === "json") {
5492
5639
  return {
5493
5640
  dryRun: true,
5494
5641
  service: serviceName,
5642
+ ...instanceName ? { instance: instanceName } : {},
5495
5643
  resource: resourceName,
5496
5644
  action: actionName,
5497
5645
  args: filteredArgs
5498
5646
  };
5499
5647
  }
5500
5648
  return {
5501
- message: `Dry run: would execute ${serviceName} ${resourceName} ${actionName}${formatDryRunArgs(filteredArgs)}`
5649
+ message: `Dry run: would execute ${serviceLabel} ${resourceName} ${actionName}${formatDryRunArgs(filteredArgs)}`
5502
5650
  };
5503
5651
  }
5504
5652
  function formatDryRunArgs(args) {
@@ -5514,8 +5662,6 @@ Run \`tsarr config init\` or set TSARR_${serviceName.toUpperCase()}_API_KEY`);
5514
5662
  } else if (error instanceof ConnectionError) {
5515
5663
  consola.error(`Connection error: ${error.message}
5516
5664
  Run \`tsarr doctor\` to diagnose.`);
5517
- } else if (error instanceof NotFoundError) {
5518
- consola.error(error.message);
5519
5665
  } else if (error instanceof TsarrError) {
5520
5666
  consola.error(`Error: ${error.message}`);
5521
5667
  } else if (error instanceof Error) {
@@ -5572,51 +5718,12 @@ __export(exports_radarr3, {
5572
5718
  resources: () => resources,
5573
5719
  radarr: () => radarr
5574
5720
  });
5575
- import { readFileSync as readFileSync2 } from "node:fs";
5576
- function unwrapData(result) {
5577
- return result?.data ?? result;
5578
- }
5579
- function parseBooleanArg(value, fallback) {
5580
- if (value === undefined)
5581
- return fallback;
5582
- if (typeof value === "boolean")
5583
- return value;
5584
- if (typeof value === "string") {
5585
- const normalized = value.trim().toLowerCase();
5586
- if (normalized === "true")
5587
- return true;
5588
- if (normalized === "false")
5589
- return false;
5590
- }
5591
- return Boolean(value);
5592
- }
5593
- function resolveQualityProfileId(profiles, profileId) {
5594
- const profile = profiles.find((item) => item?.id === profileId);
5595
- if (!profile) {
5596
- throw new Error(`Quality profile ${profileId} was not found.`);
5597
- }
5598
- return profileId;
5599
- }
5600
- function resolveRootFolderPath(folders, rootFolderPath) {
5601
- const folder = folders.find((item) => item?.path === rootFolderPath);
5602
- if (!folder) {
5603
- throw new Error(`Root folder "${rootFolderPath}" was not found.`);
5604
- }
5605
- return rootFolderPath;
5606
- }
5607
5721
  async function findMovieByTmdbId(client2, tmdbId) {
5608
5722
  if (tmdbId === undefined)
5609
5723
  return;
5610
5724
  const movies = unwrapData(await client2.getMovies());
5611
5725
  return movies.find((movie) => movie?.tmdbId === tmdbId);
5612
5726
  }
5613
- function getApiStatus(result) {
5614
- return result?.error?.status ?? result?.response?.status;
5615
- }
5616
- function readJsonInput(filePath) {
5617
- const raw = filePath === "-" ? readFileSync2(0, "utf-8") : readFileSync2(filePath, "utf-8");
5618
- return JSON.parse(raw);
5619
- }
5620
5727
  var resources, radarr;
5621
5728
  var init_radarr3 = __esm(() => {
5622
5729
  init_radarr2();
@@ -8684,38 +8791,6 @@ __export(exports_sonarr3, {
8684
8791
  sonarr: () => sonarr,
8685
8792
  resources: () => resources2
8686
8793
  });
8687
- import { readFileSync as readFileSync3 } from "node:fs";
8688
- function unwrapData2(result) {
8689
- return result?.data ?? result;
8690
- }
8691
- function parseBooleanArg2(value, fallback) {
8692
- if (value === undefined)
8693
- return fallback;
8694
- if (typeof value === "boolean")
8695
- return value;
8696
- if (typeof value === "string") {
8697
- const normalized = value.trim().toLowerCase();
8698
- if (normalized === "true")
8699
- return true;
8700
- if (normalized === "false")
8701
- return false;
8702
- }
8703
- return Boolean(value);
8704
- }
8705
- function resolveQualityProfileId2(profiles, profileId) {
8706
- const profile = profiles.find((item) => item?.id === profileId);
8707
- if (!profile) {
8708
- throw new Error(`Quality profile ${profileId} was not found.`);
8709
- }
8710
- return profileId;
8711
- }
8712
- function resolveRootFolderPath2(folders, rootFolderPath) {
8713
- const folder = folders.find((item) => item?.path === rootFolderPath);
8714
- if (!folder) {
8715
- throw new Error(`Root folder "${rootFolderPath}" was not found.`);
8716
- }
8717
- return rootFolderPath;
8718
- }
8719
8794
  function formatSeriesListItem(series) {
8720
8795
  const seasons = Array.isArray(series?.seasons) ? series.seasons.filter((season) => season?.seasonNumber !== 0) : [];
8721
8796
  const statistics = series?.statistics ?? {};
@@ -8728,26 +8803,19 @@ function formatSeriesListItem(series) {
8728
8803
  };
8729
8804
  }
8730
8805
  async function lookupSeriesByTvdbId(client3, tvdbId) {
8731
- const tvdbSearch = unwrapData2(await client3.searchSeries(`tvdb:${tvdbId}`));
8806
+ const tvdbSearch = unwrapData(await client3.searchSeries(`tvdb:${tvdbId}`));
8732
8807
  const exactTvdbMatch = tvdbSearch.find((series) => series?.tvdbId === tvdbId);
8733
8808
  if (exactTvdbMatch)
8734
8809
  return exactTvdbMatch;
8735
- const fallbackSearch = unwrapData2(await client3.searchSeries(String(tvdbId)));
8810
+ const fallbackSearch = unwrapData(await client3.searchSeries(String(tvdbId)));
8736
8811
  return fallbackSearch.find((series) => series?.tvdbId === tvdbId);
8737
8812
  }
8738
8813
  async function findSeriesByTvdbId(client3, tvdbId) {
8739
8814
  if (tvdbId === undefined)
8740
8815
  return;
8741
- const series = unwrapData2(await client3.getSeries());
8816
+ const series = unwrapData(await client3.getSeries());
8742
8817
  return series.find((item) => item?.tvdbId === tvdbId);
8743
8818
  }
8744
- function getApiStatus2(result) {
8745
- return result?.error?.status ?? result?.response?.status;
8746
- }
8747
- function readJsonInput2(filePath) {
8748
- const raw = filePath === "-" ? readFileSync3(0, "utf-8") : readFileSync3(filePath, "utf-8");
8749
- return JSON.parse(raw);
8750
- }
8751
8819
  var resources2, sonarr;
8752
8820
  var init_sonarr3 = __esm(() => {
8753
8821
  init_sonarr2();
@@ -8772,7 +8840,7 @@ var init_sonarr3 = __esm(() => {
8772
8840
  "status"
8773
8841
  ],
8774
8842
  run: async (c3) => {
8775
- const series = unwrapData2(await c3.getSeries());
8843
+ const series = unwrapData(await c3.getSeries());
8776
8844
  return series.map(formatSeriesListItem);
8777
8845
  }
8778
8846
  },
@@ -8792,7 +8860,7 @@ var init_sonarr3 = __esm(() => {
8792
8860
  ],
8793
8861
  run: async (c3, a2) => {
8794
8862
  const result = await c3.getSeriesById(a2.id);
8795
- const series = unwrapData2(result);
8863
+ const series = unwrapData(result);
8796
8864
  return formatSeriesListItem(series);
8797
8865
  }
8798
8866
  },
@@ -8805,7 +8873,7 @@ var init_sonarr3 = __esm(() => {
8805
8873
  ],
8806
8874
  columns: ["tvdbId", "title", "year", "overview"],
8807
8875
  run: async (c3, a2) => {
8808
- const results = unwrapData2(await c3.searchSeries(a2.term));
8876
+ const results = unwrapData(await c3.searchSeries(a2.term));
8809
8877
  return limitResults(results, a2.limit);
8810
8878
  }
8811
8879
  },
@@ -8829,7 +8897,7 @@ var init_sonarr3 = __esm(() => {
8829
8897
  } else {
8830
8898
  const term = await promptIfMissing(a2.term, "Search term:");
8831
8899
  const searchResult = await c3.searchSeries(term);
8832
- const results = unwrapData2(searchResult);
8900
+ const results = unwrapData(searchResult);
8833
8901
  if (!Array.isArray(results) || results.length === 0) {
8834
8902
  throw new Error("No series found.");
8835
8903
  }
@@ -8843,17 +8911,17 @@ var init_sonarr3 = __esm(() => {
8843
8911
  }
8844
8912
  }
8845
8913
  const profilesResult = await c3.getQualityProfiles();
8846
- const profiles = unwrapData2(profilesResult);
8914
+ const profiles = unwrapData(profilesResult);
8847
8915
  if (!Array.isArray(profiles) || profiles.length === 0) {
8848
8916
  throw new Error("No quality profiles found. Configure one in Sonarr first.");
8849
8917
  }
8850
- 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) }))));
8918
+ 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) }))));
8851
8919
  const foldersResult = await c3.getRootFolders();
8852
- const folders = unwrapData2(foldersResult);
8920
+ const folders = unwrapData(foldersResult);
8853
8921
  if (!Array.isArray(folders) || folders.length === 0) {
8854
8922
  throw new Error("No root folders found. Configure one in Sonarr first.");
8855
8923
  }
8856
- 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 })));
8924
+ 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 })));
8857
8925
  const confirmed = await promptConfirm(`Add "${series.title} (${series.year})"?`, !!a2.yes);
8858
8926
  if (!confirmed)
8859
8927
  throw new Error("Cancelled.");
@@ -8861,10 +8929,10 @@ var init_sonarr3 = __esm(() => {
8861
8929
  ...series,
8862
8930
  qualityProfileId: profileId,
8863
8931
  rootFolderPath,
8864
- monitored: parseBooleanArg2(a2.monitored, true),
8932
+ monitored: parseBooleanArg(a2.monitored, true),
8865
8933
  addOptions: { searchForMissingEpisodes: true }
8866
8934
  });
8867
- if (addResult?.error && getApiStatus2(addResult) === 400) {
8935
+ if (addResult?.error && getApiStatus(addResult) === 400) {
8868
8936
  const existingSeries = await findSeriesByTvdbId(c3, series.tvdbId);
8869
8937
  if (existingSeries) {
8870
8938
  throw new Error(`${existingSeries.title} is already in your library (ID: ${existingSeries.id})`);
@@ -8926,7 +8994,7 @@ var init_sonarr3 = __esm(() => {
8926
8994
  const seriesResult = await c3.getSeriesById(a2.id);
8927
8995
  if (seriesResult?.error)
8928
8996
  return seriesResult;
8929
- const series = unwrapData2(seriesResult);
8997
+ const series = unwrapData(seriesResult);
8930
8998
  const deleteResult = await c3.deleteSeries(a2.id, {
8931
8999
  deleteFiles: a2["delete-files"],
8932
9000
  addImportListExclusion: a2["add-import-list-exclusion"]
@@ -9027,7 +9095,7 @@ var init_sonarr3 = __esm(() => {
9027
9095
  const tagResult = await c3.getTag(a2.id);
9028
9096
  if (tagResult?.error)
9029
9097
  return tagResult;
9030
- const tag = unwrapData2(tagResult);
9098
+ const tag = unwrapData(tagResult);
9031
9099
  const deleteResult = await c3.deleteTag(a2.id);
9032
9100
  if (deleteResult?.error)
9033
9101
  return deleteResult;
@@ -9097,7 +9165,7 @@ var init_sonarr3 = __esm(() => {
9097
9165
  run: async (c3, a2) => {
9098
9166
  if (a2.since) {
9099
9167
  const result2 = await c3.getHistorySince(a2.since, a2["series-id"]);
9100
- const items2 = unwrapData2(result2);
9168
+ const items2 = unwrapData(result2);
9101
9169
  if (a2.until) {
9102
9170
  const untilDate = new Date(a2.until);
9103
9171
  return items2.filter((item) => new Date(item.date) <= untilDate);
@@ -9105,7 +9173,7 @@ var init_sonarr3 = __esm(() => {
9105
9173
  return items2;
9106
9174
  }
9107
9175
  const result = await c3.getHistory(undefined, undefined, undefined, undefined, a2["series-id"]);
9108
- const items = unwrapData2(result);
9176
+ const items = unwrapData(result);
9109
9177
  if (a2.until) {
9110
9178
  const untilDate = new Date(a2.until);
9111
9179
  return items.filter((item) => new Date(item.date) <= untilDate);
@@ -9155,7 +9223,7 @@ var init_sonarr3 = __esm(() => {
9155
9223
  columns: ["id", "seriesTitle", "title", "seasonNumber", "episodeNumber", "airDateUtc"],
9156
9224
  run: async (c3, a2) => {
9157
9225
  const result = await c3.getCalendar(a2.start, a2.end, a2.unmonitored);
9158
- const episodes = unwrapData2(result);
9226
+ const episodes = unwrapData(result);
9159
9227
  return episodes.map((ep) => ({
9160
9228
  ...ep,
9161
9229
  seriesTitle: ep.seriesTitle || ep.series?.title || "—"
@@ -9184,7 +9252,7 @@ var init_sonarr3 = __esm(() => {
9184
9252
  name: "add",
9185
9253
  description: "Add a notification from JSON file or stdin",
9186
9254
  args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
9187
- run: async (c3, a2) => c3.addNotification(readJsonInput2(a2.file))
9255
+ run: async (c3, a2) => c3.addNotification(readJsonInput(a2.file))
9188
9256
  },
9189
9257
  {
9190
9258
  name: "edit",
@@ -9194,8 +9262,8 @@ var init_sonarr3 = __esm(() => {
9194
9262
  { name: "file", description: "JSON file with fields to update", required: true }
9195
9263
  ],
9196
9264
  run: async (c3, a2) => {
9197
- const existing = unwrapData2(await c3.getNotification(a2.id));
9198
- return c3.updateNotification(a2.id, { ...existing, ...readJsonInput2(a2.file) });
9265
+ const existing = unwrapData(await c3.getNotification(a2.id));
9266
+ return c3.updateNotification(a2.id, { ...existing, ...readJsonInput(a2.file) });
9199
9267
  }
9200
9268
  },
9201
9269
  {
@@ -9232,7 +9300,7 @@ var init_sonarr3 = __esm(() => {
9232
9300
  name: "add",
9233
9301
  description: "Add a download client from JSON file or stdin",
9234
9302
  args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
9235
- run: async (c3, a2) => c3.addDownloadClient(readJsonInput2(a2.file))
9303
+ run: async (c3, a2) => c3.addDownloadClient(readJsonInput(a2.file))
9236
9304
  },
9237
9305
  {
9238
9306
  name: "edit",
@@ -9242,8 +9310,8 @@ var init_sonarr3 = __esm(() => {
9242
9310
  { name: "file", description: "JSON file with fields to update", required: true }
9243
9311
  ],
9244
9312
  run: async (c3, a2) => {
9245
- const existing = unwrapData2(await c3.getDownloadClient(a2.id));
9246
- return c3.updateDownloadClient(a2.id, { ...existing, ...readJsonInput2(a2.file) });
9313
+ const existing = unwrapData(await c3.getDownloadClient(a2.id));
9314
+ return c3.updateDownloadClient(a2.id, { ...existing, ...readJsonInput(a2.file) });
9247
9315
  }
9248
9316
  },
9249
9317
  {
@@ -9317,7 +9385,7 @@ var init_sonarr3 = __esm(() => {
9317
9385
  name: "add",
9318
9386
  description: "Add an import list from JSON file or stdin",
9319
9387
  args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
9320
- run: async (c3, a2) => c3.addImportList(readJsonInput2(a2.file))
9388
+ run: async (c3, a2) => c3.addImportList(readJsonInput(a2.file))
9321
9389
  },
9322
9390
  {
9323
9391
  name: "edit",
@@ -9327,8 +9395,8 @@ var init_sonarr3 = __esm(() => {
9327
9395
  { name: "file", description: "JSON file with fields to update", required: true }
9328
9396
  ],
9329
9397
  run: async (c3, a2) => {
9330
- const existing = unwrapData2(await c3.getImportList(a2.id));
9331
- return c3.updateImportList(a2.id, { ...existing, ...readJsonInput2(a2.file) });
9398
+ const existing = unwrapData(await c3.getImportList(a2.id));
9399
+ return c3.updateImportList(a2.id, { ...existing, ...readJsonInput(a2.file) });
9332
9400
  }
9333
9401
  },
9334
9402
  {
@@ -11836,38 +11904,12 @@ var exports_lidarr3 = {};
11836
11904
  __export(exports_lidarr3, {
11837
11905
  lidarr: () => lidarr
11838
11906
  });
11839
- import { readFileSync as readFileSync4 } from "node:fs";
11840
- function unwrapData3(result) {
11841
- return result?.data ?? result;
11842
- }
11843
- function formatAlbumListItem(album) {
11844
- return {
11845
- ...album,
11846
- artistName: album?.artistName ?? album?.artist?.artistName ?? "—"
11847
- };
11848
- }
11849
- function formatQueueListItem(item) {
11850
- return {
11851
- ...item,
11852
- artistName: item?.artistName ?? item?.artist?.artistName ?? "—"
11853
- };
11854
- }
11855
- function formatHistoryListItem(item) {
11856
- return {
11857
- ...item,
11858
- artistName: item?.artistName ?? item?.artist?.artistName ?? "—"
11859
- };
11860
- }
11861
- function formatBlocklistItem(item) {
11907
+ function withArtistName(item) {
11862
11908
  return {
11863
11909
  ...item,
11864
11910
  artistName: item?.artistName ?? item?.artist?.artistName ?? "—"
11865
11911
  };
11866
11912
  }
11867
- function readJsonInput3(filePath) {
11868
- const raw = filePath === "-" ? readFileSync4(0, "utf-8") : readFileSync4(filePath, "utf-8");
11869
- return JSON.parse(raw);
11870
- }
11871
11913
  var resources3, lidarr;
11872
11914
  var init_lidarr3 = __esm(() => {
11873
11915
  init_lidarr2();
@@ -11903,7 +11945,7 @@ var init_lidarr3 = __esm(() => {
11903
11945
  description: "Search and add an artist",
11904
11946
  args: [{ name: "term", description: "Search term", required: true }],
11905
11947
  run: async (c3, a2) => {
11906
- const results = unwrapData3(await c3.searchArtists(a2.term));
11948
+ const results = unwrapData(await c3.searchArtists(a2.term));
11907
11949
  if (!Array.isArray(results) || results.length === 0) {
11908
11950
  throw new Error("No artists found.");
11909
11951
  }
@@ -11915,12 +11957,12 @@ var init_lidarr3 = __esm(() => {
11915
11957
  if (!artist) {
11916
11958
  throw new Error("Selected artist was not found in the search results.");
11917
11959
  }
11918
- const profiles = unwrapData3(await c3.getQualityProfiles());
11960
+ const profiles = unwrapData(await c3.getQualityProfiles());
11919
11961
  if (!Array.isArray(profiles) || profiles.length === 0) {
11920
11962
  throw new Error("No quality profiles found. Configure one in Lidarr first.");
11921
11963
  }
11922
11964
  const profileId = await promptSelect("Select quality profile:", profiles.map((profile) => ({ label: profile.name, value: String(profile.id) })));
11923
- const folders = unwrapData3(await c3.getRootFolders());
11965
+ const folders = unwrapData(await c3.getRootFolders());
11924
11966
  if (!Array.isArray(folders) || folders.length === 0) {
11925
11967
  throw new Error("No root folders found. Configure one in Lidarr first.");
11926
11968
  }
@@ -11947,7 +11989,7 @@ var init_lidarr3 = __esm(() => {
11947
11989
  { name: "tags", description: "Comma-separated tag IDs" }
11948
11990
  ],
11949
11991
  run: async (c3, a2) => {
11950
- const artist = unwrapData3(await c3.getArtist(a2.id));
11992
+ const artist = unwrapData(await c3.getArtist(a2.id));
11951
11993
  const updates = { ...artist };
11952
11994
  if (a2.monitored !== undefined)
11953
11995
  updates.monitored = a2.monitored === "true";
@@ -11992,8 +12034,8 @@ var init_lidarr3 = __esm(() => {
11992
12034
  description: "List all albums",
11993
12035
  columns: ["id", "artistName", "title", "monitored"],
11994
12036
  run: async (c3) => {
11995
- const albums = unwrapData3(await c3.getAlbums());
11996
- return albums.map(formatAlbumListItem);
12037
+ const albums = unwrapData(await c3.getAlbums());
12038
+ return albums.map(withArtistName);
11997
12039
  }
11998
12040
  },
11999
12041
  {
@@ -12001,8 +12043,8 @@ var init_lidarr3 = __esm(() => {
12001
12043
  description: "Get an album by ID",
12002
12044
  args: [{ name: "id", description: "Album ID", required: true, type: "number" }],
12003
12045
  run: async (c3, a2) => {
12004
- const album = unwrapData3(await c3.getAlbum(a2.id));
12005
- return formatAlbumListItem(album);
12046
+ const album = unwrapData(await c3.getAlbum(a2.id));
12047
+ return withArtistName(album);
12006
12048
  }
12007
12049
  },
12008
12050
  {
@@ -12012,15 +12054,15 @@ var init_lidarr3 = __esm(() => {
12012
12054
  columns: ["foreignAlbumId", "artistName", "title", "monitored"],
12013
12055
  idField: "foreignAlbumId",
12014
12056
  run: async (c3, a2) => {
12015
- const albums = unwrapData3(await c3.searchAlbums(a2.term));
12016
- return albums.map(formatAlbumListItem);
12057
+ const albums = unwrapData(await c3.searchAlbums(a2.term));
12058
+ return albums.map(withArtistName);
12017
12059
  }
12018
12060
  },
12019
12061
  {
12020
12062
  name: "add",
12021
12063
  description: "Add an album from JSON file or stdin",
12022
12064
  args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
12023
- run: async (c3, a2) => c3.addAlbum(readJsonInput3(a2.file))
12065
+ run: async (c3, a2) => c3.addAlbum(readJsonInput(a2.file))
12024
12066
  },
12025
12067
  {
12026
12068
  name: "edit",
@@ -12030,8 +12072,8 @@ var init_lidarr3 = __esm(() => {
12030
12072
  { name: "file", description: "JSON file with fields to update", required: true }
12031
12073
  ],
12032
12074
  run: async (c3, a2) => {
12033
- const existing = unwrapData3(await c3.getAlbum(a2.id));
12034
- return c3.updateAlbum(a2.id, { ...existing, ...readJsonInput3(a2.file) });
12075
+ const existing = unwrapData(await c3.getAlbum(a2.id));
12076
+ return c3.updateAlbum(a2.id, { ...existing, ...readJsonInput(a2.file) });
12035
12077
  }
12036
12078
  },
12037
12079
  {
@@ -12106,7 +12148,7 @@ var init_lidarr3 = __esm(() => {
12106
12148
  const tagResult = await c3.getTag(a2.id);
12107
12149
  if (tagResult?.error)
12108
12150
  return tagResult;
12109
- const tag = unwrapData3(tagResult);
12151
+ const tag = unwrapData(tagResult);
12110
12152
  const deleteResult = await c3.deleteTag(a2.id);
12111
12153
  if (deleteResult?.error)
12112
12154
  return deleteResult;
@@ -12130,8 +12172,8 @@ var init_lidarr3 = __esm(() => {
12130
12172
  description: "List queue items",
12131
12173
  columns: ["id", "artistName", "title", "status", "sizeleft", "timeleft"],
12132
12174
  run: async (c3) => {
12133
- const items = unwrapData3(await c3.getQueue());
12134
- return items.map(formatQueueListItem);
12175
+ const items = unwrapData(await c3.getQueue());
12176
+ return items.map(withArtistName);
12135
12177
  }
12136
12178
  },
12137
12179
  {
@@ -12175,9 +12217,9 @@ var init_lidarr3 = __esm(() => {
12175
12217
  ],
12176
12218
  columns: ["id", "artistName", "sourceTitle", "eventType", "date"],
12177
12219
  run: async (c3, a2) => {
12178
- const items = a2.since ? unwrapData3(await c3.getHistorySince(a2.since)) : unwrapData3(await c3.getHistory());
12220
+ const items = a2.since ? unwrapData(await c3.getHistorySince(a2.since)) : unwrapData(await c3.getHistory());
12179
12221
  const filtered = a2.until ? items.filter((item) => new Date(item.date) <= new Date(a2.until)) : items;
12180
- return filtered.map(formatHistoryListItem);
12222
+ return filtered.map(withArtistName);
12181
12223
  }
12182
12224
  }
12183
12225
  ]
@@ -12196,8 +12238,8 @@ var init_lidarr3 = __esm(() => {
12196
12238
  ],
12197
12239
  columns: ["id", "artistName", "title", "releaseDate"],
12198
12240
  run: async (c3, a2) => {
12199
- const albums = unwrapData3(await c3.getCalendar(a2.start, a2.end, a2.unmonitored));
12200
- return albums.map(formatAlbumListItem);
12241
+ const albums = unwrapData(await c3.getCalendar(a2.start, a2.end, a2.unmonitored));
12242
+ return albums.map(withArtistName);
12201
12243
  }
12202
12244
  }
12203
12245
  ]
@@ -12222,7 +12264,7 @@ var init_lidarr3 = __esm(() => {
12222
12264
  name: "add",
12223
12265
  description: "Add a notification from JSON file or stdin",
12224
12266
  args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
12225
- run: async (c3, a2) => c3.addNotification(readJsonInput3(a2.file))
12267
+ run: async (c3, a2) => c3.addNotification(readJsonInput(a2.file))
12226
12268
  },
12227
12269
  {
12228
12270
  name: "edit",
@@ -12232,8 +12274,8 @@ var init_lidarr3 = __esm(() => {
12232
12274
  { name: "file", description: "JSON file with fields to update", required: true }
12233
12275
  ],
12234
12276
  run: async (c3, a2) => {
12235
- const existing = unwrapData3(await c3.getNotification(a2.id));
12236
- return c3.updateNotification(a2.id, { ...existing, ...readJsonInput3(a2.file) });
12277
+ const existing = unwrapData(await c3.getNotification(a2.id));
12278
+ return c3.updateNotification(a2.id, { ...existing, ...readJsonInput(a2.file) });
12237
12279
  }
12238
12280
  },
12239
12281
  {
@@ -12270,7 +12312,7 @@ var init_lidarr3 = __esm(() => {
12270
12312
  name: "add",
12271
12313
  description: "Add a download client from JSON file or stdin",
12272
12314
  args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
12273
- run: async (c3, a2) => c3.addDownloadClient(readJsonInput3(a2.file))
12315
+ run: async (c3, a2) => c3.addDownloadClient(readJsonInput(a2.file))
12274
12316
  },
12275
12317
  {
12276
12318
  name: "edit",
@@ -12280,8 +12322,8 @@ var init_lidarr3 = __esm(() => {
12280
12322
  { name: "file", description: "JSON file with fields to update", required: true }
12281
12323
  ],
12282
12324
  run: async (c3, a2) => {
12283
- const existing = unwrapData3(await c3.getDownloadClient(a2.id));
12284
- return c3.updateDownloadClient(a2.id, { ...existing, ...readJsonInput3(a2.file) });
12325
+ const existing = unwrapData(await c3.getDownloadClient(a2.id));
12326
+ return c3.updateDownloadClient(a2.id, { ...existing, ...readJsonInput(a2.file) });
12285
12327
  }
12286
12328
  },
12287
12329
  {
@@ -12307,8 +12349,8 @@ var init_lidarr3 = __esm(() => {
12307
12349
  description: "List blocked releases",
12308
12350
  columns: ["id", "artistName", "sourceTitle", "date"],
12309
12351
  run: async (c3) => {
12310
- const items = unwrapData3(await c3.getBlocklist());
12311
- return items.map(formatBlocklistItem);
12352
+ const items = unwrapData(await c3.getBlocklist());
12353
+ return items.map(withArtistName);
12312
12354
  }
12313
12355
  },
12314
12356
  {
@@ -12329,8 +12371,8 @@ var init_lidarr3 = __esm(() => {
12329
12371
  description: "List albums with missing tracks",
12330
12372
  columns: ["id", "artistName", "title", "releaseDate"],
12331
12373
  run: async (c3) => {
12332
- const albums = unwrapData3(await c3.getWantedMissing());
12333
- return albums.map(formatAlbumListItem);
12374
+ const albums = unwrapData(await c3.getWantedMissing());
12375
+ return albums.map(withArtistName);
12334
12376
  }
12335
12377
  },
12336
12378
  {
@@ -12338,8 +12380,8 @@ var init_lidarr3 = __esm(() => {
12338
12380
  description: "List albums below quality cutoff",
12339
12381
  columns: ["id", "artistName", "title", "releaseDate"],
12340
12382
  run: async (c3) => {
12341
- const albums = unwrapData3(await c3.getWantedCutoff());
12342
- return albums.map(formatAlbumListItem);
12383
+ const albums = unwrapData(await c3.getWantedCutoff());
12384
+ return albums.map(withArtistName);
12343
12385
  }
12344
12386
  }
12345
12387
  ]
@@ -14871,38 +14913,12 @@ var exports_readarr3 = {};
14871
14913
  __export(exports_readarr3, {
14872
14914
  readarr: () => readarr
14873
14915
  });
14874
- import { readFileSync as readFileSync5 } from "node:fs";
14875
- function unwrapData4(result) {
14876
- return result?.data ?? result;
14877
- }
14878
- function formatBookListItem(book) {
14879
- return {
14880
- ...book,
14881
- authorName: book?.authorName ?? book?.authorTitle ?? book?.author?.authorName ?? "—"
14882
- };
14883
- }
14884
- function formatQueueListItem2(item) {
14885
- return {
14886
- ...item,
14887
- authorName: item?.authorName ?? item?.authorTitle ?? item?.author?.authorName ?? "—"
14888
- };
14889
- }
14890
- function formatHistoryListItem2(item) {
14916
+ function withAuthorName(item) {
14891
14917
  return {
14892
14918
  ...item,
14893
14919
  authorName: item?.authorName ?? item?.authorTitle ?? item?.author?.authorName ?? "—"
14894
14920
  };
14895
14921
  }
14896
- function formatBlocklistItem2(item) {
14897
- return {
14898
- ...item,
14899
- authorName: item?.authorName ?? item?.authorTitle ?? item?.author?.authorName ?? "—"
14900
- };
14901
- }
14902
- function readJsonInput4(filePath) {
14903
- const raw = filePath === "-" ? readFileSync5(0, "utf-8") : readFileSync5(filePath, "utf-8");
14904
- return JSON.parse(raw);
14905
- }
14906
14922
  var resources4, readarr;
14907
14923
  var init_readarr3 = __esm(() => {
14908
14924
  init_readarr2();
@@ -14938,7 +14954,7 @@ var init_readarr3 = __esm(() => {
14938
14954
  description: "Search and add an author",
14939
14955
  args: [{ name: "term", description: "Search term", required: true }],
14940
14956
  run: async (c3, a2) => {
14941
- const results = unwrapData4(await c3.searchAuthors(a2.term));
14957
+ const results = unwrapData(await c3.searchAuthors(a2.term));
14942
14958
  if (!Array.isArray(results) || results.length === 0) {
14943
14959
  throw new Error("No authors found.");
14944
14960
  }
@@ -14950,12 +14966,12 @@ var init_readarr3 = __esm(() => {
14950
14966
  if (!author) {
14951
14967
  throw new Error("Selected author was not found in the search results.");
14952
14968
  }
14953
- const profiles = unwrapData4(await c3.getQualityProfiles());
14969
+ const profiles = unwrapData(await c3.getQualityProfiles());
14954
14970
  if (!Array.isArray(profiles) || profiles.length === 0) {
14955
14971
  throw new Error("No quality profiles found. Configure one in Readarr first.");
14956
14972
  }
14957
14973
  const profileId = await promptSelect("Select quality profile:", profiles.map((profile) => ({ label: profile.name, value: String(profile.id) })));
14958
- const folders = unwrapData4(await c3.getRootFolders());
14974
+ const folders = unwrapData(await c3.getRootFolders());
14959
14975
  if (!Array.isArray(folders) || folders.length === 0) {
14960
14976
  throw new Error("No root folders found. Configure one in Readarr first.");
14961
14977
  }
@@ -14982,7 +14998,7 @@ var init_readarr3 = __esm(() => {
14982
14998
  { name: "tags", description: "Comma-separated tag IDs" }
14983
14999
  ],
14984
15000
  run: async (c3, a2) => {
14985
- const author = unwrapData4(await c3.getAuthor(a2.id));
15001
+ const author = unwrapData(await c3.getAuthor(a2.id));
14986
15002
  const updates = { ...author };
14987
15003
  if (a2.monitored !== undefined)
14988
15004
  updates.monitored = a2.monitored === "true";
@@ -15027,8 +15043,8 @@ var init_readarr3 = __esm(() => {
15027
15043
  description: "List all books",
15028
15044
  columns: ["id", "authorName", "title", "monitored"],
15029
15045
  run: async (c3) => {
15030
- const books = unwrapData4(await c3.getBooks());
15031
- return books.map(formatBookListItem);
15046
+ const books = unwrapData(await c3.getBooks());
15047
+ return books.map(withAuthorName);
15032
15048
  }
15033
15049
  },
15034
15050
  {
@@ -15036,8 +15052,8 @@ var init_readarr3 = __esm(() => {
15036
15052
  description: "Get a book by ID",
15037
15053
  args: [{ name: "id", description: "Book ID", required: true, type: "number" }],
15038
15054
  run: async (c3, a2) => {
15039
- const book = unwrapData4(await c3.getBook(a2.id));
15040
- return formatBookListItem(book);
15055
+ const book = unwrapData(await c3.getBook(a2.id));
15056
+ return withAuthorName(book);
15041
15057
  }
15042
15058
  },
15043
15059
  {
@@ -15047,15 +15063,15 @@ var init_readarr3 = __esm(() => {
15047
15063
  columns: ["foreignBookId", "authorName", "title", "monitored"],
15048
15064
  idField: "foreignBookId",
15049
15065
  run: async (c3, a2) => {
15050
- const books = unwrapData4(await c3.searchBooks(a2.term));
15051
- return books.map(formatBookListItem);
15066
+ const books = unwrapData(await c3.searchBooks(a2.term));
15067
+ return books.map(withAuthorName);
15052
15068
  }
15053
15069
  },
15054
15070
  {
15055
15071
  name: "add",
15056
15072
  description: "Add a book from JSON file or stdin",
15057
15073
  args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
15058
- run: async (c3, a2) => c3.addBook(readJsonInput4(a2.file))
15074
+ run: async (c3, a2) => c3.addBook(readJsonInput(a2.file))
15059
15075
  },
15060
15076
  {
15061
15077
  name: "edit",
@@ -15065,8 +15081,8 @@ var init_readarr3 = __esm(() => {
15065
15081
  { name: "file", description: "JSON file with fields to update", required: true }
15066
15082
  ],
15067
15083
  run: async (c3, a2) => {
15068
- const existing = unwrapData4(await c3.getBook(a2.id));
15069
- return c3.updateBook(a2.id, { ...existing, ...readJsonInput4(a2.file) });
15084
+ const existing = unwrapData(await c3.getBook(a2.id));
15085
+ return c3.updateBook(a2.id, { ...existing, ...readJsonInput(a2.file) });
15070
15086
  }
15071
15087
  },
15072
15088
  {
@@ -15141,7 +15157,7 @@ var init_readarr3 = __esm(() => {
15141
15157
  const tagResult = await c3.getTag(a2.id);
15142
15158
  if (tagResult?.error)
15143
15159
  return tagResult;
15144
- const tag = unwrapData4(tagResult);
15160
+ const tag = unwrapData(tagResult);
15145
15161
  const deleteResult = await c3.deleteTag(a2.id);
15146
15162
  if (deleteResult?.error)
15147
15163
  return deleteResult;
@@ -15165,8 +15181,8 @@ var init_readarr3 = __esm(() => {
15165
15181
  description: "List queue items",
15166
15182
  columns: ["id", "authorName", "title", "status", "sizeleft", "timeleft"],
15167
15183
  run: async (c3) => {
15168
- const items = unwrapData4(await c3.getQueue());
15169
- return items.map(formatQueueListItem2);
15184
+ const items = unwrapData(await c3.getQueue());
15185
+ return items.map(withAuthorName);
15170
15186
  }
15171
15187
  },
15172
15188
  {
@@ -15210,9 +15226,9 @@ var init_readarr3 = __esm(() => {
15210
15226
  ],
15211
15227
  columns: ["id", "authorName", "sourceTitle", "eventType", "date"],
15212
15228
  run: async (c3, a2) => {
15213
- const items = a2.since ? unwrapData4(await c3.getHistorySince(a2.since)) : unwrapData4(await c3.getHistory());
15229
+ const items = a2.since ? unwrapData(await c3.getHistorySince(a2.since)) : unwrapData(await c3.getHistory());
15214
15230
  const filtered = a2.until ? items.filter((item) => new Date(item.date) <= new Date(a2.until)) : items;
15215
- return filtered.map(formatHistoryListItem2);
15231
+ return filtered.map(withAuthorName);
15216
15232
  }
15217
15233
  }
15218
15234
  ]
@@ -15231,8 +15247,8 @@ var init_readarr3 = __esm(() => {
15231
15247
  ],
15232
15248
  columns: ["id", "authorName", "title", "releaseDate"],
15233
15249
  run: async (c3, a2) => {
15234
- const books = unwrapData4(await c3.getCalendar(a2.start, a2.end, a2.unmonitored));
15235
- return books.map(formatBookListItem);
15250
+ const books = unwrapData(await c3.getCalendar(a2.start, a2.end, a2.unmonitored));
15251
+ return books.map(withAuthorName);
15236
15252
  }
15237
15253
  }
15238
15254
  ]
@@ -15257,7 +15273,7 @@ var init_readarr3 = __esm(() => {
15257
15273
  name: "add",
15258
15274
  description: "Add a notification from JSON file or stdin",
15259
15275
  args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
15260
- run: async (c3, a2) => c3.addNotification(readJsonInput4(a2.file))
15276
+ run: async (c3, a2) => c3.addNotification(readJsonInput(a2.file))
15261
15277
  },
15262
15278
  {
15263
15279
  name: "edit",
@@ -15267,8 +15283,8 @@ var init_readarr3 = __esm(() => {
15267
15283
  { name: "file", description: "JSON file with fields to update", required: true }
15268
15284
  ],
15269
15285
  run: async (c3, a2) => {
15270
- const existing = unwrapData4(await c3.getNotification(a2.id));
15271
- return c3.updateNotification(a2.id, { ...existing, ...readJsonInput4(a2.file) });
15286
+ const existing = unwrapData(await c3.getNotification(a2.id));
15287
+ return c3.updateNotification(a2.id, { ...existing, ...readJsonInput(a2.file) });
15272
15288
  }
15273
15289
  },
15274
15290
  {
@@ -15305,7 +15321,7 @@ var init_readarr3 = __esm(() => {
15305
15321
  name: "add",
15306
15322
  description: "Add a download client from JSON file or stdin",
15307
15323
  args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
15308
- run: async (c3, a2) => c3.addDownloadClient(readJsonInput4(a2.file))
15324
+ run: async (c3, a2) => c3.addDownloadClient(readJsonInput(a2.file))
15309
15325
  },
15310
15326
  {
15311
15327
  name: "edit",
@@ -15315,8 +15331,8 @@ var init_readarr3 = __esm(() => {
15315
15331
  { name: "file", description: "JSON file with fields to update", required: true }
15316
15332
  ],
15317
15333
  run: async (c3, a2) => {
15318
- const existing = unwrapData4(await c3.getDownloadClient(a2.id));
15319
- return c3.updateDownloadClient(a2.id, { ...existing, ...readJsonInput4(a2.file) });
15334
+ const existing = unwrapData(await c3.getDownloadClient(a2.id));
15335
+ return c3.updateDownloadClient(a2.id, { ...existing, ...readJsonInput(a2.file) });
15320
15336
  }
15321
15337
  },
15322
15338
  {
@@ -15342,8 +15358,8 @@ var init_readarr3 = __esm(() => {
15342
15358
  description: "List blocked releases",
15343
15359
  columns: ["id", "authorName", "sourceTitle", "date"],
15344
15360
  run: async (c3) => {
15345
- const items = unwrapData4(await c3.getBlocklist());
15346
- return items.map(formatBlocklistItem2);
15361
+ const items = unwrapData(await c3.getBlocklist());
15362
+ return items.map(withAuthorName);
15347
15363
  }
15348
15364
  },
15349
15365
  {
@@ -15364,8 +15380,8 @@ var init_readarr3 = __esm(() => {
15364
15380
  description: "List books with missing files",
15365
15381
  columns: ["id", "authorName", "title", "releaseDate"],
15366
15382
  run: async (c3) => {
15367
- const books = unwrapData4(await c3.getWantedMissing());
15368
- return books.map(formatBookListItem);
15383
+ const books = unwrapData(await c3.getWantedMissing());
15384
+ return books.map(withAuthorName);
15369
15385
  }
15370
15386
  },
15371
15387
  {
@@ -15373,8 +15389,8 @@ var init_readarr3 = __esm(() => {
15373
15389
  description: "List books below quality cutoff",
15374
15390
  columns: ["id", "authorName", "title", "releaseDate"],
15375
15391
  run: async (c3) => {
15376
- const books = unwrapData4(await c3.getWantedCutoff());
15377
- return books.map(formatBookListItem);
15392
+ const books = unwrapData(await c3.getWantedCutoff());
15393
+ return books.map(withAuthorName);
15378
15394
  }
15379
15395
  }
15380
15396
  ]
@@ -17070,14 +17086,6 @@ var exports_prowlarr3 = {};
17070
17086
  __export(exports_prowlarr3, {
17071
17087
  prowlarr: () => prowlarr
17072
17088
  });
17073
- import { readFileSync as readFileSync6 } from "node:fs";
17074
- function unwrapData5(result) {
17075
- return result?.data ?? result;
17076
- }
17077
- function readJsonInput5(filePath) {
17078
- const raw = filePath === "-" ? readFileSync6(0, "utf-8") : readFileSync6(filePath, "utf-8");
17079
- return JSON.parse(raw);
17080
- }
17081
17089
  async function runIndexerTest(client6, indexer) {
17082
17090
  const result = await client6.testIndexer(indexer);
17083
17091
  if (result?.error) {
@@ -17091,7 +17099,7 @@ async function runIndexerTest(client6, indexer) {
17091
17099
  message: error?.title ?? error?.message ?? `API error (${status ?? "unknown"})`
17092
17100
  };
17093
17101
  }
17094
- const data = unwrapData5(result);
17102
+ const data = unwrapData(result);
17095
17103
  return {
17096
17104
  id: indexer?.id,
17097
17105
  name: indexer?.name ?? "Unknown indexer",
@@ -17144,7 +17152,7 @@ var init_prowlarr3 = __esm(() => {
17144
17152
  description: "Add an indexer from JSON file or stdin",
17145
17153
  args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
17146
17154
  run: async (c3, a2) => {
17147
- const body = readJsonInput5(a2.file);
17155
+ const body = readJsonInput(a2.file);
17148
17156
  return c3.addIndexer(body);
17149
17157
  }
17150
17158
  },
@@ -17156,8 +17164,8 @@ var init_prowlarr3 = __esm(() => {
17156
17164
  { name: "file", description: "JSON file with fields to update", required: true }
17157
17165
  ],
17158
17166
  run: async (c3, a2) => {
17159
- const existing = unwrapData5(await c3.getIndexer(a2.id));
17160
- const updates = readJsonInput5(a2.file);
17167
+ const existing = unwrapData(await c3.getIndexer(a2.id));
17168
+ const updates = readJsonInput(a2.file);
17161
17169
  return c3.updateIndexer(a2.id, { ...existing, ...updates });
17162
17170
  }
17163
17171
  },
@@ -17174,7 +17182,7 @@ var init_prowlarr3 = __esm(() => {
17174
17182
  args: [{ name: "id", description: "Indexer ID", type: "number" }],
17175
17183
  columns: ["id", "name", "status", "message"],
17176
17184
  run: async (c3, a2) => {
17177
- const indexers = a2.id ? [unwrapData5(await c3.getIndexer(a2.id))] : unwrapData5(await c3.getIndexers());
17185
+ const indexers = a2.id ? [unwrapData(await c3.getIndexer(a2.id))] : unwrapData(await c3.getIndexers());
17178
17186
  const results = [];
17179
17187
  for (const indexer of indexers) {
17180
17188
  results.push(await runIndexerTest(c3, indexer));
@@ -17221,7 +17229,7 @@ var init_prowlarr3 = __esm(() => {
17221
17229
  description: "Add an application from JSON file or stdin",
17222
17230
  args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
17223
17231
  run: async (c3, a2) => {
17224
- const body = readJsonInput5(a2.file);
17232
+ const body = readJsonInput(a2.file);
17225
17233
  return c3.addApplication(body);
17226
17234
  }
17227
17235
  },
@@ -17233,8 +17241,8 @@ var init_prowlarr3 = __esm(() => {
17233
17241
  { name: "file", description: "JSON file with fields to update", required: true }
17234
17242
  ],
17235
17243
  run: async (c3, a2) => {
17236
- const existing = unwrapData5(await c3.getApplication(a2.id));
17237
- const updates = readJsonInput5(a2.file);
17244
+ const existing = unwrapData(await c3.getApplication(a2.id));
17245
+ const updates = readJsonInput(a2.file);
17238
17246
  return c3.updateApplication(a2.id, { ...existing, ...updates });
17239
17247
  }
17240
17248
  },
@@ -17302,7 +17310,7 @@ var init_prowlarr3 = __esm(() => {
17302
17310
  "averageResponseTime"
17303
17311
  ],
17304
17312
  run: async (c3) => {
17305
- const result = unwrapData5(await c3.getIndexerStats());
17313
+ const result = unwrapData(await c3.getIndexerStats());
17306
17314
  return result?.indexers ?? result;
17307
17315
  }
17308
17316
  }
@@ -17328,7 +17336,7 @@ var init_prowlarr3 = __esm(() => {
17328
17336
  name: "add",
17329
17337
  description: "Add a notification from JSON file or stdin",
17330
17338
  args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
17331
- run: async (c3, a2) => c3.addNotification(readJsonInput5(a2.file))
17339
+ run: async (c3, a2) => c3.addNotification(readJsonInput(a2.file))
17332
17340
  },
17333
17341
  {
17334
17342
  name: "edit",
@@ -17338,8 +17346,8 @@ var init_prowlarr3 = __esm(() => {
17338
17346
  { name: "file", description: "JSON file with fields to update", required: true }
17339
17347
  ],
17340
17348
  run: async (c3, a2) => {
17341
- const existing = unwrapData5(await c3.getNotification(a2.id));
17342
- return c3.updateNotification(a2.id, { ...existing, ...readJsonInput5(a2.file) });
17349
+ const existing = unwrapData(await c3.getNotification(a2.id));
17350
+ return c3.updateNotification(a2.id, { ...existing, ...readJsonInput(a2.file) });
17343
17351
  }
17344
17352
  },
17345
17353
  {
@@ -17376,7 +17384,7 @@ var init_prowlarr3 = __esm(() => {
17376
17384
  name: "add",
17377
17385
  description: "Add a download client from JSON file or stdin",
17378
17386
  args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
17379
- run: async (c3, a2) => c3.addDownloadClient(readJsonInput5(a2.file))
17387
+ run: async (c3, a2) => c3.addDownloadClient(readJsonInput(a2.file))
17380
17388
  },
17381
17389
  {
17382
17390
  name: "edit",
@@ -17386,8 +17394,8 @@ var init_prowlarr3 = __esm(() => {
17386
17394
  { name: "file", description: "JSON file with fields to update", required: true }
17387
17395
  ],
17388
17396
  run: async (c3, a2) => {
17389
- const existing = unwrapData5(await c3.getDownloadClient(a2.id));
17390
- return c3.updateDownloadClient(a2.id, { ...existing, ...readJsonInput5(a2.file) });
17397
+ const existing = unwrapData(await c3.getDownloadClient(a2.id));
17398
+ return c3.updateDownloadClient(a2.id, { ...existing, ...readJsonInput(a2.file) });
17391
17399
  }
17392
17400
  },
17393
17401
  {
@@ -21316,9 +21324,10 @@ var init_doctor = __esm(() => {
21316
21324
  consola.info(`Checking connections...
21317
21325
  `);
21318
21326
  }
21327
+ let hasMultiInstance = false;
21319
21328
  for (const service of SERVICES) {
21320
- const svcConfig = getServiceConfig(service);
21321
- if (!svcConfig) {
21329
+ const instances = getServiceInstances(service);
21330
+ if (instances.length === 0) {
21322
21331
  results.push({
21323
21332
  service,
21324
21333
  configured: false,
@@ -21326,56 +21335,62 @@ var init_doctor = __esm(() => {
21326
21335
  });
21327
21336
  continue;
21328
21337
  }
21329
- hasAny = true;
21330
- try {
21331
- const factory = clientFactories[service];
21332
- if (!factory) {
21338
+ if (instances.length > 1)
21339
+ hasMultiInstance = true;
21340
+ for (const inst of instances) {
21341
+ const svcConfig = getServiceConfig(service, inst.name);
21342
+ if (!svcConfig) {
21343
+ results.push({
21344
+ service,
21345
+ ...inst.name ? { instance: inst.name } : {},
21346
+ configured: false,
21347
+ status: "not configured"
21348
+ });
21349
+ continue;
21350
+ }
21351
+ hasAny = true;
21352
+ try {
21353
+ const client9 = clientFactories[service](svcConfig);
21354
+ const status = await client9.getSystemStatus();
21355
+ if (status?.error !== undefined) {
21356
+ const err = status.error;
21357
+ const code = err?.cause?.code ?? err?.code;
21358
+ const cause = code ? { code } : undefined;
21359
+ const message = err?.cause?.message ?? err?.message ?? err?.code ?? "Unknown API error";
21360
+ throw Object.assign(new Error(message), cause ? { cause } : {});
21361
+ }
21362
+ const version = extractVersion(service, status) ?? "?";
21363
+ if (version === "?") {
21364
+ throw new Error("Unexpected response payload");
21365
+ }
21333
21366
  results.push({
21334
21367
  service,
21368
+ ...inst.name ? { instance: inst.name } : {},
21369
+ configured: true,
21370
+ status: "ok",
21371
+ version: String(version),
21372
+ baseUrl: svcConfig.baseUrl
21373
+ });
21374
+ } catch (error) {
21375
+ results.push({
21376
+ service,
21377
+ ...inst.name ? { instance: inst.name } : {},
21335
21378
  configured: true,
21336
21379
  status: "fail",
21337
21380
  baseUrl: svcConfig.baseUrl,
21338
- error: "No client factory available"
21381
+ error: classifyError(error)
21339
21382
  });
21340
- continue;
21341
- }
21342
- const client9 = factory(svcConfig);
21343
- const status = await client9.getSystemStatus();
21344
- if (status?.error !== undefined) {
21345
- const err = status.error;
21346
- const code = err?.cause?.code ?? err?.code;
21347
- const cause = code ? { code } : undefined;
21348
- const message = err?.cause?.message ?? err?.message ?? err?.code ?? "Unknown API error";
21349
- throw Object.assign(new Error(message), cause ? { cause } : {});
21350
21383
  }
21351
- const version = extractVersion(service, status) ?? "?";
21352
- if (version === "?") {
21353
- throw new Error("Unexpected response payload");
21354
- }
21355
- results.push({
21356
- service,
21357
- configured: true,
21358
- status: "ok",
21359
- version: String(version),
21360
- baseUrl: svcConfig.baseUrl
21361
- });
21362
- } catch (error) {
21363
- results.push({
21364
- service,
21365
- configured: true,
21366
- status: "fail",
21367
- baseUrl: svcConfig.baseUrl,
21368
- error: classifyError(error)
21369
- });
21370
21384
  }
21371
21385
  }
21372
21386
  const hadFailure = !hasAny || results.some((r3) => r3.status === "fail");
21373
21387
  if (!hasAny && format === "table") {
21374
21388
  consola.warn("\nNo services configured. Run `tsarr config init` to set up.");
21375
21389
  }
21390
+ const columns = hasMultiInstance ? ["service", "instance", "status", "configured", "version", "baseUrl", "error"] : ["service", "status", "configured", "version", "baseUrl", "error"];
21376
21391
  formatOutput(results, {
21377
21392
  format,
21378
- columns: ["service", "status", "configured", "version", "baseUrl", "error"],
21393
+ columns,
21379
21394
  idField: "service",
21380
21395
  select: args.select
21381
21396
  });
@@ -21391,6 +21406,61 @@ var exports_config = {};
21391
21406
  __export(exports_config, {
21392
21407
  config: () => config
21393
21408
  });
21409
+ async function configureInstance(service, instanceName) {
21410
+ const baseUrl = await promptIfMissing(undefined, `${service}${instanceName ? ` (${instanceName})` : ""} base URL (e.g. http://localhost:${DEFAULT_PORTS[service]})`);
21411
+ if (service === "qbittorrent") {
21412
+ const username = await promptIfMissing(undefined, `${service} username`);
21413
+ const password = await promptIfMissing(undefined, `${service} password`);
21414
+ const cfg2 = { baseUrl, username, password };
21415
+ if (instanceName)
21416
+ cfg2.name = instanceName;
21417
+ return cfg2;
21418
+ }
21419
+ const apiKey = await promptIfMissing(undefined, `${service} API key`);
21420
+ const cfg = { baseUrl, apiKey };
21421
+ if (instanceName)
21422
+ cfg.name = instanceName;
21423
+ return cfg;
21424
+ }
21425
+ async function testConnection(service, serviceConfig) {
21426
+ try {
21427
+ if (service === "qbittorrent") {
21428
+ const { QBittorrentClient: QBittorrentClient2 } = await Promise.resolve().then(() => (init_qbittorrent2(), exports_qbittorrent));
21429
+ const client9 = new QBittorrentClient2({
21430
+ baseUrl: serviceConfig.baseUrl,
21431
+ username: serviceConfig.username,
21432
+ password: serviceConfig.password
21433
+ });
21434
+ const status = await client9.getSystemStatus();
21435
+ consola.success(`Connected to ${service}${serviceConfig.name ? ` (${serviceConfig.name})` : ""} v${status.version}`);
21436
+ } else {
21437
+ const { RadarrClient: RadarrClient2 } = await Promise.resolve().then(() => (init_radarr2(), exports_radarr));
21438
+ const { SonarrClient: SonarrClient2 } = await Promise.resolve().then(() => (init_sonarr2(), exports_sonarr));
21439
+ const { LidarrClient: LidarrClient2 } = await Promise.resolve().then(() => (init_lidarr2(), exports_lidarr));
21440
+ const { ReadarrClient: ReadarrClient2 } = await Promise.resolve().then(() => (init_readarr2(), exports_readarr));
21441
+ const { ProwlarrClient: ProwlarrClient2 } = await Promise.resolve().then(() => (init_prowlarr2(), exports_prowlarr));
21442
+ const { BazarrClient: BazarrClient2 } = await Promise.resolve().then(() => (init_bazarr2(), exports_bazarr));
21443
+ const { SeerrClient: SeerrClient2 } = await Promise.resolve().then(() => (init_seerr2(), exports_seerr));
21444
+ const factories = {
21445
+ radarr: (c3) => new RadarrClient2(c3),
21446
+ sonarr: (c3) => new SonarrClient2(c3),
21447
+ lidarr: (c3) => new LidarrClient2(c3),
21448
+ readarr: (c3) => new ReadarrClient2(c3),
21449
+ prowlarr: (c3) => new ProwlarrClient2(c3),
21450
+ bazarr: (c3) => new BazarrClient2(c3),
21451
+ seerr: (c3) => new SeerrClient2(c3)
21452
+ };
21453
+ const client9 = factories[service]?.(serviceConfig);
21454
+ if (client9) {
21455
+ const status = await client9.getSystemStatus();
21456
+ const version = status?.data?.version ?? status?.version ?? "?";
21457
+ consola.success(`Connected to ${service}${serviceConfig.name ? ` (${serviceConfig.name})` : ""} v${version}`);
21458
+ }
21459
+ }
21460
+ } catch {
21461
+ consola.warn(`Could not connect to ${service}${serviceConfig.name ? ` (${serviceConfig.name})` : ""} — config saved anyway.`);
21462
+ }
21463
+ }
21394
21464
  var DEFAULT_PORTS, configInit, configSet, configGet, configShow, config;
21395
21465
  var init_config2 = __esm(() => {
21396
21466
  init_dist();
@@ -21430,49 +21500,25 @@ var init_config2 = __esm(() => {
21430
21500
  const config = { services: {} };
21431
21501
  for (const service of selected) {
21432
21502
  console.log();
21433
- const baseUrl = await promptIfMissing(undefined, `${service} base URL (e.g. http://localhost:${DEFAULT_PORTS[service]})`);
21434
- if (service === "qbittorrent") {
21435
- const username = await promptIfMissing(undefined, `${service} username`);
21436
- const password = await promptIfMissing(undefined, `${service} password`);
21437
- config.services[service] = { baseUrl, username, password };
21438
- try {
21439
- const { QBittorrentClient: QBittorrentClient2 } = await Promise.resolve().then(() => (init_qbittorrent2(), exports_qbittorrent));
21440
- const client9 = new QBittorrentClient2({ baseUrl, username, password });
21441
- const status = await client9.getSystemStatus();
21442
- consola.success(`Connected to ${service} v${status.version}`);
21443
- } catch {
21444
- consola.warn(`Could not connect to ${service} — config saved anyway.`);
21445
- }
21446
- } else {
21447
- const apiKey = await promptIfMissing(undefined, `${service} API key`);
21448
- config.services[service] = { baseUrl, apiKey };
21449
- try {
21450
- const { RadarrClient: RadarrClient2 } = await Promise.resolve().then(() => (init_radarr2(), exports_radarr));
21451
- const { SonarrClient: SonarrClient2 } = await Promise.resolve().then(() => (init_sonarr2(), exports_sonarr));
21452
- const { LidarrClient: LidarrClient2 } = await Promise.resolve().then(() => (init_lidarr2(), exports_lidarr));
21453
- const { ReadarrClient: ReadarrClient2 } = await Promise.resolve().then(() => (init_readarr2(), exports_readarr));
21454
- const { ProwlarrClient: ProwlarrClient2 } = await Promise.resolve().then(() => (init_prowlarr2(), exports_prowlarr));
21455
- const { BazarrClient: BazarrClient2 } = await Promise.resolve().then(() => (init_bazarr2(), exports_bazarr));
21456
- const { SeerrClient: SeerrClient2 } = await Promise.resolve().then(() => (init_seerr2(), exports_seerr));
21457
- const factories = {
21458
- radarr: (c3) => new RadarrClient2(c3),
21459
- sonarr: (c3) => new SonarrClient2(c3),
21460
- lidarr: (c3) => new LidarrClient2(c3),
21461
- readarr: (c3) => new ReadarrClient2(c3),
21462
- prowlarr: (c3) => new ProwlarrClient2(c3),
21463
- bazarr: (c3) => new BazarrClient2(c3),
21464
- seerr: (c3) => new SeerrClient2(c3)
21465
- };
21466
- const client9 = factories[service]?.(config.services[service]);
21467
- if (client9) {
21468
- const status = await client9.getSystemStatus();
21469
- const version = status?.data?.version ?? status?.version ?? "?";
21470
- consola.success(`Connected to ${service} v${version}`);
21471
- }
21472
- } catch {
21473
- consola.warn(`Could not connect to ${service} — config saved anyway.`);
21503
+ const instances = [];
21504
+ const first = await configureInstance(service);
21505
+ await testConnection(service, first);
21506
+ instances.push(first);
21507
+ while (true) {
21508
+ const addMore = await promptConfirm(`Add another ${service} instance?`);
21509
+ if (!addMore)
21510
+ break;
21511
+ if (instances.length === 1 && !instances[0].name) {
21512
+ const firstName = await promptIfMissing(undefined, `Name for the existing ${service} instance (e.g. "main", "1080p")`);
21513
+ instances[0].name = firstName;
21474
21514
  }
21515
+ const newName = await promptIfMissing(undefined, `Name for the new ${service} instance (e.g. "4K")`);
21516
+ console.log();
21517
+ const newInstance = await configureInstance(service, newName);
21518
+ await testConnection(service, newInstance);
21519
+ instances.push(newInstance);
21475
21520
  }
21521
+ config.services[service] = instances;
21476
21522
  }
21477
21523
  const location = await promptSelect("Save config to:", [
21478
21524
  { label: `Global (${GLOBAL_CONFIG_PATH})`, value: "global" },
@@ -21535,11 +21581,15 @@ var init_config2 = __esm(() => {
21535
21581
  const config = loadConfig();
21536
21582
  const redacted = JSON.parse(JSON.stringify(config));
21537
21583
  if (redacted.services) {
21538
- for (const svc of Object.values(redacted.services)) {
21539
- if (svc?.apiKey)
21540
- svc.apiKey = "*****";
21541
- if (svc?.password)
21542
- svc.password = "*****";
21584
+ for (const instances of Object.values(redacted.services)) {
21585
+ if (Array.isArray(instances)) {
21586
+ for (const svc of instances) {
21587
+ if (svc?.apiKey)
21588
+ svc.apiKey = "*****";
21589
+ if (svc?.password)
21590
+ svc.password = "*****";
21591
+ }
21592
+ }
21543
21593
  }
21544
21594
  }
21545
21595
  console.log(JSON.stringify(redacted, null, 2));
@@ -21654,7 +21704,8 @@ ${actionAssoc}
21654
21704
  '--json[Output as JSON]' \\
21655
21705
  '--table[Output as table]' \\
21656
21706
  '--quiet[Output IDs only]' \\
21657
- '--yes[Skip confirmation prompts]'
21707
+ '--yes[Skip confirmation prompts]' \\
21708
+ '--instance[Instance name for multi-instance services]:instance:'
21658
21709
  ;;
21659
21710
  esac
21660
21711
  }
@@ -21691,7 +21742,9 @@ ${actionCompletions}
21691
21742
  complete -c tsarr -l json -d "Output as JSON"
21692
21743
  complete -c tsarr -l table -d "Output as table"
21693
21744
  complete -c tsarr -l quiet -s q -d "Output IDs only"
21694
- complete -c tsarr -l yes -s y -d "Skip confirmation prompts"`;
21745
+ complete -c tsarr -l yes -s y -d "Skip confirmation prompts"
21746
+ complete -c tsarr -l instance -s i -d "Instance name (multi-instance services)"
21747
+ `;
21695
21748
  }
21696
21749
  var SERVICE_COMMANDS, completions;
21697
21750
  var init_completions = __esm(() => {
@@ -21790,7 +21843,7 @@ init_dist();
21790
21843
  // package.json
21791
21844
  var package_default = {
21792
21845
  name: "tsarr",
21793
- version: "2.7.5",
21846
+ version: "2.8.0",
21794
21847
  author: "Robbe Verhelst",
21795
21848
  repository: {
21796
21849
  type: "git",
@@ -21802,7 +21855,7 @@ var package_default = {
21802
21855
  main: "dist/index.js",
21803
21856
  module: "dist/index.js",
21804
21857
  devDependencies: {
21805
- "@biomejs/biome": "2.4.11",
21858
+ "@biomejs/biome": "2.4.12",
21806
21859
  "@hey-api/openapi-ts": "^0.96.0",
21807
21860
  "@semantic-release/changelog": "^6.0.3",
21808
21861
  "@semantic-release/git": "^10.0.1",