tsarr 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -1,5 +1,4 @@
1
- #!/usr/bin/env bun
2
- // @bun
1
+ #!/usr/bin/env node
3
2
  var __defProp = Object.defineProperty;
4
3
  var __returnValue = (v) => v;
5
4
  function __exportSetter(name, newValue) {
@@ -88,7 +87,7 @@ var init_scule = __esm(() => {
88
87
  });
89
88
 
90
89
  // node_modules/citty/dist/index.mjs
91
- import { parseArgs as parseArgs$1 } from "util";
90
+ import { parseArgs as parseArgs$1 } from "node:util";
92
91
  function toArray(val) {
93
92
  if (Array.isArray(val))
94
93
  return val;
@@ -714,7 +713,7 @@ var separatorArrayExplode = (style) => {
714
713
  return "";
715
714
  }
716
715
  if (typeof value === "object") {
717
- throw new Error("Deeply-nested arrays/objects aren\u2019t supported. Provide your own `querySerializer()` to handle these.");
716
+ throw new Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");
718
717
  }
719
718
  return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;
720
719
  }, serializeObjectParam = ({
@@ -2794,6 +2793,10 @@ class RadarrClient {
2794
2793
  updateConfig(newConfig) {
2795
2794
  const updatedConfig = { ...this.clientConfig.config, ...newConfig };
2796
2795
  this.clientConfig = createServarrClient(updatedConfig);
2796
+ client.setConfig({
2797
+ baseUrl: this.clientConfig.getBaseUrl(),
2798
+ headers: this.clientConfig.getHeaders()
2799
+ });
2797
2800
  return this.clientConfig.config;
2798
2801
  }
2799
2802
  }
@@ -3203,8 +3206,8 @@ var init_core = __esm(() => {
3203
3206
  });
3204
3207
 
3205
3208
  // node_modules/consola/dist/shared/consola.DRwqZj3T.mjs
3206
- import { formatWithOptions } from "util";
3207
- import { sep } from "path";
3209
+ import { formatWithOptions } from "node:util";
3210
+ import { sep } from "node:path";
3208
3211
  function parseStack(stack, message) {
3209
3212
  const cwd = process.cwd() + sep;
3210
3213
  const lines = stack.split(`
@@ -3281,7 +3284,7 @@ var bracket = (x) => x ? `[${x}]` : "";
3281
3284
  var init_consola_DRwqZj3T = () => {};
3282
3285
 
3283
3286
  // node_modules/consola/dist/shared/consola.DXBYu-KD.mjs
3284
- import * as tty from "tty";
3287
+ import * as tty from "node:tty";
3285
3288
  function replaceClose(index, string, close, replace, head = string.slice(0, Math.max(0, index)) + replace, tail = string.slice(Math.max(0, index + close.length)), next = tail.indexOf(close)) {
3286
3289
  return head + (next < 0 ? tail : replaceClose(next, tail, close, replace));
3287
3290
  }
@@ -3421,68 +3424,68 @@ var init_consola_DXBYu_KD = __esm(() => {
3421
3424
  ].join("|");
3422
3425
  boxStylePresets = {
3423
3426
  solid: {
3424
- tl: "\u250C",
3425
- tr: "\u2510",
3426
- bl: "\u2514",
3427
- br: "\u2518",
3428
- h: "\u2500",
3429
- v: "\u2502"
3427
+ tl: "",
3428
+ tr: "",
3429
+ bl: "",
3430
+ br: "",
3431
+ h: "",
3432
+ v: ""
3430
3433
  },
3431
3434
  double: {
3432
- tl: "\u2554",
3433
- tr: "\u2557",
3434
- bl: "\u255A",
3435
- br: "\u255D",
3436
- h: "\u2550",
3437
- v: "\u2551"
3435
+ tl: "",
3436
+ tr: "",
3437
+ bl: "",
3438
+ br: "",
3439
+ h: "",
3440
+ v: ""
3438
3441
  },
3439
3442
  doubleSingle: {
3440
- tl: "\u2553",
3441
- tr: "\u2556",
3442
- bl: "\u2559",
3443
- br: "\u255C",
3444
- h: "\u2500",
3445
- v: "\u2551"
3443
+ tl: "",
3444
+ tr: "",
3445
+ bl: "",
3446
+ br: "",
3447
+ h: "",
3448
+ v: ""
3446
3449
  },
3447
3450
  doubleSingleRounded: {
3448
- tl: "\u256D",
3449
- tr: "\u256E",
3450
- bl: "\u2570",
3451
- br: "\u256F",
3452
- h: "\u2500",
3453
- v: "\u2551"
3451
+ tl: "",
3452
+ tr: "",
3453
+ bl: "",
3454
+ br: "",
3455
+ h: "",
3456
+ v: ""
3454
3457
  },
3455
3458
  singleThick: {
3456
- tl: "\u250F",
3457
- tr: "\u2513",
3458
- bl: "\u2517",
3459
- br: "\u251B",
3460
- h: "\u2501",
3461
- v: "\u2503"
3459
+ tl: "",
3460
+ tr: "",
3461
+ bl: "",
3462
+ br: "",
3463
+ h: "",
3464
+ v: ""
3462
3465
  },
3463
3466
  singleDouble: {
3464
- tl: "\u2552",
3465
- tr: "\u2555",
3466
- bl: "\u2558",
3467
- br: "\u255B",
3468
- h: "\u2550",
3469
- v: "\u2502"
3467
+ tl: "",
3468
+ tr: "",
3469
+ bl: "",
3470
+ br: "",
3471
+ h: "",
3472
+ v: ""
3470
3473
  },
3471
3474
  singleDoubleRounded: {
3472
- tl: "\u256D",
3473
- tr: "\u256E",
3474
- bl: "\u2570",
3475
- br: "\u256F",
3476
- h: "\u2550",
3477
- v: "\u2502"
3475
+ tl: "",
3476
+ tr: "",
3477
+ bl: "",
3478
+ br: "",
3479
+ h: "",
3480
+ v: ""
3478
3481
  },
3479
3482
  rounded: {
3480
- tl: "\u256D",
3481
- tr: "\u256E",
3482
- bl: "\u2570",
3483
- br: "\u256F",
3484
- h: "\u2500",
3485
- v: "\u2502"
3483
+ tl: "",
3484
+ tr: "",
3485
+ bl: "",
3486
+ br: "",
3487
+ h: "",
3488
+ v: ""
3486
3489
  }
3487
3490
  };
3488
3491
  defaultStyle = {
@@ -3502,9 +3505,9 @@ __export(exports_prompt, {
3502
3505
  prompt: () => prompt,
3503
3506
  kCancel: () => kCancel
3504
3507
  });
3505
- import g, { stdin, stdout } from "process";
3506
- import f from "readline";
3507
- import { WriteStream } from "tty";
3508
+ import g, { stdin, stdout } from "node:process";
3509
+ import f from "node:readline";
3510
+ import { WriteStream } from "node:tty";
3508
3511
  function getDefaultExportFromCjs(x) {
3509
3512
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
3510
3513
  }
@@ -4137,7 +4140,7 @@ var init_prompt = __esm(() => {
4137
4140
  eD = Object.keys(r.bgColor);
4138
4141
  [...tD, ...eD];
4139
4142
  iD = sD();
4140
- v = new Set(["\x1B", "\x9B"]);
4143
+ v = new Set(["\x1B", "›"]);
4141
4144
  y = `${rD}8;;`;
4142
4145
  aD = ["up", "down", "left", "right", "space", "enter", "cancel"];
4143
4146
  c = { actions: new Set(aD), aliases: new Map([["k", "up"], ["j", "down"], ["h", "left"], ["l", "right"], ["\x03", "cancel"], ["escape", "cancel"]]) };
@@ -4223,7 +4226,7 @@ var init_prompt = __esm(() => {
4223
4226
  if (this.state === "submit")
4224
4227
  return this.value;
4225
4228
  if (this.cursor >= this.value.length)
4226
- return `${this.value}\u2588`;
4229
+ return `${this.value}█`;
4227
4230
  const u = this.value.slice(0, this.cursor), [F, ...e$1] = this.value.slice(this.cursor);
4228
4231
  return `${u}${e.inverse(F)}${e$1.join("")}`;
4229
4232
  }
@@ -4237,23 +4240,23 @@ var init_prompt = __esm(() => {
4237
4240
  }
4238
4241
  };
4239
4242
  V = ce();
4240
- le = u("\u276F", ">");
4241
- L = u("\u25A0", "x");
4242
- W = u("\u25B2", "x");
4243
- C = u("\u2714", "\u221A");
4243
+ le = u("", ">");
4244
+ L = u("", "x");
4245
+ W = u("", "x");
4246
+ C = u("", "");
4244
4247
  o = u("");
4245
4248
  d = u("");
4246
- k = u("\u25CF", ">");
4247
- P = u("\u25CB", " ");
4248
- A = u("\u25FB", "[\u2022]");
4249
- T = u("\u25FC", "[+]");
4250
- F = u("\u25FB", "[ ]");
4249
+ k = u("", ">");
4250
+ P = u("", " ");
4251
+ A = u("", "[]");
4252
+ T = u("", "[+]");
4253
+ F = u("", "[ ]");
4251
4254
  `${e.gray(o)} `;
4252
4255
  kCancel = Symbol.for("cancel");
4253
4256
  });
4254
4257
 
4255
4258
  // node_modules/consola/dist/index.mjs
4256
- import g$1 from "process";
4259
+ import g$1 from "node:process";
4257
4260
  function b() {
4258
4261
  if (globalThis.process?.env)
4259
4262
  for (const e2 of f2) {
@@ -4477,16 +4480,16 @@ var init_dist2 = __esm(() => {
4477
4480
  };
4478
4481
  unicode = isUnicodeSupported();
4479
4482
  TYPE_ICONS = {
4480
- error: s("\u2716", "\xD7"),
4481
- fatal: s("\u2716", "\xD7"),
4482
- ready: s("\u2714", "\u221A"),
4483
- warn: s("\u26A0", "\u203C"),
4484
- info: s("\u2139", "i"),
4485
- success: s("\u2714", "\u221A"),
4486
- debug: s("\u2699", "D"),
4487
- trace: s("\u2192", "\u2192"),
4488
- fail: s("\u2716", "\xD7"),
4489
- start: s("\u25D0", "o"),
4483
+ error: s("", "×"),
4484
+ fatal: s("", "×"),
4485
+ ready: s("", ""),
4486
+ warn: s("", ""),
4487
+ info: s("", "i"),
4488
+ success: s("", ""),
4489
+ debug: s("", "D"),
4490
+ trace: s("", ""),
4491
+ fail: s("", "×"),
4492
+ start: s("", "o"),
4490
4493
  log: ""
4491
4494
  };
4492
4495
  FancyReporter = class FancyReporter extends BasicReporter {
@@ -4587,9 +4590,9 @@ var init_prompt2 = __esm(() => {
4587
4590
  });
4588
4591
 
4589
4592
  // src/cli/config.ts
4590
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
4591
- import { homedir } from "os";
4592
- import { dirname, isAbsolute, join, resolve } from "path";
4593
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4594
+ import { homedir } from "node:os";
4595
+ import { dirname, isAbsolute, join, resolve } from "node:path";
4593
4596
  function normalizeServiceConfig(service) {
4594
4597
  if (!service)
4595
4598
  return;
@@ -4627,12 +4630,15 @@ function getEnvConfig() {
4627
4630
  const baseUrl = process.env[`TSARR_${upper}_URL`];
4628
4631
  const apiKey = process.env[`TSARR_${upper}_API_KEY`];
4629
4632
  const timeout = process.env[`TSARR_${upper}_TIMEOUT`];
4630
- if (baseUrl || apiKey) {
4631
- services[service] = {
4632
- baseUrl: baseUrl ?? "",
4633
- apiKey: apiKey ?? "",
4634
- ...timeout ? { timeout: Number(timeout) } : {}
4635
- };
4633
+ const partial = {};
4634
+ if (baseUrl)
4635
+ partial.baseUrl = baseUrl;
4636
+ if (apiKey)
4637
+ partial.apiKey = apiKey;
4638
+ if (timeout)
4639
+ partial.timeout = Number(timeout);
4640
+ if (Object.keys(partial).length > 0) {
4641
+ services[service] = partial;
4636
4642
  }
4637
4643
  }
4638
4644
  return Object.keys(services).length ? { services } : {};
@@ -4794,6 +4800,8 @@ function detectFormat(args) {
4794
4800
  return "quiet";
4795
4801
  if (args.json)
4796
4802
  return "json";
4803
+ if (args.plain)
4804
+ return "plain";
4797
4805
  if (args.table)
4798
4806
  return "table";
4799
4807
  return process.stdout.isTTY ? "table" : "json";
@@ -4803,9 +4811,11 @@ function formatOutput(data, options) {
4803
4811
  return;
4804
4812
  }
4805
4813
  switch (options.format) {
4806
- case "json":
4807
- console.log(JSON.stringify(data, null, 2));
4814
+ case "json": {
4815
+ const output = options.select ? selectFields(data, options.select) : data;
4816
+ console.log(JSON.stringify(output, null, 2));
4808
4817
  break;
4818
+ }
4809
4819
  case "quiet": {
4810
4820
  const items = Array.isArray(data) ? data : [data];
4811
4821
  const field = options.idField ?? "id";
@@ -4815,12 +4825,44 @@ function formatOutput(data, options) {
4815
4825
  }
4816
4826
  break;
4817
4827
  }
4828
+ case "plain":
4829
+ printPlain(data, options.columns);
4830
+ break;
4818
4831
  case "table":
4819
- printTable(data, options.columns);
4832
+ printTable(data, options.columns, options.noHeader);
4820
4833
  break;
4821
4834
  }
4822
4835
  }
4823
- function printTable(data, columns) {
4836
+ function selectFields(data, select) {
4837
+ const fields = select.split(",").map((f3) => f3.trim());
4838
+ if (Array.isArray(data)) {
4839
+ return data.map((item) => pickFields(item, fields));
4840
+ }
4841
+ return pickFields(data, fields);
4842
+ }
4843
+ function pickFields(item, fields) {
4844
+ if (item == null || typeof item !== "object")
4845
+ return {};
4846
+ const result = {};
4847
+ for (const field of fields) {
4848
+ result[field] = item[field];
4849
+ }
4850
+ return result;
4851
+ }
4852
+ function printPlain(data, columns) {
4853
+ const items = Array.isArray(data) ? data : [data];
4854
+ if (items.length === 0)
4855
+ return;
4856
+ const cols = columns ?? Object.keys(items[0] ?? {}).slice(0, 8);
4857
+ if (cols.length === 0)
4858
+ return;
4859
+ console.log(cols.join("\t"));
4860
+ for (const item of items) {
4861
+ const row = cols.map((col) => formatCellPlain(item?.[col])).join("\t");
4862
+ console.log(row);
4863
+ }
4864
+ }
4865
+ function printTable(data, columns, noHeader) {
4824
4866
  const items = Array.isArray(data) ? data : [data];
4825
4867
  if (items.length === 0) {
4826
4868
  console.log("No results.");
@@ -4829,34 +4871,122 @@ function printTable(data, columns) {
4829
4871
  const cols = columns ?? Object.keys(items[0] ?? {}).slice(0, 8);
4830
4872
  if (cols.length === 0)
4831
4873
  return;
4874
+ const formattedRows = items.map((item) => cols.map((col) => formatCell(col, item?.[col])));
4832
4875
  const maxColWidth = 60;
4833
- const widths = cols.map((col) => {
4834
- const values = items.map((item) => formatCell(item?.[col]));
4835
- return Math.min(maxColWidth, Math.max(col.length, ...values.map((v2) => v2.length)));
4876
+ const headers = cols.map((col) => formatHeader(col));
4877
+ const widths = cols.map((_3, i2) => {
4878
+ const values = formattedRows.map((row) => stripAnsi3(row[i2]).length);
4879
+ return Math.min(maxColWidth, Math.max(headers[i2].length, ...values));
4836
4880
  });
4837
- const header = cols.map((col, i2) => col.toUpperCase().padEnd(widths[i2])).join(" ");
4838
- console.log(header);
4839
- console.log(cols.map((_3, i2) => "\u2500".repeat(widths[i2])).join(" "));
4840
- for (const item of items) {
4841
- const row = cols.map((col, i2) => {
4842
- const cell = formatCell(item?.[col]);
4843
- const truncated = cell.length > widths[i2] ? `${cell.slice(0, widths[i2] - 1)}\u2026` : cell;
4844
- return truncated.padEnd(widths[i2]);
4881
+ if (!noHeader) {
4882
+ const header = headers.map((h2, i2) => h2.padEnd(widths[i2])).join(" ");
4883
+ console.log(header);
4884
+ console.log(cols.map((_3, i2) => "─".repeat(widths[i2])).join(" "));
4885
+ }
4886
+ for (const row of formattedRows) {
4887
+ const line = row.map((cell, i2) => {
4888
+ const visible = stripAnsi3(cell).length;
4889
+ const truncated = visible > widths[i2] ? truncateWithAnsi(cell, widths[i2]) : cell;
4890
+ const pad = widths[i2] - stripAnsi3(truncated).length;
4891
+ return truncated + " ".repeat(Math.max(0, pad));
4845
4892
  }).join(" ");
4846
- console.log(row);
4893
+ console.log(line);
4847
4894
  }
4848
4895
  console.log(`
4849
4896
  ${items.length} result${items.length === 1 ? "" : "s"}`);
4850
4897
  }
4851
- function formatCell(value) {
4898
+ function formatHeader(col) {
4899
+ return col.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/_/g, " ").toUpperCase();
4900
+ }
4901
+ function formatCell(column, value) {
4852
4902
  if (value == null)
4853
- return "\u2014";
4903
+ return "";
4904
+ if (typeof value === "boolean") {
4905
+ return value ? `${GREEN}✓${RESET}` : `${RED}✗${RESET}`;
4906
+ }
4907
+ if (column === "status") {
4908
+ return formatStatus(String(value));
4909
+ }
4910
+ if (typeof value === "number" && (column.toLowerCase().includes("size") || column === "freeSpace" || column === "sizeleft")) {
4911
+ return formatBytes(value);
4912
+ }
4913
+ if (column.toLowerCase().includes("date") || column === "createdAt" || column === "updatedAt") {
4914
+ return formatDate(value);
4915
+ }
4916
+ if (typeof value === "object")
4917
+ return JSON.stringify(value);
4918
+ return String(value);
4919
+ }
4920
+ function formatCellPlain(value) {
4921
+ if (value == null)
4922
+ return "";
4854
4923
  if (typeof value === "boolean")
4855
- return value ? "yes" : "no";
4924
+ return value ? "true" : "false";
4856
4925
  if (typeof value === "object")
4857
4926
  return JSON.stringify(value);
4858
4927
  return String(value);
4859
4928
  }
4929
+ function formatStatus(status) {
4930
+ const lower = status.toLowerCase();
4931
+ if (lower === "ok" || lower === "available" || lower === "ended" || lower === "continuing") {
4932
+ return `${GREEN}${status}${RESET}`;
4933
+ }
4934
+ if (lower === "fail" || lower === "missing" || lower === "not configured") {
4935
+ return `${RED}${status}${RESET}`;
4936
+ }
4937
+ if (lower === "warning" || lower === "downloading" || lower === "queued") {
4938
+ return `${YELLOW}${status}${RESET}`;
4939
+ }
4940
+ return status;
4941
+ }
4942
+ function formatBytes(bytes) {
4943
+ if (bytes === 0)
4944
+ return "0 B";
4945
+ const units = ["B", "KB", "MB", "GB", "TB"];
4946
+ const i2 = Math.floor(Math.log(bytes) / Math.log(1024));
4947
+ const val = bytes / 1024 ** i2;
4948
+ return `${val < 10 ? val.toFixed(1) : Math.round(val)} ${units[i2]}`;
4949
+ }
4950
+ function formatDate(value) {
4951
+ if (typeof value !== "string" && !(value instanceof Date))
4952
+ return String(value);
4953
+ try {
4954
+ const d2 = new Date(value);
4955
+ if (Number.isNaN(d2.getTime()))
4956
+ return String(value);
4957
+ return d2.toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" });
4958
+ } catch {
4959
+ return String(value);
4960
+ }
4961
+ }
4962
+ function stripAnsi3(str) {
4963
+ return str.replace(ANSI_PATTERN, "");
4964
+ }
4965
+ function truncateWithAnsi(str, maxWidth) {
4966
+ let visible = 0;
4967
+ let i2 = 0;
4968
+ while (i2 < str.length && visible < maxWidth - 1) {
4969
+ if (str[i2] === ESC) {
4970
+ const end = str.indexOf("m", i2);
4971
+ if (end !== -1) {
4972
+ i2 = end + 1;
4973
+ continue;
4974
+ }
4975
+ }
4976
+ visible++;
4977
+ i2++;
4978
+ }
4979
+ return `${str.slice(0, i2)}…${RESET}`;
4980
+ }
4981
+ var ESC, GREEN, RED, YELLOW, RESET, ANSI_PATTERN;
4982
+ var init_output = __esm(() => {
4983
+ ESC = String.fromCharCode(27);
4984
+ GREEN = `${ESC}[32m`;
4985
+ RED = `${ESC}[31m`;
4986
+ YELLOW = `${ESC}[33m`;
4987
+ RESET = `${ESC}[0m`;
4988
+ ANSI_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
4989
+ });
4860
4990
 
4861
4991
  // src/cli/commands/service.ts
4862
4992
  function buildServiceCommand(serviceName, description, clientFactory, resources) {
@@ -4872,7 +5002,13 @@ function buildServiceCommand(serviceName, description, clientFactory, resources)
4872
5002
  args: {
4873
5003
  json: { type: "boolean", description: "Output as JSON" },
4874
5004
  table: { type: "boolean", description: "Output as table" },
5005
+ plain: { type: "boolean", description: "Output as TSV (no colors, for piping)" },
4875
5006
  quiet: { type: "boolean", alias: "q", description: "Output IDs only" },
5007
+ "no-header": { type: "boolean", description: "Hide table header row" },
5008
+ select: {
5009
+ type: "string",
5010
+ description: "Cherry-pick fields (comma-separated, JSON mode)"
5011
+ },
4876
5012
  yes: { type: "boolean", alias: "y", description: "Skip confirmation prompts" },
4877
5013
  ...(action.args ?? []).reduce((acc, arg) => {
4878
5014
  acc[arg.name] = {
@@ -4936,7 +5072,9 @@ Run \`tsarr config init\` or set TSARR_${serviceName.toUpperCase()}_API_KEY`);
4936
5072
  formatOutput(result, {
4937
5073
  format,
4938
5074
  columns: action.columns,
4939
- idField: action.idField
5075
+ idField: action.idField,
5076
+ noHeader: !!args["no-header"],
5077
+ select: args.select
4940
5078
  });
4941
5079
  } catch (error) {
4942
5080
  handleError(error, serviceName);
@@ -4983,6 +5121,7 @@ var init_service = __esm(() => {
4983
5121
  init_dist2();
4984
5122
  init_errors();
4985
5123
  init_config();
5124
+ init_output();
4986
5125
  init_prompt2();
4987
5126
  });
4988
5127
 
@@ -5426,7 +5565,7 @@ var separatorArrayExplode2 = (style) => {
5426
5565
  return "";
5427
5566
  }
5428
5567
  if (typeof value === "object") {
5429
- throw new Error("Deeply-nested arrays/objects aren\u2019t supported. Provide your own `querySerializer()` to handle these.");
5568
+ throw new Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");
5430
5569
  }
5431
5570
  return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;
5432
5571
  }, serializeObjectParam2 = ({
@@ -7577,6 +7716,10 @@ class SonarrClient {
7577
7716
  updateConfig(newConfig) {
7578
7717
  const updatedConfig = { ...this.clientConfig.config, ...newConfig };
7579
7718
  this.clientConfig = createServarrClient(updatedConfig);
7719
+ client2.setConfig({
7720
+ baseUrl: this.clientConfig.getBaseUrl(),
7721
+ headers: this.clientConfig.getHeaders()
7722
+ });
7580
7723
  return this.clientConfig.config;
7581
7724
  }
7582
7725
  }
@@ -7996,7 +8139,7 @@ var separatorArrayExplode3 = (style) => {
7996
8139
  return "";
7997
8140
  }
7998
8141
  if (typeof value === "object") {
7999
- throw new Error("Deeply-nested arrays/objects aren\u2019t supported. Provide your own `querySerializer()` to handle these.");
8142
+ throw new Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");
8000
8143
  }
8001
8144
  return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;
8002
8145
  }, serializeObjectParam3 = ({
@@ -10091,6 +10234,10 @@ class LidarrClient {
10091
10234
  updateConfig(newConfig) {
10092
10235
  const updatedConfig = { ...this.clientConfig.config, ...newConfig };
10093
10236
  this.clientConfig = createServarrClient(updatedConfig);
10237
+ client3.setConfig({
10238
+ baseUrl: this.clientConfig.getBaseUrl(),
10239
+ headers: this.clientConfig.getHeaders()
10240
+ });
10094
10241
  return this.clientConfig.config;
10095
10242
  }
10096
10243
  }
@@ -10132,6 +10279,7 @@ var init_lidarr3 = __esm(() => {
10132
10279
  description: "Search for artists",
10133
10280
  args: [{ name: "term", description: "Search term", required: true }],
10134
10281
  columns: ["foreignArtistId", "artistName", "overview"],
10282
+ idField: "foreignArtistId",
10135
10283
  run: (c3, a2) => c3.searchArtists(a2.term)
10136
10284
  },
10137
10285
  {
@@ -10240,6 +10388,7 @@ var init_lidarr3 = __esm(() => {
10240
10388
  description: "Search for albums",
10241
10389
  args: [{ name: "term", description: "Search term", required: true }],
10242
10390
  columns: ["foreignAlbumId", "title", "artistId"],
10391
+ idField: "foreignAlbumId",
10243
10392
  run: (c3, a2) => c3.searchAlbums(a2.term)
10244
10393
  }
10245
10394
  ]
@@ -10520,7 +10669,7 @@ var separatorArrayExplode4 = (style) => {
10520
10669
  return "";
10521
10670
  }
10522
10671
  if (typeof value === "object") {
10523
- throw new Error("Deeply-nested arrays/objects aren\u2019t supported. Provide your own `querySerializer()` to handle these.");
10672
+ throw new Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");
10524
10673
  }
10525
10674
  return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;
10526
10675
  }, serializeObjectParam4 = ({
@@ -12561,6 +12710,10 @@ class ReadarrClient {
12561
12710
  updateConfig(newConfig) {
12562
12711
  const updatedConfig = { ...this.clientConfig.config, ...newConfig };
12563
12712
  this.clientConfig = createServarrClient(updatedConfig);
12713
+ client4.setConfig({
12714
+ baseUrl: this.clientConfig.getBaseUrl(),
12715
+ headers: this.clientConfig.getHeaders()
12716
+ });
12564
12717
  return this.clientConfig.config;
12565
12718
  }
12566
12719
  }
@@ -12987,7 +13140,7 @@ var separatorArrayExplode5 = (style) => {
12987
13140
  return "";
12988
13141
  }
12989
13142
  if (typeof value === "object") {
12990
- throw new Error("Deeply-nested arrays/objects aren\u2019t supported. Provide your own `querySerializer()` to handle these.");
13143
+ throw new Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");
12991
13144
  }
12992
13145
  return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;
12993
13146
  }, serializeObjectParam5 = ({
@@ -14359,6 +14512,10 @@ class ProwlarrClient {
14359
14512
  updateConfig(newConfig) {
14360
14513
  const updatedConfig = { ...this.clientConfig.config, ...newConfig };
14361
14514
  this.clientConfig = createServarrClient(updatedConfig);
14515
+ client5.setConfig({
14516
+ baseUrl: this.clientConfig.getBaseUrl(),
14517
+ headers: this.clientConfig.getHeaders()
14518
+ });
14362
14519
  return this.clientConfig.config;
14363
14520
  }
14364
14521
  }
@@ -14710,7 +14867,7 @@ var separatorArrayExplode6 = (style) => {
14710
14867
  return "";
14711
14868
  }
14712
14869
  if (typeof value === "object") {
14713
- throw new Error("Deeply-nested arrays/objects aren\u2019t supported. Provide your own `querySerializer()` to handle these.");
14870
+ throw new Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");
14714
14871
  }
14715
14872
  return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;
14716
14873
  }, serializeObjectParam6 = ({
@@ -15511,11 +15668,7 @@ var getBadges = (options) => (options?.client ?? client6).get({
15511
15668
  security: [{ name: "X-API-KEY", type: "apiKey" }],
15512
15669
  url: "/api/system/logs",
15513
15670
  ...options
15514
- }), getSystemPing = (options) => (options?.client ?? client6).get({
15515
- security: [{ name: "X-API-KEY", type: "apiKey" }],
15516
- url: "/api/system/ping",
15517
- ...options
15518
- }), getSystemReleases = (options) => (options?.client ?? client6).get({
15671
+ }), getSystemPing = (options) => (options?.client ?? client6).get({ url: "/api/system/ping", ...options }), getSystemReleases = (options) => (options?.client ?? client6).get({
15519
15672
  security: [{ name: "X-API-KEY", type: "apiKey" }],
15520
15673
  url: "/api/system/releases",
15521
15674
  ...options
@@ -16042,6 +16195,43 @@ var exports_doctor = {};
16042
16195
  __export(exports_doctor, {
16043
16196
  doctor: () => doctor
16044
16197
  });
16198
+ function classifyError(error) {
16199
+ if (!(error instanceof Error))
16200
+ return "Unknown error";
16201
+ const msg = error.message;
16202
+ const cause = error.cause;
16203
+ if (cause?.code === "ECONNREFUSED" || msg.includes("ECONNREFUSED")) {
16204
+ return "Connection refused - is the service running?";
16205
+ }
16206
+ if (cause?.code === "ENOTFOUND" || msg.includes("ENOTFOUND")) {
16207
+ return "Host not found - check the URL";
16208
+ }
16209
+ if (cause?.code === "ECONNRESET" || msg.includes("ECONNRESET")) {
16210
+ return "Connection reset - service may have crashed";
16211
+ }
16212
+ if (cause?.code === "ETIMEDOUT" || msg.includes("ETIMEDOUT") || msg.includes("timed out")) {
16213
+ return "Connection timed out - service may be unreachable";
16214
+ }
16215
+ if (msg.includes("fetch failed") || msg.includes("Failed to fetch")) {
16216
+ return `Service unreachable - ${cause?.message ?? "check URL and network"}`;
16217
+ }
16218
+ if (msg.includes("401") || msg.includes("Unauthorized")) {
16219
+ return "Authentication failed (401) - check your API key";
16220
+ }
16221
+ if (msg.includes("403") || msg.includes("Forbidden")) {
16222
+ return "Access denied (403) - check your API key permissions";
16223
+ }
16224
+ if (msg.includes("502") || msg.includes("Bad Gateway")) {
16225
+ return "Bad gateway (502) - reverse proxy or service issue";
16226
+ }
16227
+ if (msg.includes("503") || msg.includes("Service Unavailable")) {
16228
+ return "Service unavailable (503) - service may be starting up";
16229
+ }
16230
+ if (msg.includes("CERT") || msg.includes("certificate") || msg.includes("SSL")) {
16231
+ return "SSL/TLS certificate error - check HTTPS configuration";
16232
+ }
16233
+ return msg;
16234
+ }
16045
16235
  function extractVersion(service, status) {
16046
16236
  const data = status?.data ?? status;
16047
16237
  if (typeof data === "string") {
@@ -16063,6 +16253,7 @@ var init_doctor = __esm(() => {
16063
16253
  init_readarr2();
16064
16254
  init_sonarr2();
16065
16255
  init_config();
16256
+ init_output();
16066
16257
  clientFactories = {
16067
16258
  radarr: (c3) => new RadarrClient(c3),
16068
16259
  sonarr: (c3) => new SonarrClient(c3),
@@ -16079,7 +16270,9 @@ var init_doctor = __esm(() => {
16079
16270
  args: {
16080
16271
  json: { type: "boolean", description: "Output as JSON" },
16081
16272
  table: { type: "boolean", description: "Output as table" },
16082
- quiet: { type: "boolean", alias: "q", description: "Output service names only" }
16273
+ plain: { type: "boolean", description: "Output as TSV (no colors, for piping)" },
16274
+ quiet: { type: "boolean", alias: "q", description: "Output service names only" },
16275
+ select: { type: "string", description: "Cherry-pick fields (comma-separated, JSON mode)" }
16083
16276
  },
16084
16277
  async run({ args }) {
16085
16278
  const format = detectFormat(args);
@@ -16126,24 +16319,28 @@ var init_doctor = __esm(() => {
16126
16319
  baseUrl: svcConfig.baseUrl
16127
16320
  });
16128
16321
  } catch (error) {
16129
- const msg = error instanceof Error ? error.message : "Unknown error";
16130
16322
  results.push({
16131
16323
  service,
16132
16324
  configured: true,
16133
16325
  status: "fail",
16134
16326
  baseUrl: svcConfig.baseUrl,
16135
- error: msg
16327
+ error: classifyError(error)
16136
16328
  });
16137
16329
  }
16138
16330
  }
16331
+ const hadFailure = !hasAny || results.some((r3) => r3.status === "fail");
16139
16332
  if (!hasAny && format === "table") {
16140
16333
  consola.warn("\nNo services configured. Run `tsarr config init` to set up.");
16141
16334
  }
16142
16335
  formatOutput(results, {
16143
16336
  format,
16144
16337
  columns: ["service", "status", "version", "baseUrl", "error"],
16145
- idField: "service"
16338
+ idField: "service",
16339
+ select: args.select
16146
16340
  });
16341
+ if (hadFailure) {
16342
+ process.exitCode = 1;
16343
+ }
16147
16344
  }
16148
16345
  });
16149
16346
  });
@@ -16216,7 +16413,7 @@ var init_config2 = __esm(() => {
16216
16413
  consola.success(`Connected to ${service} v${version}`);
16217
16414
  }
16218
16415
  } catch {
16219
- consola.warn(`Could not connect to ${service} \u2014 config saved anyway.`);
16416
+ consola.warn(`Could not connect to ${service} config saved anyway.`);
16220
16417
  }
16221
16418
  }
16222
16419
  const location = await promptSelect("Save config to:", [
@@ -16249,7 +16446,8 @@ var init_config2 = __esm(() => {
16249
16446
  },
16250
16447
  async run({ args }) {
16251
16448
  setConfigValue(args.key, args.value, !args.local);
16252
- consola.success(`Set ${args.key} = ${args.value}`);
16449
+ const displayValue = /\b(apiKey|apikey|token|secret|password)\b/i.test(args.key) ? "*****" : args.value;
16450
+ consola.success(`Set ${args.key} = ${displayValue}`);
16253
16451
  }
16254
16452
  });
16255
16453
  configGet = defineCommand({
@@ -16277,7 +16475,14 @@ var init_config2 = __esm(() => {
16277
16475
  },
16278
16476
  async run() {
16279
16477
  const config = loadConfig();
16280
- console.log(JSON.stringify(config, null, 2));
16478
+ const redacted = JSON.parse(JSON.stringify(config));
16479
+ if (redacted.services) {
16480
+ for (const svc of Object.values(redacted.services)) {
16481
+ if (svc?.apiKey)
16482
+ svc.apiKey = "*****";
16483
+ }
16484
+ }
16485
+ console.log(JSON.stringify(redacted, null, 2));
16281
16486
  }
16282
16487
  });
16283
16488
  config = defineCommand({
@@ -16404,7 +16609,7 @@ function generateFishCompletion() {
16404
16609
  return `complete -c tsarr -n "__fish_seen_subcommand_from ${svc}; and not __fish_seen_subcommand_from ${res}" -a "${res}"`;
16405
16610
  }).join(`
16406
16611
  `);
16407
- const actionCompletions = Object.entries(SERVICE_COMMANDS).flatMap(([_svc, resources7]) => Object.entries(resources7).map(([res, actions]) => `complete -c tsarr -n "__fish_seen_subcommand_from ${res}; and not __fish_seen_subcommand_from ${actions.join(" ")}" -a "${actions.join(" ")}"`)).join(`
16612
+ const actionCompletions = Object.entries(SERVICE_COMMANDS).flatMap(([svc, resources7]) => Object.entries(resources7).map(([res, actions]) => `complete -c tsarr -n "__fish_seen_subcommand_from ${svc}; and __fish_seen_subcommand_from ${res}; and not __fish_seen_subcommand_from ${actions.join(" ")}" -a "${actions.join(" ")}"`)).join(`
16408
16613
  `);
16409
16614
  return `# Fish completions for tsarr
16410
16615
  set -l services ${services}
@@ -16516,13 +16721,13 @@ var init_completions = __esm(() => {
16516
16721
 
16517
16722
  // src/cli/index.ts
16518
16723
  init_dist();
16519
- import { readFileSync as readFileSync2 } from "fs";
16724
+ import { readFileSync as readFileSync2 } from "node:fs";
16520
16725
  var { version } = JSON.parse(readFileSync2(new URL("../../package.json", import.meta.url), "utf-8"));
16521
16726
  var main = defineCommand({
16522
16727
  meta: {
16523
16728
  name: "tsarr",
16524
16729
  version,
16525
- description: "CLI for Servarr APIs (Radarr, Sonarr, Lidarr, Readarr, Prowlarr, Bazarr)"
16730
+ description: "Type-safe CLI for Servarr APIs (Radarr, Sonarr, Lidarr, Readarr, Prowlarr, Bazarr)"
16526
16731
  },
16527
16732
  subCommands: {
16528
16733
  radarr: () => Promise.resolve().then(() => (init_radarr3(), exports_radarr3)).then((m2) => m2.radarr),