@muhammedaksam/easiarr 0.1.8 → 0.1.10

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.1.8",
3
+ "version": "0.1.10",
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",
@@ -86,6 +86,7 @@ export function createQBittorrentConfig(
86
86
  { name: "username", value: username },
87
87
  { name: "password", value: password },
88
88
  { name: categoryField, value: category },
89
+ { name: "savePath", value: "/data/torrents" },
89
90
  { name: "recentMoviePriority", value: 0 },
90
91
  { name: "olderMoviePriority", value: 0 },
91
92
  { name: "initialState", value: 0 },
@@ -111,6 +112,7 @@ export function createSABnzbdConfig(host: string, port: number, apiKey: string,
111
112
  { name: "port", value: port },
112
113
  { name: "apiKey", value: apiKey },
113
114
  { name: categoryField, value: category },
115
+ { name: "savePath", value: "/data/usenet" },
114
116
  { name: "recentMoviePriority", value: -100 },
115
117
  { name: "olderMoviePriority", value: -100 },
116
118
  ],
@@ -210,8 +212,8 @@ export class ArrApiClient {
210
212
  return null // Skip - password already configured
211
213
  }
212
214
 
213
- // Update with authentication settings
214
- const updatedConfig = {
215
+ // Update with authentication settings (id must be in body, not URL)
216
+ const updatedConfig: HostConfig = {
215
217
  ...currentConfig,
216
218
  authenticationMethod: "forms",
217
219
  authenticationRequired: "enabled",
@@ -220,7 +222,8 @@ export class ArrApiClient {
220
222
  passwordConfirmation: password,
221
223
  }
222
224
 
223
- return this.request<HostConfig>(`/config/host/${currentConfig.id}`, {
225
+ // PUT to /config/host with id in body (not /config/host/{id})
226
+ return this.request<HostConfig>("/config/host", {
224
227
  method: "PUT",
225
228
  body: JSON.stringify(updatedConfig),
226
229
  })
@@ -13,7 +13,6 @@ import {
13
13
  } from "@opentui/core"
14
14
  import { existsSync, readFileSync } from "node:fs"
15
15
  import { writeFile, readFile } from "node:fs/promises"
16
- import { join } from "node:path"
17
16
  import { createPageLayout } from "../components/PageLayout"
18
17
  import { EasiarrConfig, AppId } from "../../config/schema"
19
18
  import { getApp } from "../../apps/registry"
@@ -114,7 +113,7 @@ export class AppConfigurator extends BoxRenderable {
114
113
  const { container, content } = createPageLayout(this.cliRenderer, {
115
114
  title: "Configure Apps",
116
115
  stepInfo: "Global Credentials",
117
- footerHint: "Enter credentials for all *arr apps Tab Next Field Enter Continue Esc Skip",
116
+ footerHint: "Tab Cycle Fields/Shortcuts O Override Enter Continue Esc Skip",
118
117
  })
119
118
  this.pageContainer = container
120
119
  this.add(container)
@@ -161,23 +160,29 @@ export class AppConfigurator extends BoxRenderable {
161
160
  content.add(overrideText)
162
161
 
163
162
  userInput.focus()
164
- let focusedInput = userInput
163
+ let focusedInput: InputRenderable | null = userInput
165
164
 
166
165
  // Handle key events
167
166
  this.keyHandler = (key: KeyEvent) => {
168
- if (key.name === "o" && !userInput.focused && !passInput.focused) {
169
- // Toggle override
167
+ // Skip shortcut keys when an input is focused (allow typing 'o')
168
+ const inputIsFocused = focusedInput !== null
169
+
170
+ if (key.name === "o" && !inputIsFocused) {
171
+ // Toggle override only when no input is focused
170
172
  this.overrideExisting = !this.overrideExisting
171
173
  overrideText.content = `[O] Override existing: ${this.overrideExisting ? "Yes" : "No"}`
172
174
  overrideText.fg = this.overrideExisting ? "#50fa7b" : "#6272a4"
173
175
  } else if (key.name === "tab") {
174
- // Toggle focus between inputs
176
+ // Cycle focus: username -> password -> no focus (shortcuts work) -> username
175
177
  if (focusedInput === userInput) {
176
178
  userInput.blur()
177
179
  passInput.focus()
178
180
  focusedInput = passInput
179
- } else {
181
+ } else if (focusedInput === passInput) {
180
182
  passInput.blur()
183
+ focusedInput = null // No focus state - shortcuts available
184
+ } else {
185
+ // No input focused, go back to username
181
186
  userInput.focus()
182
187
  focusedInput = userInput
183
188
  }
@@ -186,6 +191,7 @@ export class AppConfigurator extends BoxRenderable {
186
191
  this.cliRenderer.keyInput.off("keypress", this.keyHandler)
187
192
  userInput.blur()
188
193
  passInput.blur()
194
+ focusedInput = null
189
195
  this.currentStep = "configure"
190
196
  this.runConfiguration()
191
197
  } else if (key.name === "return") {
@@ -196,6 +202,7 @@ export class AppConfigurator extends BoxRenderable {
196
202
  this.cliRenderer.keyInput.off("keypress", this.keyHandler)
197
203
  userInput.blur()
198
204
  passInput.blur()
205
+ focusedInput = null
199
206
 
200
207
  // Save credentials to .env
201
208
  this.saveGlobalCredentialsToEnv()
@@ -338,27 +345,25 @@ export class AppConfigurator extends BoxRenderable {
338
345
  }
339
346
 
340
347
  private extractApiKey(appId: AppId): string | null {
341
- const appDef = getApp(appId)
342
- if (!appDef?.apiKeyMeta) return null
343
-
344
- const volumes = appDef.volumes(this.config.rootDir)
345
- if (volumes.length === 0) return null
346
-
347
- const parts = volumes[0].split(":")
348
- const hostPath = parts[0]
349
- const configFilePath = join(hostPath, appDef.apiKeyMeta.configFile)
348
+ // Use API keys from .env file (format: API_KEY_APPNAME)
349
+ try {
350
+ const envPath = getComposePath().replace("docker-compose.yml", ".env")
351
+ if (!existsSync(envPath)) return null
350
352
 
351
- if (!existsSync(configFilePath)) return null
353
+ const content = readFileSync(envPath, "utf-8")
354
+ const envKey = `API_KEY_${appId.toUpperCase()}`
352
355
 
353
- const content = readFileSync(configFilePath, "utf-8")
356
+ for (const line of content.split("\n")) {
357
+ const [key, ...val] = line.split("=")
358
+ if (key?.trim() === envKey && val.length > 0) {
359
+ return val.join("=").trim()
360
+ }
361
+ }
354
362
 
355
- if (appDef.apiKeyMeta.parser === "regex") {
356
- const regex = new RegExp(appDef.apiKeyMeta.selector)
357
- const match = regex.exec(content)
358
- return match?.[1] || null
363
+ return null
364
+ } catch {
365
+ return null
359
366
  }
360
-
361
- return null
362
367
  }
363
368
 
364
369
  private renderConfigProgress() {