tsarr 2.7.6 → 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.
package/README.md CHANGED
@@ -157,7 +157,7 @@ export TSARR_RADARR_URL=http://localhost:7878
157
157
  export TSARR_RADARR_API_KEY=your-api-key
158
158
  ```
159
159
 
160
- Config is stored in `~/.config/tsarr/config.json` (global) or `.tsarr.json` (local project). Environment variables take priority over config files.
160
+ Config is stored in `~/.config/tsarr/config.json` (global) or `.tsarr.json` (local project). Environment variables take priority over config files. You can configure multiple instances of the same service (e.g. a 4K and 1080p Radarr) — see the [CLI Guide](./docs/cli.md) for details.
161
161
 
162
162
  ### Usage
163
163
 
@@ -171,6 +171,9 @@ tsarr sonarr series list
171
171
  tsarr prowlarr indexer list
172
172
  tsarr lidarr artist search --term "Radiohead"
173
173
 
174
+ # Multi-instance: target a specific named instance
175
+ tsarr radarr movie list --instance 4K
176
+
174
177
  # Output formats
175
178
  tsarr radarr movie list --table # Table (default in terminal)
176
179
  tsarr radarr movie list --json # JSON (default when piped)
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/config.ts"],"names":[],"mappings":"AA+LA,eAAO,MAAM,MAAM,qDAWjB,CAAC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/config.ts"],"names":[],"mappings":"AAmPA,eAAO,MAAM,MAAM,qDAWjB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/doctor.ts"],"names":[],"mappings":"AAoCA,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;EAkFjB,CAAC"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/doctor.ts"],"names":[],"mappings":"AAqCA,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;EAwGjB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/service.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IACvC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,GAAG,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,SAAS,EAAE,CAAC;CACtB;AAED,eAAO,MAAM,sBAAsB,UAA0D,CAAC;AAE9F,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,CAAC,EAAE,CAM5E;AAED,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,GAAG,EACnC,SAAS,EAAE,WAAW,EAAE,uDA+LzB;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,CAE5C;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,CAGnD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAS1E;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAMlF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CAMpF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,GAAG,GAAG,MAAM,GAAG,SAAS,CAE5D"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/service.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IACvC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,GAAG,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,SAAS,EAAE,CAAC;CACtB;AAED,eAAO,MAAM,sBAAsB,UAA0D,CAAC;AAE9F,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,CAAC,EAAE,CAM5E;AAED,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,GAAG,EACnC,SAAS,EAAE,WAAW,EAAE,uDAoNzB;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,CAE5C;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,CAGnD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAS1E;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAMlF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CAMpF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,GAAG,GAAG,MAAM,GAAG,SAAS,CAE5D"}
@@ -1 +1 @@
1
- {"version":3,"file":"completions.d.ts","sourceRoot":"","sources":["../../src/cli/completions.ts"],"names":[],"mappings":"AAkOA,eAAO,MAAM,WAAW;;;;;;EA4BtB,CAAC"}
1
+ {"version":3,"file":"completions.d.ts","sourceRoot":"","sources":["../../src/cli/completions.ts"],"names":[],"mappings":"AAqOA,eAAO,MAAM,WAAW;;;;;;EA4BtB,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import type { QBittorrentClientConfig, ServarrClientConfig } from '../core/types';
2
2
  export interface ServiceConfig {
3
+ name?: string;
3
4
  baseUrl: string;
4
5
  apiKey?: string;
5
6
  apiKeyFile?: string;
@@ -7,8 +8,9 @@ export interface ServiceConfig {
7
8
  password?: string;
8
9
  timeout?: number;
9
10
  }
11
+ /** Internal config — services always normalized to arrays */
10
12
  export interface TsarrCliConfig {
11
- services: Record<string, ServiceConfig>;
13
+ services: Record<string, ServiceConfig[]>;
12
14
  defaults?: {
13
15
  output?: 'json' | 'table' | 'quiet';
14
16
  };
@@ -17,7 +19,11 @@ declare const SERVICES: readonly ["radarr", "sonarr", "lidarr", "readarr", "prow
17
19
  declare const GLOBAL_CONFIG_PATH: string;
18
20
  declare const LOCAL_CONFIG_NAME = ".tsarr.json";
19
21
  export declare function loadConfig(): TsarrCliConfig;
20
- export declare function getServiceConfig(serviceName: string): ServarrClientConfig | QBittorrentClientConfig | null;
22
+ /** Returns all instances for a service (normalized, always an array). */
23
+ export declare function getServiceInstances(serviceName: string): ServiceConfig[];
24
+ /** Returns instance names for a service. */
25
+ export declare function getInstanceNames(serviceName: string): string[];
26
+ export declare function getServiceConfig(serviceName: string, instanceName?: string): ServarrClientConfig | QBittorrentClientConfig | null;
21
27
  export declare function saveGlobalConfig(config: TsarrCliConfig): void;
22
28
  export declare function saveLocalConfig(config: TsarrCliConfig): void;
23
29
  export declare function getConfigValue(key: string): string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/cli/config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAElF,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACxC,QAAQ,CAAC,EAAE;QACT,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;KACrC,CAAC;CACH;AAED,QAAA,MAAM,QAAQ,kGASJ,CAAC;AAEX,QAAA,MAAM,kBAAkB,QAAyC,CAAC;AAClE,QAAA,MAAM,iBAAiB,gBAAgB,CAAC;AA6ExC,wBAAgB,UAAU,IAAI,cAAc,CA+B3C;AA0CD,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,GAClB,mBAAmB,GAAG,uBAAuB,GAAG,IAAI,CAgCtD;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAG7D;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAI5D;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAS9D;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,UAAO,GAAG,IAAI,CAoB9E;AAcD,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAQhD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC,CAMnF;AAED,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,QAAQ,EAAE,CAAC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/cli/config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAElF,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,6DAA6D;AAC7D,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC;IAC1C,QAAQ,CAAC,EAAE;QACT,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;KACrC,CAAC;CACH;AAUD,QAAA,MAAM,QAAQ,kGASJ,CAAC;AAEX,QAAA,MAAM,kBAAkB,QAAyC,CAAC;AAClE,QAAA,MAAM,iBAAiB,gBAAgB,CAAC;AA6FxC,wBAAgB,UAAU,IAAI,cAAc,CAqE3C;AAoDD,yEAAyE;AACzE,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,EAAE,CAGxE;AAED,4CAA4C;AAC5C,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAI9D;AAWD,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,EACnB,YAAY,CAAC,EAAE,MAAM,GACpB,mBAAmB,GAAG,uBAAuB,GAAG,IAAI,CAgCtD;AAiBD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAG7D;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAI5D;AAYD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAoB9D;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,UAAO,GAAG,IAAI,CAmC9E;AAcD,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAWhD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC,CAMnF;AAED,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,QAAQ,EAAE,CAAC"}
package/dist/cli/index.js CHANGED
@@ -4856,6 +4856,7 @@ function normalizeServiceConfig(service) {
4856
4856
  const normalized = {
4857
4857
  baseUrl: service.baseUrl ?? "",
4858
4858
  apiKey: service.apiKey ?? "",
4859
+ ...service.name ? { name: service.name } : {},
4859
4860
  ...service.apiKeyFile ? { apiKeyFile: service.apiKeyFile } : {},
4860
4861
  ...service.username ? { username: service.username } : {},
4861
4862
  ...service.password ? { password: service.password } : {}
@@ -4866,12 +4867,24 @@ function normalizeServiceConfig(service) {
4866
4867
  }
4867
4868
  return normalized;
4868
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
+ }
4869
4877
  function normalizeConfig(config) {
4870
- const services = Object.fromEntries(Object.entries(config.services ?? {}).map(([name, service]) => [name, normalizeServiceConfig(service)]).filter(([, service]) => service != null));
4871
- return {
4872
- ...config,
4873
- ...Object.keys(services).length > 0 ? { services } : {}
4874
- };
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;
4875
4888
  }
4876
4889
  function readJsonFile(path) {
4877
4890
  if (!existsSync(path))
@@ -4902,7 +4915,7 @@ function getEnvConfig() {
4902
4915
  if (timeout)
4903
4916
  partial.timeout = Number(timeout);
4904
4917
  if (Object.keys(partial).length > 0) {
4905
- services[service] = partial;
4918
+ services[service] = [partial];
4906
4919
  }
4907
4920
  }
4908
4921
  return Object.keys(services).length ? { services } : {};
@@ -4919,29 +4932,64 @@ function findLocalConfigPath() {
4919
4932
  dir = parent;
4920
4933
  }
4921
4934
  }
4935
+ function isArrayEntry(raw) {
4936
+ return Array.isArray(raw);
4937
+ }
4922
4938
  function loadConfig() {
4923
- const global = readJsonFile(GLOBAL_CONFIG_PATH);
4939
+ const globalRaw = readJsonFile(GLOBAL_CONFIG_PATH);
4924
4940
  const localPath = findLocalConfigPath();
4925
- const local = localPath ? readJsonFile(localPath) : {};
4941
+ const localRaw = localPath ? readJsonFile(localPath) : {};
4926
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 {}
4927
4955
  const allServiceNames = new Set([
4928
- ...Object.keys(global.services ?? {}),
4929
- ...Object.keys(local.services ?? {}),
4956
+ ...Object.keys(globalRaw.services ?? {}),
4957
+ ...Object.keys(localRaw.services ?? {}),
4930
4958
  ...Object.keys(env2.services ?? {})
4931
4959
  ]);
4932
4960
  const services = {};
4933
4961
  for (const name of allServiceNames) {
4934
- services[name] = {
4935
- ...global.services?.[name],
4936
- ...local.services?.[name],
4937
- ...env2.services?.[name]
4938
- };
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
+ }
4939
4987
  }
4940
4988
  const merged = {
4941
4989
  services,
4942
4990
  defaults: {
4943
- ...global.defaults,
4944
- ...local.defaults
4991
+ ...globalRaw.defaults,
4992
+ ...localRaw.defaults
4945
4993
  }
4946
4994
  };
4947
4995
  return merged;
@@ -4952,16 +5000,20 @@ function resolveConfigRelativePath(filePath, configPath) {
4952
5000
  }
4953
5001
  return resolve(dirname(configPath), filePath);
4954
5002
  }
4955
- function getResolvedApiKeyFilePath(serviceName, service, localPath, local, global) {
4956
- if (!service.apiKeyFile)
5003
+ function getResolvedApiKeyFilePath(serviceName, instance, localPath, local, global) {
5004
+ if (!instance.apiKeyFile)
4957
5005
  return;
4958
- if (local.services?.[serviceName]?.apiKeyFile) {
4959
- 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);
4960
5010
  }
4961
- if (global.services?.[serviceName]?.apiKeyFile) {
4962
- 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);
4963
5015
  }
4964
- return service.apiKeyFile;
5016
+ return instance.apiKeyFile;
4965
5017
  }
4966
5018
  function readApiKeyFile(filePath) {
4967
5019
  if (!existsSync(filePath)) {
@@ -4973,75 +5025,132 @@ function readApiKeyFile(filePath) {
4973
5025
  throw new Error(`Failed to read API key file: ${filePath}: ${err instanceof Error ? err.message : String(err)}`);
4974
5026
  }
4975
5027
  }
4976
- 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) {
4977
5040
  const global = readJsonFile(GLOBAL_CONFIG_PATH);
4978
5041
  const localPath = findLocalConfigPath();
4979
5042
  const local = localPath ? readJsonFile(localPath) : {};
4980
5043
  const config = loadConfig();
4981
- const service = config.services[serviceName];
4982
- if (!service?.baseUrl)
5044
+ const instances = config.services[serviceName] ?? [];
5045
+ const instance = resolveInstance(instances, instanceName);
5046
+ if (!instance?.baseUrl)
4983
5047
  return null;
4984
5048
  if (serviceName === "qbittorrent") {
4985
- if (!service.username || !service.password)
5049
+ if (!instance.username || !instance.password)
4986
5050
  return null;
4987
5051
  return {
4988
- baseUrl: service.baseUrl,
4989
- username: service.username,
4990
- password: service.password,
4991
- ...service.timeout ? { timeout: service.timeout } : {}
5052
+ baseUrl: instance.baseUrl,
5053
+ username: instance.username,
5054
+ password: instance.password,
5055
+ ...instance.timeout ? { timeout: instance.timeout } : {}
4992
5056
  };
4993
5057
  }
4994
- let apiKey = service.apiKey;
4995
- const apiKeyFilePath = getResolvedApiKeyFilePath(serviceName, service, localPath, local, global);
5058
+ let apiKey = instance.apiKey;
5059
+ const apiKeyFilePath = getResolvedApiKeyFilePath(serviceName, instance, localPath, local, global);
4996
5060
  if (!apiKey && apiKeyFilePath) {
4997
5061
  apiKey = readApiKeyFile(apiKeyFilePath);
4998
5062
  }
4999
5063
  if (!apiKey)
5000
5064
  return null;
5001
5065
  return {
5002
- baseUrl: service.baseUrl,
5066
+ baseUrl: instance.baseUrl,
5003
5067
  apiKey,
5004
- ...service.timeout ? { timeout: service.timeout } : {}
5068
+ ...instance.timeout ? { timeout: instance.timeout } : {}
5005
5069
  };
5006
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
+ }
5007
5083
  function saveGlobalConfig(config) {
5008
5084
  mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true });
5009
- writeFileSync(GLOBAL_CONFIG_PATH, `${JSON.stringify(config, null, 2)}
5085
+ writeFileSync(GLOBAL_CONFIG_PATH, `${JSON.stringify(serializeForDisk(config), null, 2)}
5010
5086
  `);
