@muhammedaksam/easiarr 0.3.1 → 0.3.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@muhammedaksam/easiarr",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "TUI tool for generating docker-compose files for the *arr media ecosystem with 41 apps, TRaSH Guides best practices, VPN routing, and Traefik reverse proxy support",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
@@ -35,6 +35,7 @@
35
35
  ],
36
36
  "scripts": {
37
37
  "dev": "bun run --watch src/index.ts",
38
+ "dev:debug": "bun run dev --debug",
38
39
  "build": "bun build src/index.ts --outdir dist --target bun",
39
40
  "typecheck": "bun x tsc --noEmit",
40
41
  "lint": "eslint src/",
@@ -3,6 +3,8 @@
3
3
  * Interacts with Radarr, Sonarr, Lidarr, Readarr, Whisparr APIs
4
4
  */
5
5
 
6
+ import { debugLog } from "../utils/debug"
7
+
6
8
  // Types for Root Folder API
7
9
  export interface RootFolder {
8
10
  id?: number
@@ -15,6 +17,7 @@ export interface RootFolder {
15
17
  // Options for adding root folder (some apps like Lidarr need extra fields)
16
18
  export interface AddRootFolderOptions {
17
19
  path: string
20
+ name?: string // Required for Lidarr
18
21
  defaultMetadataProfileId?: number
19
22
  defaultQualityProfileId?: number
20
23
  }
@@ -124,16 +127,24 @@ export class ArrApiClient {
124
127
  ...options.headers,
125
128
  }
126
129
 
130
+ debugLog("ArrAPI", `${options.method || "GET"} ${url}`)
131
+ if (options.body) {
132
+ debugLog("ArrAPI", `Request Body: ${options.body}`)
133
+ }
134
+
127
135
  const response = await fetch(url, { ...options, headers })
136
+ const text = await response.text()
137
+
138
+ debugLog("ArrAPI", `Response ${response.status} from ${endpoint}`)
139
+ if (text && text.length < 2000) {
140
+ debugLog("ArrAPI", `Response Body: ${text}`)
141
+ }
128
142
 
129
143
  if (!response.ok) {
130
- throw new Error(`API request failed: ${response.status} ${response.statusText}`)
144
+ throw new Error(`API request failed: ${response.status} ${response.statusText} - ${text}`)
131
145
  }
132
146
 
133
- // Handle empty responses (DELETE returns empty body)
134
- const text = await response.text()
135
147
  if (!text) return {} as T
136
-
137
148
  return JSON.parse(text) as T
138
149
  }
139
150
 
@@ -3,6 +3,8 @@
3
3
  * Manages Indexer Proxies, Sync Profiles, and FlareSolverr integration
4
4
  */
5
5
 
6
+ import { debugLog } from "../utils/debug"
7
+
6
8
  export interface IndexerProxy {
7
9
  id?: number
8
10
  name: string
@@ -55,13 +57,23 @@ export class ProwlarrClient {
55
57
  ...((options.headers as Record<string, string>) || {}),
56
58
  }
57
59
 
60
+ debugLog("Prowlarr", `${options.method || "GET"} ${url}`)
61
+ if (options.body) {
62
+ debugLog("Prowlarr", `Request Body: ${options.body}`)
63
+ }
64
+
58
65
  const response = await fetch(url, { ...options, headers })
66
+ const text = await response.text()
67
+
68
+ debugLog("Prowlarr", `Response ${response.status} from ${endpoint}`)
69
+ if (text && text.length < 2000) {
70
+ debugLog("Prowlarr", `Response Body: ${text}`)
71
+ }
59
72
 
60
73
  if (!response.ok) {
61
- throw new Error(`Prowlarr API request failed: ${response.status} ${response.statusText}`)
74
+ throw new Error(`Prowlarr API request failed: ${response.status} ${response.statusText} - ${text}`)
62
75
  }
63
76
 
64
- const text = await response.text()
65
77
  if (!text) return {} as T
66
78
  return JSON.parse(text) as T
67
79
  }
@@ -248,11 +260,21 @@ export class ProwlarrClient {
248
260
  appApiKey: string,
249
261
  syncLevel: "disabled" | "addOnly" | "fullSync" = "fullSync"
250
262
  ): Promise<Application> {
263
+ // Default sync categories for each app type
264
+ // Radarr: Movies (2000, 2010, etc), Sonarr: TV (5000, 5010, etc)
265
+ // Lidarr: Audio (3000), Readarr: Books (7000, 8010)
266
+ const syncCategoriesMap: Record<ArrAppType, number[]> = {
267
+ Radarr: [2000, 2010, 2020, 2030, 2040, 2045, 2050, 2060, 2070, 2080],
268
+ Sonarr: [5000, 5010, 5020, 5030, 5040, 5045, 5050, 5060, 5070, 5080],
269
+ Lidarr: [3000, 3010, 3020, 3030, 3040],
270
+ Readarr: [7000, 7010, 7020, 7030, 8000, 8010, 8020],
271
+ }
272
+
251
273
  const fields: { name: string; value: unknown }[] = [
252
274
  { name: "prowlarrUrl", value: prowlarrUrl },
253
275
  { name: "baseUrl", value: appUrl },
254
276
  { name: "apiKey", value: appApiKey },
255
- { name: "syncCategories", value: [] },
277
+ { name: "syncCategories", value: syncCategoriesMap[appType] || [] },
256
278
  ]
257
279
 
258
280
  return this.request<Application>("/applications", {
package/src/index.ts CHANGED
@@ -2,12 +2,21 @@
2
2
  /**
3
3
  * Easiarr Entry Point
4
4
  * TUI tool for generating docker-compose files for the *arr ecosystem
5
+ *
6
+ * Usage:
7
+ * easiarr - Start the TUI
8
+ * easiarr --debug - Start with debug logging enabled
9
+ * easiarr -d - Same as --debug
5
10
  */
6
11
 
7
12
  import { createCliRenderer } from "@opentui/core"
8
13
  import { App } from "./ui/App"
14
+ import { initDebug } from "./utils/debug"
9
15
 
10
16
  async function main() {
17
+ // Initialize debug logging if enabled
18
+ initDebug()
19
+
11
20
  const renderer = await createCliRenderer({
12
21
  consoleOptions: {
13
22
  startInDebugMode: false,
@@ -313,12 +313,13 @@ export class AppConfigurator extends BoxRenderable {
313
313
  throw new Error("Already configured")
314
314
  }
315
315
 
316
- // Add root folder - Lidarr requires profile IDs
316
+ // Add root folder - Lidarr requires profile IDs and name
317
317
  if (appId === "lidarr") {
318
318
  const metadataProfiles = await client.getMetadataProfiles()
319
319
  const qualityProfiles = await client.getQualityProfiles()
320
320
  await client.addRootFolder({
321
321
  path: appDef.rootFolder.path,
322
+ name: "Music", // Required by Lidarr
322
323
  defaultMetadataProfileId: metadataProfiles[0]?.id || 1,
323
324
  defaultQualityProfileId: qualityProfiles[0]?.id || 1,
324
325
  })
@@ -560,6 +561,12 @@ export class AppConfigurator extends BoxRenderable {
560
561
  const client = new ArrApiClient("localhost", port, apiKey, appDef.rootFolder.apiVersion)
561
562
 
562
563
  try {
564
+ // Check if download client already exists
565
+ const existingClients = await client.getDownloadClients()
566
+ const clientName = type === "qbittorrent" ? "qBittorrent" : "SABnzbd"
567
+ const alreadyExists = existingClients.some((c) => c.name === clientName)
568
+ if (alreadyExists) continue
569
+
563
570
  if (type === "qbittorrent") {
564
571
  const config = createQBittorrentConfig(this.qbHost, this.qbPort, this.qbUser, this.qbPass, appConfig.id)
565
572
  await client.addDownloadClient(config)
@@ -1,18 +1,38 @@
1
1
  /**
2
2
  * Debug logging utility for Easiarr
3
3
  *
4
- * Only logs when EASIARR_DEBUG environment variable is set.
5
- * Usage: EASIARR_DEBUG=1 bun run dev
4
+ * Enable debug logging via:
5
+ * - CLI flag: easiarr --debug
6
+ * - Environment variable: EASIARR_DEBUG=1 bun run dev
6
7
  */
7
8
 
8
- import { appendFileSync } from "fs"
9
+ import { appendFileSync, writeFileSync } from "fs"
9
10
  import { join } from "path"
10
11
 
11
- const DEBUG_ENABLED = process.env.EASIARR_DEBUG === "1" || process.env.EASIARR_DEBUG === "true"
12
- const logFile = join(import.meta.dir, "..", "..", "debug.log")
12
+ // Check CLI args for --debug flag
13
+ const hasDebugFlag = process.argv.includes("--debug") || process.argv.includes("-d")
14
+ const hasEnvDebug = process.env.EASIARR_DEBUG === "1" || process.env.EASIARR_DEBUG === "true"
15
+
16
+ export const DEBUG_ENABLED = hasDebugFlag || hasEnvDebug
17
+
18
+ // Save debug log to ~/.easiarr/ like other config files
19
+ const easiarrDir = join(process.env.HOME || "~", ".easiarr")
20
+ const logFile = join(easiarrDir, "debug.log")
13
21
 
14
22
  /**
15
- * Log a debug message to debug.log file if EASIARR_DEBUG is enabled
23
+ * Initialize debug mode - clears old log file
24
+ */
25
+ export function initDebug(): void {
26
+ if (!DEBUG_ENABLED) return
27
+ try {
28
+ writeFileSync(logFile, `=== Easiarr Debug Log - ${new Date().toISOString()} ===\n`)
29
+ } catch {
30
+ // Ignore
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Log a debug message to debug.log file if debug mode is enabled
16
36
  */
17
37
  export function debugLog(category: string, message: string): void {
18
38
  if (!DEBUG_ENABLED) return
@@ -25,3 +45,25 @@ export function debugLog(category: string, message: string): void {
25
45
  // Ignore logging errors
26
46
  }
27
47
  }
48
+
49
+ /**
50
+ * Log API request details for debugging
51
+ */
52
+ export function debugRequest(method: string, url: string, body?: unknown): void {
53
+ if (!DEBUG_ENABLED) return
54
+ debugLog("API", `${method} ${url}`)
55
+ if (body) {
56
+ debugLog("API", `Body: ${JSON.stringify(body, null, 2)}`)
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Log API response details for debugging
62
+ */
63
+ export function debugResponse(status: number, url: string, body?: string): void {
64
+ if (!DEBUG_ENABLED) return
65
+ debugLog("API", `Response ${status} from ${url}`)
66
+ if (body && body.length < 2000) {
67
+ debugLog("API", `Response Body: ${body}`)
68
+ }
69
+ }