tsarr 2.7.6 → 2.9.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 +4 -1
- package/dist/cli/commands/config.d.ts.map +1 -1
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/lidarr.d.ts.map +1 -1
- package/dist/cli/commands/readarr.d.ts.map +1 -1
- package/dist/cli/commands/service.d.ts.map +1 -1
- package/dist/cli/completions.d.ts.map +1 -1
- package/dist/cli/config.d.ts +8 -2
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/index.js +350 -135
- package/dist/tsarr-2.9.0.tgz +0 -0
- package/package.json +2 -2
- package/dist/tsarr-2.7.6.tgz +0 -0
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":"
|
|
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":"
|
|
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":"lidarr.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/lidarr.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"lidarr.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/lidarr.ts"],"names":[],"mappings":"AAqkBA,eAAO,MAAM,MAAM,qDAKlB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"readarr.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/readarr.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"readarr.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/readarr.ts"],"names":[],"mappings":"AAskBA,eAAO,MAAM,OAAO,qDAKnB,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,
|
|
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":"
|
|
1
|
+
{"version":3,"file":"completions.d.ts","sourceRoot":"","sources":["../../src/cli/completions.ts"],"names":[],"mappings":"AAqOA,eAAO,MAAM,WAAW;;;;;;EA4BtB,CAAC"}
|
package/dist/cli/config.d.ts
CHANGED
|
@@ -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
|
-
|
|
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;
|
package/dist/cli/config.d.ts.map
CHANGED
|
@@ -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;
|
|
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,
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
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
|
|
4939
|
+
const globalRaw = readJsonFile(GLOBAL_CONFIG_PATH);
|
|
4924
4940
|
const localPath = findLocalConfigPath();
|
|
4925
|
-
const
|
|
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(
|
|
4929
|
-
...Object.keys(
|
|
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
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
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
|
-
...
|
|
4944
|
-
...
|
|
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,
|
|
4956
|
-
if (!
|
|
5003
|
+
function getResolvedApiKeyFilePath(serviceName, instance, localPath, local, global) {
|
|
5004
|
+
if (!instance.apiKeyFile)
|
|
4957
5005
|
return;
|
|
4958
|
-
|
|
4959
|
-
|
|
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
|
-
|
|
4962
|
-
|
|
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
|
|
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
|
|
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
|
|
4982
|
-
|
|
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 (!
|
|
5049
|
+
if (!instance.username || !instance.password)
|
|
4986
5050
|
return null;
|
|
4987
5051
|
return {
|
|
4988
|
-
baseUrl:
|
|
4989
|
-
username:
|
|
4990
|
-
password:
|
|
4991
|
-
...
|
|
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 =
|
|
4995
|
-
const apiKeyFilePath = getResolvedApiKeyFilePath(serviceName,
|
|
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:
|
|
5066
|
+
baseUrl: instance.baseUrl,
|
|
5003
5067
|
apiKey,
|
|
5004
|
-
...
|
|
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 (
|
|
5107
|
+
for (let i2 = 0;i2 < parts.length; i2++) {
|
|
5023
5108
|
if (current == null || typeof current !== "object")
|
|
5024
5109
|
return;
|
|
5025
|
-
current
|
|
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
|
|
5125
|
+
const raw = existsSync(configPath) ? JSON.parse(readFileSync(configPath, "utf-8")) : {};
|
|
5032
5126
|
const parts = key.split(".");
|
|
5033
|
-
let current =
|
|
5127
|
+
let current = raw;
|
|
5034
5128
|
for (let i2 = 0;i2 < parts.length - 1; i2++) {
|
|
5035
|
-
|
|
5036
|
-
|
|
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[
|
|
5144
|
+
current = current[part];
|
|
5039
5145
|
}
|
|
5040
5146
|
current[parts[parts.length - 1]] = parseConfigValue(key, value);
|
|
5041
5147
|
if (global) {
|
|
5042
|
-
|
|
5148
|
+
mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true });
|
|
5149
|
+
writeFileSync(configPath, `${JSON.stringify(raw, null, 2)}
|
|
5150
|
+
`);
|
|
5043
5151
|
} else {
|
|
5044
|
-
|
|
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
|
|
5466
|
+
const instanceName = args.instance;
|
|
5467
|
+
const config = getServiceConfig(serviceName, instanceName);
|
|
5353
5468
|
if (!config) {
|
|
5354
|
-
|
|
5355
|
-
|
|
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 ${
|
|
5649
|
+
message: `Dry run: would execute ${serviceLabel} ${resourceName} ${actionName}${formatDryRunArgs(filteredArgs)}`
|
|
5527
5650
|
};
|
|
5528
5651
|
}
|
|
5529
5652
|
function formatDryRunArgs(args) {
|
|
@@ -12279,6 +12402,24 @@ var init_lidarr3 = __esm(() => {
|
|
|
12279
12402
|
args: [{ name: "id", description: "Import list ID", required: true, type: "number" }],
|
|
12280
12403
|
run: (c3, a2) => c3.getImportList(a2.id)
|
|
12281
12404
|
},
|
|
12405
|
+
{
|
|
12406
|
+
name: "add",
|
|
12407
|
+
description: "Add an import list from JSON file or stdin",
|
|
12408
|
+
args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
|
|
12409
|
+
run: async (c3, a2) => c3.addImportList(readJsonInput(a2.file))
|
|
12410
|
+
},
|
|
12411
|
+
{
|
|
12412
|
+
name: "edit",
|
|
12413
|
+
description: "Edit an import list (merges JSON with existing)",
|
|
12414
|
+
args: [
|
|
12415
|
+
{ name: "id", description: "Import list ID", required: true, type: "number" },
|
|
12416
|
+
{ name: "file", description: "JSON file with fields to update", required: true }
|
|
12417
|
+
],
|
|
12418
|
+
run: async (c3, a2) => {
|
|
12419
|
+
const existing = unwrapData(await c3.getImportList(a2.id));
|
|
12420
|
+
return c3.updateImportList(a2.id, { ...existing, ...readJsonInput(a2.file) });
|
|
12421
|
+
}
|
|
12422
|
+
},
|
|
12282
12423
|
{
|
|
12283
12424
|
name: "delete",
|
|
12284
12425
|
description: "Delete an import list",
|
|
@@ -15288,6 +15429,24 @@ var init_readarr3 = __esm(() => {
|
|
|
15288
15429
|
args: [{ name: "id", description: "Import list ID", required: true, type: "number" }],
|
|
15289
15430
|
run: (c3, a2) => c3.getImportList(a2.id)
|
|
15290
15431
|
},
|
|
15432
|
+
{
|
|
15433
|
+
name: "add",
|
|
15434
|
+
description: "Add an import list from JSON file or stdin",
|
|
15435
|
+
args: [{ name: "file", description: "JSON file path (use - for stdin)", required: true }],
|
|
15436
|
+
run: async (c3, a2) => c3.addImportList(readJsonInput(a2.file))
|
|
15437
|
+
},
|
|
15438
|
+
{
|
|
15439
|
+
name: "edit",
|
|
15440
|
+
description: "Edit an import list (merges JSON with existing)",
|
|
15441
|
+
args: [
|
|
15442
|
+
{ name: "id", description: "Import list ID", required: true, type: "number" },
|
|
15443
|
+
{ name: "file", description: "JSON file with fields to update", required: true }
|
|
15444
|
+
],
|
|
15445
|
+
run: async (c3, a2) => {
|
|
15446
|
+
const existing = unwrapData(await c3.getImportList(a2.id));
|
|
15447
|
+
return c3.updateImportList(a2.id, { ...existing, ...readJsonInput(a2.file) });
|
|
15448
|
+
}
|
|
15449
|
+
},
|
|
15291
15450
|
{
|
|
15292
15451
|
name: "delete",
|
|
15293
15452
|
description: "Delete an import list",
|
|
@@ -21201,9 +21360,10 @@ var init_doctor = __esm(() => {
|
|
|
21201
21360
|
consola.info(`Checking connections...
|
|
21202
21361
|
`);
|
|
21203
21362
|
}
|
|
21363
|
+
let hasMultiInstance = false;
|
|
21204
21364
|
for (const service of SERVICES) {
|
|
21205
|
-
const
|
|
21206
|
-
if (
|
|
21365
|
+
const instances = getServiceInstances(service);
|
|
21366
|
+
if (instances.length === 0) {
|
|
21207
21367
|
results.push({
|
|
21208
21368
|
service,
|
|
21209
21369
|
configured: false,
|
|
@@ -21211,45 +21371,62 @@ var init_doctor = __esm(() => {
|
|
|
21211
21371
|
});
|
|
21212
21372
|
continue;
|
|
21213
21373
|
}
|
|
21214
|
-
|
|
21215
|
-
|
|
21216
|
-
|
|
21217
|
-
const
|
|
21218
|
-
if (
|
|
21219
|
-
|
|
21220
|
-
|
|
21221
|
-
|
|
21222
|
-
|
|
21223
|
-
|
|
21374
|
+
if (instances.length > 1)
|
|
21375
|
+
hasMultiInstance = true;
|
|
21376
|
+
for (const inst of instances) {
|
|
21377
|
+
const svcConfig = getServiceConfig(service, inst.name);
|
|
21378
|
+
if (!svcConfig) {
|
|
21379
|
+
results.push({
|
|
21380
|
+
service,
|
|
21381
|
+
...inst.name ? { instance: inst.name } : {},
|
|
21382
|
+
configured: false,
|
|
21383
|
+
status: "not configured"
|
|
21384
|
+
});
|
|
21385
|
+
continue;
|
|
21224
21386
|
}
|
|
21225
|
-
|
|
21226
|
-
|
|
21227
|
-
|
|
21387
|
+
hasAny = true;
|
|
21388
|
+
try {
|
|
21389
|
+
const client9 = clientFactories[service](svcConfig);
|
|
21390
|
+
const status = await client9.getSystemStatus();
|
|
21391
|
+
if (status?.error !== undefined) {
|
|
21392
|
+
const err = status.error;
|
|
21393
|
+
const code = err?.cause?.code ?? err?.code;
|
|
21394
|
+
const cause = code ? { code } : undefined;
|
|
21395
|
+
const message = err?.cause?.message ?? err?.message ?? err?.code ?? "Unknown API error";
|
|
21396
|
+
throw Object.assign(new Error(message), cause ? { cause } : {});
|
|
21397
|
+
}
|
|
21398
|
+
const version = extractVersion(service, status) ?? "?";
|
|
21399
|
+
if (version === "?") {
|
|
21400
|
+
throw new Error("Unexpected response payload");
|
|
21401
|
+
}
|
|
21402
|
+
results.push({
|
|
21403
|
+
service,
|
|
21404
|
+
...inst.name ? { instance: inst.name } : {},
|
|
21405
|
+
configured: true,
|
|
21406
|
+
status: "ok",
|
|
21407
|
+
version: String(version),
|
|
21408
|
+
baseUrl: svcConfig.baseUrl
|
|
21409
|
+
});
|
|
21410
|
+
} catch (error) {
|
|
21411
|
+
results.push({
|
|
21412
|
+
service,
|
|
21413
|
+
...inst.name ? { instance: inst.name } : {},
|
|
21414
|
+
configured: true,
|
|
21415
|
+
status: "fail",
|
|
21416
|
+
baseUrl: svcConfig.baseUrl,
|
|
21417
|
+
error: classifyError(error)
|
|
21418
|
+
});
|
|
21228
21419
|
}
|
|
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
21420
|
}
|
|
21245
21421
|
}
|
|
21246
21422
|
const hadFailure = !hasAny || results.some((r3) => r3.status === "fail");
|
|
21247
21423
|
if (!hasAny && format === "table") {
|
|
21248
21424
|
consola.warn("\nNo services configured. Run `tsarr config init` to set up.");
|
|
21249
21425
|
}
|
|
21426
|
+
const columns = hasMultiInstance ? ["service", "instance", "status", "configured", "version", "baseUrl", "error"] : ["service", "status", "configured", "version", "baseUrl", "error"];
|
|
21250
21427
|
formatOutput(results, {
|
|
21251
21428
|
format,
|
|
21252
|
-
columns
|
|
21429
|
+
columns,
|
|
21253
21430
|
idField: "service",
|
|
21254
21431
|
select: args.select
|
|
21255
21432
|
});
|
|
@@ -21265,6 +21442,61 @@ var exports_config = {};
|
|
|
21265
21442
|
__export(exports_config, {
|
|
21266
21443
|
config: () => config
|
|
21267
21444
|
});
|
|
21445
|
+
async function configureInstance(service, instanceName) {
|
|
21446
|
+
const baseUrl = await promptIfMissing(undefined, `${service}${instanceName ? ` (${instanceName})` : ""} base URL (e.g. http://localhost:${DEFAULT_PORTS[service]})`);
|
|
21447
|
+
if (service === "qbittorrent") {
|
|
21448
|
+
const username = await promptIfMissing(undefined, `${service} username`);
|
|
21449
|
+
const password = await promptIfMissing(undefined, `${service} password`);
|
|
21450
|
+
const cfg2 = { baseUrl, username, password };
|
|
21451
|
+
if (instanceName)
|
|
21452
|
+
cfg2.name = instanceName;
|
|
21453
|
+
return cfg2;
|
|
21454
|
+
}
|
|
21455
|
+
const apiKey = await promptIfMissing(undefined, `${service} API key`);
|
|
21456
|
+
const cfg = { baseUrl, apiKey };
|
|
21457
|
+
if (instanceName)
|
|
21458
|
+
cfg.name = instanceName;
|
|
21459
|
+
return cfg;
|
|
21460
|
+
}
|
|
21461
|
+
async function testConnection(service, serviceConfig) {
|
|
21462
|
+
try {
|
|
21463
|
+
if (service === "qbittorrent") {
|
|
21464
|
+
const { QBittorrentClient: QBittorrentClient2 } = await Promise.resolve().then(() => (init_qbittorrent2(), exports_qbittorrent));
|
|
21465
|
+
const client9 = new QBittorrentClient2({
|
|
21466
|
+
baseUrl: serviceConfig.baseUrl,
|
|
21467
|
+
username: serviceConfig.username,
|
|
21468
|
+
password: serviceConfig.password
|
|
21469
|
+
});
|
|
21470
|
+
const status = await client9.getSystemStatus();
|
|
21471
|
+
consola.success(`Connected to ${service}${serviceConfig.name ? ` (${serviceConfig.name})` : ""} v${status.version}`);
|
|
21472
|
+
} else {
|
|
21473
|
+
const { RadarrClient: RadarrClient2 } = await Promise.resolve().then(() => (init_radarr2(), exports_radarr));
|
|
21474
|
+
const { SonarrClient: SonarrClient2 } = await Promise.resolve().then(() => (init_sonarr2(), exports_sonarr));
|
|
21475
|
+
const { LidarrClient: LidarrClient2 } = await Promise.resolve().then(() => (init_lidarr2(), exports_lidarr));
|
|
21476
|
+
const { ReadarrClient: ReadarrClient2 } = await Promise.resolve().then(() => (init_readarr2(), exports_readarr));
|
|
21477
|
+
const { ProwlarrClient: ProwlarrClient2 } = await Promise.resolve().then(() => (init_prowlarr2(), exports_prowlarr));
|
|
21478
|
+
const { BazarrClient: BazarrClient2 } = await Promise.resolve().then(() => (init_bazarr2(), exports_bazarr));
|
|
21479
|
+
const { SeerrClient: SeerrClient2 } = await Promise.resolve().then(() => (init_seerr2(), exports_seerr));
|
|
21480
|
+
const factories = {
|
|
21481
|
+
radarr: (c3) => new RadarrClient2(c3),
|
|
21482
|
+
sonarr: (c3) => new SonarrClient2(c3),
|
|
21483
|
+
lidarr: (c3) => new LidarrClient2(c3),
|
|
21484
|
+
readarr: (c3) => new ReadarrClient2(c3),
|
|
21485
|
+
prowlarr: (c3) => new ProwlarrClient2(c3),
|
|
21486
|
+
bazarr: (c3) => new BazarrClient2(c3),
|
|
21487
|
+
seerr: (c3) => new SeerrClient2(c3)
|
|
21488
|
+
};
|
|
21489
|
+
const client9 = factories[service]?.(serviceConfig);
|
|
21490
|
+
if (client9) {
|
|
21491
|
+
const status = await client9.getSystemStatus();
|
|
21492
|
+
const version = status?.data?.version ?? status?.version ?? "?";
|
|
21493
|
+
consola.success(`Connected to ${service}${serviceConfig.name ? ` (${serviceConfig.name})` : ""} v${version}`);
|
|
21494
|
+
}
|
|
21495
|
+
}
|
|
21496
|
+
} catch {
|
|
21497
|
+
consola.warn(`Could not connect to ${service}${serviceConfig.name ? ` (${serviceConfig.name})` : ""} — config saved anyway.`);
|
|
21498
|
+
}
|
|
21499
|
+
}
|
|
21268
21500
|
var DEFAULT_PORTS, configInit, configSet, configGet, configShow, config;
|
|
21269
21501
|
var init_config2 = __esm(() => {
|
|
21270
21502
|
init_dist();
|
|
@@ -21304,49 +21536,25 @@ var init_config2 = __esm(() => {
|
|
|
21304
21536
|
const config = { services: {} };
|
|
21305
21537
|
for (const service of selected) {
|
|
21306
21538
|
console.log();
|
|
21307
|
-
const
|
|
21308
|
-
|
|
21309
|
-
|
|
21310
|
-
|
|
21311
|
-
|
|
21312
|
-
|
|
21313
|
-
|
|
21314
|
-
|
|
21315
|
-
|
|
21316
|
-
|
|
21317
|
-
|
|
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.`);
|
|
21539
|
+
const instances = [];
|
|
21540
|
+
const first = await configureInstance(service);
|
|
21541
|
+
await testConnection(service, first);
|
|
21542
|
+
instances.push(first);
|
|
21543
|
+
while (true) {
|
|
21544
|
+
const addMore = await promptConfirm(`Add another ${service} instance?`);
|
|
21545
|
+
if (!addMore)
|
|
21546
|
+
break;
|
|
21547
|
+
if (instances.length === 1 && !instances[0].name) {
|
|
21548
|
+
const firstName = await promptIfMissing(undefined, `Name for the existing ${service} instance (e.g. "main", "1080p")`);
|
|
21549
|
+
instances[0].name = firstName;
|
|
21348
21550
|
}
|
|
21551
|
+
const newName = await promptIfMissing(undefined, `Name for the new ${service} instance (e.g. "4K")`);
|
|
21552
|
+
console.log();
|
|
21553
|
+
const newInstance = await configureInstance(service, newName);
|
|
21554
|
+
await testConnection(service, newInstance);
|
|
21555
|
+
instances.push(newInstance);
|
|
21349
21556
|
}
|
|
21557
|
+
config.services[service] = instances;
|
|
21350
21558
|
}
|
|
21351
21559
|
const location = await promptSelect("Save config to:", [
|
|
21352
21560
|
{ label: `Global (${GLOBAL_CONFIG_PATH})`, value: "global" },
|
|
@@ -21409,11 +21617,15 @@ var init_config2 = __esm(() => {
|
|
|
21409
21617
|
const config = loadConfig();
|
|
21410
21618
|
const redacted = JSON.parse(JSON.stringify(config));
|
|
21411
21619
|
if (redacted.services) {
|
|
21412
|
-
for (const
|
|
21413
|
-
if (
|
|
21414
|
-
svc
|
|
21415
|
-
|
|
21416
|
-
|
|
21620
|
+
for (const instances of Object.values(redacted.services)) {
|
|
21621
|
+
if (Array.isArray(instances)) {
|
|
21622
|
+
for (const svc of instances) {
|
|
21623
|
+
if (svc?.apiKey)
|
|
21624
|
+
svc.apiKey = "*****";
|
|
21625
|
+
if (svc?.password)
|
|
21626
|
+
svc.password = "*****";
|
|
21627
|
+
}
|
|
21628
|
+
}
|
|
21417
21629
|
}
|
|
21418
21630
|
}
|
|
21419
21631
|
console.log(JSON.stringify(redacted, null, 2));
|
|
@@ -21528,7 +21740,8 @@ ${actionAssoc}
|
|
|
21528
21740
|
'--json[Output as JSON]' \\
|
|
21529
21741
|
'--table[Output as table]' \\
|
|
21530
21742
|
'--quiet[Output IDs only]' \\
|
|
21531
|
-
'--yes[Skip confirmation prompts]'
|
|
21743
|
+
'--yes[Skip confirmation prompts]' \\
|
|
21744
|
+
'--instance[Instance name for multi-instance services]:instance:'
|
|
21532
21745
|
;;
|
|
21533
21746
|
esac
|
|
21534
21747
|
}
|
|
@@ -21565,7 +21778,9 @@ ${actionCompletions}
|
|
|
21565
21778
|
complete -c tsarr -l json -d "Output as JSON"
|
|
21566
21779
|
complete -c tsarr -l table -d "Output as table"
|
|
21567
21780
|
complete -c tsarr -l quiet -s q -d "Output IDs only"
|
|
21568
|
-
complete -c tsarr -l yes -s y -d "Skip confirmation prompts"
|
|
21781
|
+
complete -c tsarr -l yes -s y -d "Skip confirmation prompts"
|
|
21782
|
+
complete -c tsarr -l instance -s i -d "Instance name (multi-instance services)"
|
|
21783
|
+
`;
|
|
21569
21784
|
}
|
|
21570
21785
|
var SERVICE_COMMANDS, completions;
|
|
21571
21786
|
var init_completions = __esm(() => {
|
|
@@ -21664,7 +21879,7 @@ init_dist();
|
|
|
21664
21879
|
// package.json
|
|
21665
21880
|
var package_default = {
|
|
21666
21881
|
name: "tsarr",
|
|
21667
|
-
version: "2.
|
|
21882
|
+
version: "2.9.0",
|
|
21668
21883
|
author: "Robbe Verhelst",
|
|
21669
21884
|
repository: {
|
|
21670
21885
|
type: "git",
|
|
@@ -21676,7 +21891,7 @@ var package_default = {
|
|
|
21676
21891
|
main: "dist/index.js",
|
|
21677
21892
|
module: "dist/index.js",
|
|
21678
21893
|
devDependencies: {
|
|
21679
|
-
"@biomejs/biome": "2.4.
|
|
21894
|
+
"@biomejs/biome": "2.4.12",
|
|
21680
21895
|
"@hey-api/openapi-ts": "^0.96.0",
|
|
21681
21896
|
"@semantic-release/changelog": "^6.0.3",
|
|
21682
21897
|
"@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.
|
|
3
|
+
"version": "2.9.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.
|
|
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",
|
package/dist/tsarr-2.7.6.tgz
DELETED
|
Binary file
|