5011
5087
  }
5012
5088
  function saveLocalConfig(config) {
5013
5089
  const existingPath = findLocalConfigPath();
5014
5090
  const targetPath = existingPath ?? join(process.cwd(), LOCAL_CONFIG_NAME);
5015
- writeFileSync(targetPath, `${JSON.stringify(config, null, 2)}
5091
+ writeFileSync(targetPath, `${JSON.stringify(serializeForDisk(config), null, 2)}
5016
5092
  `);
5017
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
+ }
5018
5103
  function getConfigValue(key) {
5019
5104
  const config = loadConfig();
5020
5105
  const parts = key.split(".");
5021
5106
  let current = config;
5022
- for (const part of parts) {
5107
+ for (let i2 = 0;i2 < parts.length; i2++) {
5023
5108
  if (current == null || typeof current !== "object")
5024
5109
  return;
5025
- 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
+ }
5026
5120
  }
5027
5121
  return current != null ? String(current) : undefined;
5028
5122
  }
5029
5123
  function setConfigValue(key, value, global = true) {
5030
5124
  const configPath = global ? GLOBAL_CONFIG_PATH : findLocalConfigPath() ?? join(process.cwd(), LOCAL_CONFIG_NAME);
5031
- const config = readJsonFile(configPath);
5125
+ const raw = existsSync(configPath) ? JSON.parse(readFileSync(configPath, "utf-8")) : {};
5032
5126
  const parts = key.split(".");
5033
- let current = config;
5127
+ let current = raw;
5034
5128
  for (let i2 = 0;i2 < parts.length - 1; i2++) {
5035
- if (current[parts[i2]] == null || typeof current[parts[i2]] !== "object") {
5036
- 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;
5140
+ }
5141
+ if (current[part] == null || typeof current[part] !== "object") {
5142
+ current[part] = {};
5037
5143
  }
5038
- current = current[parts[i2]];
5144
+ current = current[part];
5039
5145
  }
5040
5146
  current[parts[parts.length - 1]] = parseConfigValue(key, value);
5041
5147
  if (global) {
5042
- saveGlobalConfig(config);
5148
+ mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true });
5149
+ writeFileSync(configPath, `${JSON.stringify(raw, null, 2)}
5150
+ `);
5043
5151
  } else {
5044
- saveLocalConfig(config);
5152
+ writeFileSync(configPath, `${JSON.stringify(raw, null, 2)}
5153
+ `);
5045
5154
  }
5046
5155
  }
5047
5156
  function parseConfigValue(key, value) {
@@ -5339,6 +5448,11 @@ function buildServiceCommand(serviceName, description, clientFactory, resources)
5339
5448
  description: "Cherry-pick fields (comma-separated, JSON mode)"
5340
5449
  },
5341
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
+ },
5342
5456
  ...(action.args ?? []).reduce((acc, arg) => {
5343
5457
  acc[arg.name] = {
5344
5458
  type: arg.type === "boolean" ? "boolean" : arg.type === "number" ? "string" : "string",
@@ -5349,10 +5463,17 @@ function buildServiceCommand(serviceName, description, clientFactory, resources)
5349
5463
  }, {})
5350
5464
  },
5351
5465
  async run({ args }) {
5352
- const config = getServiceConfig(serviceName);
5466
+ const instanceName = args.instance;
5467
+ const config = getServiceConfig(serviceName, instanceName);
5353
5468
  if (!config) {
5354
- const envHint = serviceName === "qbittorrent" ? `TSARR_QBITTORRENT_URL, TSARR_QBITTORRENT_USERNAME, and TSARR_QBITTORRENT_PASSWORD` : `TSARR_${serviceName.toUpperCase()}_URL and TSARR_${serviceName.toUpperCase()}_API_KEY`;
5355
- 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
+ }
5356
5477
  process.exit(1);
5357
5478
  }
5358
5479
  try {
@@ -5380,7 +5501,7 @@ function buildServiceCommand(serviceName, description, clientFactory, resources)
5380
5501
  const noHeader = process.argv.includes("--no-header") || !!args.noHeader;
5381
5502
  const dryRun = !!(args["dry-run"] ?? args.dryRun ?? process.argv.includes("--dry-run"));
5382
5503
  if (dryRun) {
5383
- formatOutput(buildDryRunPreview(format, serviceName, resource.name, action.name, resolvedArgs), {
5504
+ formatOutput(buildDryRunPreview(format, serviceName, resource.name, action.name, resolvedArgs, instanceName), {
5384
5505
  format,
5385
5506
  noHeader
5386
5507
  });
@@ -5511,19 +5632,21 @@ function coerceBooleanArg(value) {
5511
5632
  }
5512
5633
  return Boolean(value);
5513
5634
  }
5514
- function buildDryRunPreview(format, serviceName, resourceName, actionName, args) {
5515
- 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;
5516
5638
  if (format === "json") {
5517
5639
  return {
5518
5640
  dryRun: true,
5519
5641
  service: serviceName,
5642
+ ...instanceName ? { instance: instanceName } : {},
5520
5643
  resource: resourceName,
5521
5644
  action: actionName,
5522
5645
  args: filteredArgs
5523
5646
  };
5524
5647
  }
5525
5648
  return {
5526
- message: `Dry run: would execute ${serviceName} ${resourceName} ${actionName}${formatDryRunArgs(filteredArgs)}`
5649
+ message: `Dry run: would execute ${serviceLabel} ${resourceName} ${actionName}${formatDryRunArgs(filteredArgs)}`
5527
5650
  };
5528
5651
  }
5529
5652
  function formatDryRunArgs(args) {
@@ -21201,9 +21324,10 @@ var init_doctor = __esm(() => {
21201
21324
  consola.info(`Checking connections...
21202
21325
  `);
21203
21326
  }
21327
+ let hasMultiInstance = false;
21204
21328
  for (const service of SERVICES) {
21205
- const svcConfig = getServiceConfig(service);
21206
- if (!svcConfig) {
21329
+ const instances = getServiceInstances(service);
21330
+ if (instances.length === 0) {
21207
21331
  results.push({
21208
21332
  service,
21209
21333
  configured: false,
@@ -21211,45 +21335,62 @@ var init_doctor = __esm(() => {
21211
21335
  });
21212
21336
  continue;
21213
21337
  }
21214
- hasAny = true;
21215
- try {
21216
- const client9 = clientFactories[service](svcConfig);
21217
- const status = await client9.getSystemStatus();
21218
- if (status?.error !== undefined) {
21219
- const err = status.error;
21220
- const code = err?.cause?.code ?? err?.code;
21221
- const cause = code ? { code } : undefined;
21222
- const message = err?.cause?.message ?? err?.message ?? err?.code ?? "Unknown API error";
21223
- throw Object.assign(new Error(message), cause ? { cause } : {});
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;
21224
21350
  }
21225
- const version = extractVersion(service, status) ?? "?";
21226
- if (version === "?") {
21227
- throw new Error("Unexpected response payload");
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
+ }
21366
+ results.push({
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 } : {},
21378
+ configured: true,
21379
+ status: "fail",
21380
+ baseUrl: svcConfig.baseUrl,
21381
+ error: classifyError(error)
21382
+ });
21228
21383
  }
21229
- results.push({
21230
- service,
21231
- configured: true,
21232
- status: "ok",
21233
- version: String(version),
21234
- baseUrl: svcConfig.baseUrl
21235
- });
21236
- } catch (error) {
21237
- results.push({
21238
- service,
21239
- configured: true,
21240
- status: "fail",
21241
- baseUrl: svcConfig.baseUrl,
21242
- error: classifyError(error)
21243
- });
21244
21384
  }
21245
21385
  }
21246
21386
  const hadFailure = !hasAny || results.some((r3) => r3.status === "fail");
21247
21387
  if (!hasAny && format === "table") {
21248
21388
  consola.warn("\nNo services configured. Run `tsarr config init` to set up.");
21249
21389
  }
21390
+ const columns = hasMultiInstance ? ["service", "instance", "status", "configured", "version", "baseUrl", "error"] : ["service", "status", "configured", "version", "baseUrl", "error"];
21250
21391
  formatOutput(results, {
21251
21392
  format,
21252
- columns: ["service", "status", "configured", "version", "baseUrl", "error"],
21393
+ columns,
21253
21394
  idField: "service",
21254
21395
  select: args.select
21255
21396
  });
@@ -21265,6 +21406,61 @@ var exports_config = {};
21265
21406
  __export(exports_config, {
21266
21407
  config: () => config
21267
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
+ }
21268
21464
  var DEFAULT_PORTS, configInit, configSet, configGet, configShow, config;
21269
21465
  var init_config2 = __esm(() => {
21270
21466
  init_dist();
@@ -21304,49 +21500,25 @@ var init_config2 = __esm(() => {
21304
21500
  const config = { services: {} };
21305
21501
  for (const service of selected) {
21306
21502
  console.log();
21307
- const baseUrl = await promptIfMissing(undefined, `${service} base URL (e.g. http://localhost:${DEFAULT_PORTS[service]})`);
21308
- if (service === "qbittorrent") {
21309
- const username = await promptIfMissing(undefined, `${service} username`);
21310
- const password = await promptIfMissing(undefined, `${service} password`);
21311
- config.services[service] = { baseUrl, username, password };
21312
- try {
21313
- const { QBittorrentClient: QBittorrentClient2 } = await Promise.resolve().then(() => (init_qbittorrent2(), exports_qbittorrent));
21314
- const client9 = new QBittorrentClient2({ baseUrl, username, password });
21315
- const status = await client9.getSystemStatus();
21316
- consola.success(`Connected to ${service} v${status.version}`);
21317
- } catch {
21318
- consola.warn(`Could not connect to ${service} — config saved anyway.`);
21319
- }
21320
- } else {
21321
- const apiKey = await promptIfMissing(undefined, `${service} API key`);
21322
- config.services[service] = { baseUrl, apiKey };
21323
- try {
21324
- const { RadarrClient: RadarrClient2 } = await Promise.resolve().then(() => (init_radarr2(), exports_radarr));
21325
- const { SonarrClient: SonarrClient2 } = await Promise.resolve().then(() => (init_sonarr2(), exports_sonarr));
21326
- const { LidarrClient: LidarrClient2 } = await Promise.resolve().then(() => (init_lidarr2(), exports_lidarr));
21327
- const { ReadarrClient: ReadarrClient2 } = await Promise.resolve().then(() => (init_readarr2(), exports_readarr));
21328
- const { ProwlarrClient: ProwlarrClient2 } = await Promise.resolve().then(() => (init_prowlarr2(), exports_prowlarr));
21329
- const { BazarrClient: BazarrClient2 } = await Promise.resolve().then(() => (init_bazarr2(), exports_bazarr));
21330
- const { SeerrClient: SeerrClient2 } = await Promise.resolve().then(() => (init_seerr2(), exports_seerr));
21331
- const factories = {
21332
- radarr: (c3) => new RadarrClient2(c3),
21333
- sonarr: (c3) => new SonarrClient2(c3),
21334
- lidarr: (c3) => new LidarrClient2(c3),
21335
- readarr: (c3) => new ReadarrClient2(c3),
21336
- prowlarr: (c3) => new ProwlarrClient2(c3),
21337
- bazarr: (c3) => new BazarrClient2(c3),
21338
- seerr: (c3) => new SeerrClient2(c3)
21339
- };
21340
- const client9 = factories[service]?.(config.services[service]);
21341
- if (client9) {
21342
- const status = await client9.getSystemStatus();
21343
- const version = status?.data?.version ?? status?.version ?? "?";
21344
- consola.success(`Connected to ${service} v${version}`);
21345
- }
21346
- } catch {
21347
- 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;
21348
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);
21349
21520
  }
21521
+ config.services[service] = instances;
21350
21522
  }
21351
21523
  const location = await promptSelect("Save config to:", [
21352
21524
  { label: `Global (${GLOBAL_CONFIG_PATH})`, value: "global" },
@@ -21409,11 +21581,15 @@ var init_config2 = __esm(() => {
21409
21581
  const config = loadConfig();
21410
21582
  const redacted = JSON.parse(JSON.stringify(config));
21411
21583
  if (redacted.services) {
21412
- for (const svc of Object.values(redacted.services)) {
21413
- if (svc?.apiKey)
21414
- svc.apiKey = "*****";
21415
- if (svc?.password)
21416
- 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
+ }
21417
21593
  }
21418
21594
  }
21419
21595
  console.log(JSON.stringify(redacted, null, 2));
@@ -21528,7 +21704,8 @@ ${actionAssoc}
21528
21704
  '--json[Output as JSON]' \\
21529
21705
  '--table[Output as table]' \\
21530
21706
  '--quiet[Output IDs only]' \\
21531
- '--yes[Skip confirmation prompts]'
21707
+ '--yes[Skip confirmation prompts]' \\
21708
+ '--instance[Instance name for multi-instance services]:instance:'
21532
21709
  ;;
21533
21710
  esac
21534
21711
  }
@@ -21565,7 +21742,9 @@ ${actionCompletions}
21565
21742
  complete -c tsarr -l json -d "Output as JSON"
21566
21743
  complete -c tsarr -l table -d "Output as table"
21567
21744
  complete -c tsarr -l quiet -s q -d "Output IDs only"
21568
- 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
+ `;
21569
21748
  }
21570
21749
  var SERVICE_COMMANDS, completions;
21571
21750
  var init_completions = __esm(() => {
@@ -21664,7 +21843,7 @@ init_dist();
21664
21843
  // package.json
21665
21844
  var package_default = {
21666
21845
  name: "tsarr",
21667
- version: "2.7.6",
21846
+ version: "2.8.0",
21668
21847
  author: "Robbe Verhelst",
21669
21848
  repository: {
21670
21849
  type: "git",
@@ -21676,7 +21855,7 @@ var package_default = {
21676
21855
  main: "dist/index.js",
21677
21856
  module: "dist/index.js",
21678
21857
  devDependencies: {
21679
- "@biomejs/biome": "2.4.11",
21858
+ "@biomejs/biome": "2.4.12",
21680
21859
  "@hey-api/openapi-ts": "^0.96.0",
21681
21860
  "@semantic-release/changelog": "^6.0.3",
21682
21861
  "@semantic-release/git": "^10.0.1",
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsarr",
3
- "version": "2.7.6",
3
+ "version": "2.8.0",
4
4
  "author": "Robbe Verhelst",
5
5
  "repository": {
6
6
  "type": "git",
@@ -12,7 +12,7 @@
12
12
  "main": "dist/index.js",
13
13
  "module": "dist/index.js",
14
14
  "devDependencies": {
15
- "@biomejs/biome": "2.4.11",
15
+ "@biomejs/biome": "2.4.12",
16
16
  "@hey-api/openapi-ts": "^0.96.0",
17
17
  "@semantic-release/changelog": "^6.0.3",
18
18
  "@semantic-release/git": "^10.0.1",
Binary file