yt-dlp-bridge 0.1.6 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +217 -87
  2. package/dist/config.d.ts.map +1 -1
  3. package/dist/config.js +33 -14
  4. package/dist/config.js.map +1 -1
  5. package/dist/environment.d.ts +2 -2
  6. package/dist/environment.d.ts.map +1 -1
  7. package/dist/environment.js +26 -52
  8. package/dist/environment.js.map +1 -1
  9. package/dist/errors.d.ts +1 -1
  10. package/dist/errors.d.ts.map +1 -1
  11. package/dist/errors.js +25 -43
  12. package/dist/errors.js.map +1 -1
  13. package/dist/filesystem.d.ts +7 -3
  14. package/dist/filesystem.d.ts.map +1 -1
  15. package/dist/filesystem.js +20 -19
  16. package/dist/filesystem.js.map +1 -1
  17. package/dist/generated/ytdlp-options.json +2087 -5511
  18. package/dist/index.d.ts +3 -12
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +3 -12
  21. package/dist/index.js.map +1 -1
  22. package/dist/option-catalog.d.ts +4 -3
  23. package/dist/option-catalog.d.ts.map +1 -1
  24. package/dist/option-catalog.js +69 -71
  25. package/dist/option-catalog.js.map +1 -1
  26. package/dist/output-events.d.ts +42 -0
  27. package/dist/output-events.d.ts.map +1 -0
  28. package/dist/output-events.js +65 -0
  29. package/dist/output-events.js.map +1 -0
  30. package/dist/parsers.d.ts +2 -1
  31. package/dist/parsers.d.ts.map +1 -1
  32. package/dist/parsers.js +90 -98
  33. package/dist/parsers.js.map +1 -1
  34. package/dist/redaction.d.ts +5 -3
  35. package/dist/redaction.d.ts.map +1 -1
  36. package/dist/redaction.js +48 -21
  37. package/dist/redaction.js.map +1 -1
  38. package/dist/runner.d.ts +1 -1
  39. package/dist/runner.d.ts.map +1 -1
  40. package/dist/runner.js +27 -31
  41. package/dist/runner.js.map +1 -1
  42. package/dist/schemas.d.ts +482 -40
  43. package/dist/schemas.d.ts.map +1 -1
  44. package/dist/schemas.js +47 -42
  45. package/dist/schemas.js.map +1 -1
  46. package/dist/types.d.ts +5 -5
  47. package/dist/types.d.ts.map +1 -1
  48. package/dist/workflow.d.ts +168 -0
  49. package/dist/workflow.d.ts.map +1 -0
  50. package/dist/workflow.js +827 -0
  51. package/dist/workflow.js.map +1 -0
  52. package/package.json +88 -72
  53. package/dist/argv.d.ts +0 -18
  54. package/dist/argv.d.ts.map +0 -1
  55. package/dist/argv.js +0 -276
  56. package/dist/argv.js.map +0 -1
  57. package/dist/planner.d.ts +0 -10
  58. package/dist/planner.d.ts.map +0 -1
  59. package/dist/planner.js +0 -233
  60. package/dist/planner.js.map +0 -1
package/README.md CHANGED
@@ -1,16 +1,20 @@
1
1
  # yt-dlp-bridge
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/yt-dlp-bridge.svg)](https://www.npmjs.com/package/yt-dlp-bridge)
4
- [![npm downloads](https://img.shields.io/npm/dm/yt-dlp-bridge.svg)](https://www.npmjs.com/package/yt-dlp-bridge)
5
- [![CI](https://github.com/antonio-orionus/yt-dlp-bridge/actions/workflows/ci.yml/badge.svg)](https://github.com/antonio-orionus/yt-dlp-bridge/actions/workflows/ci.yml)
3
+ [![registry version](https://img.shields.io/npm/v/yt-dlp-bridge.svg)](https://www.npmjs.com/package/yt-dlp-bridge)
4
+ [![registry downloads](https://img.shields.io/npm/dm/yt-dlp-bridge.svg)](https://www.npmjs.com/package/yt-dlp-bridge)
5
+ [![CI](https://github.com/antonio-orionus/Arroxy/actions/workflows/ci.yml/badge.svg)](https://github.com/antonio-orionus/Arroxy/actions/workflows/ci.yml)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
7
 
8
- TypeScript library for safe, no-shell `yt-dlp` integration in Node.js services: policy-aware command planning, argv generation, environment checks, process execution, output parsing, secret redaction, and structured errors.
8
+ TypeScript library for safe, no-shell `yt-dlp` integration in Node.js services: workflow planning, argv generation, command facts, environment checks, process execution, output parsing, secret redaction, filesystem policy, and structured errors.
9
9
 
10
- `yt-dlp-bridge` is intentionally MCP-agnostic. It is the integration layer used by `yt-dlp-mcp-server`, but it works anywhere a Node.js service needs to plan or run `yt-dlp` without shell interpolation.
10
+ `yt-dlp-bridge` is intentionally app-agnostic. It is used by Arroxy and `yt-dlp-mcp-server`, but it does not own Electron token minting, binary resolution, retries, queue lifecycle, settings stores, plugin installation, or UI progress policy.
11
+
12
+ Source and contributions live in the Arroxy monorepo at [`packages/yt-dlp-bridge`](https://github.com/antonio-orionus/Arroxy/tree/main/packages/yt-dlp-bridge).
11
13
 
12
14
  - [Install](#install)
13
15
  - [Features](#features)
16
+ - [What changed in 0.3.0](#what-changed-in-030)
17
+ - [What changed in 0.2.0](#what-changed-in-020)
14
18
  - [Usage](#usage)
15
19
  - [API](#api)
16
20
  - [Configuration](#configuration)
@@ -22,100 +26,212 @@ TypeScript library for safe, no-shell `yt-dlp` integration in Node.js services:
22
26
  ## Install
23
27
 
24
28
  ```bash
25
- npm install yt-dlp-bridge
29
+ bun add yt-dlp-bridge
26
30
  # pnpm add yt-dlp-bridge
27
31
  # yarn add yt-dlp-bridge
28
- # bun add yt-dlp-bridge
29
32
  ```
30
33
 
31
34
  Requires Node.js >= 22.13 and ESM.
32
35
 
33
- ```ts
34
- import { PlanDownloadInputSchema, loadConfig, planDownload } from "yt-dlp-bridge";
35
-
36
- const config = loadConfig();
37
- const input = PlanDownloadInputSchema.parse({
38
- url: "https://www.youtube.com/watch?v=BaW_jenozKc",
39
- kind: "audio",
40
- postprocess: { audioFormat: "mp3" },
41
- output: { outputRoot: "audio" }
42
- });
43
-
44
- const plan = await planDownload(input, config);
45
- console.log(plan.commandPreview.redactedArgs);
46
- console.log(plan.requiredDependencies);
47
- ```
48
-
49
36
  ## Features
50
37
 
51
- - Source-derived option catalog generated from `yt_dlp.options.create_parser`.
52
- - Safe argv builders that return argument arrays instead of shell command strings.
53
- - Dependency-aware download and post-processing plans for `yt-dlp`, `ffmpeg`, `ffprobe`, external downloaders, browser impersonation, and JavaScript runtimes.
38
+ - One canonical `planWorkflow(input, options?)` planner with caller-owned app inputs and managed/server schema inputs.
39
+ - Caller-owned probe/media/subtitle inputs for apps that already resolved output directories, format selection, subtitles, embeds, resume info JSON, and audio conversion policy.
40
+ - Managed schema inputs for inspect, media, audio, subtitles, thumbnail, playlist, postprocess, and expert/raw workflows that need bridge-owned normalization.
41
+ - Workflow facts for media-vs-sidecar behavior, output roots/templates, playlist sentinel scope, subtitle format, dependencies, risks, and side effects.
42
+ - Safe argv arrays instead of shell command strings.
54
43
  - Managed output policy for output roots, temp roots, archive files, overwrite behavior, and parent-directory traversal checks.
55
- - Secret redaction for auth flags, cookies, signed URLs, and command previews.
56
- - Parsers for JSON lines, format lists, subtitle lists, thumbnail lists, progress output, and final output paths.
57
- - Structured error adaptation through [`ytdlp-errors`](https://www.npmjs.com/package/ytdlp-errors).
44
+ - Secret redaction for auth flags, cookies, PO tokens, visitor data, proxies, signed URLs, and command previews.
45
+ - Parsers for JSON lines, format lists, subtitle lists, thumbnail lists, progress output, final output paths, and live yt-dlp output events.
46
+ - Low-level process runner using `spawn` with `shell: false`, bounded output capture, timeouts, and structured failures.
47
+ - Source-derived option catalog generated from `yt_dlp.options.create_parser`.
58
48
  - ESM and TypeScript declarations.
59
49
 
50
+ ## What changed in 0.3.0
51
+
52
+ - `parseYtDlpOutputLine` emits two new event kinds: `artifact` (a written description, info JSON, or thumbnail sidecar, with its `path`) and `transfer-retry` (yt-dlp's in-process `Got error: ... Retrying (n/m)` line, with `attempt` and `total`). `postprocess` events now carry the output `path` when yt-dlp prints one. Exhaustive `switch` statements over `YtDlpOutputEvent` need the two new cases.
53
+ - Media plans always pass `--socket-timeout`. `DownloadRetryPolicy` gains an optional `socketTimeout` (seconds), defaulting to yt-dlp's own 20s via `DEFAULT_SOCKET_TIMEOUT_SECONDS`.
54
+ - A media plan that replays `resume.loadInfoJsonPath` without an explicit `downloadRetryPolicy` now uses `INFO_JSON_DOWNLOAD_RETRY_POLICY`: 3 retries and a 15s socket timeout, so a pinned, unreachable CDN host fails in about a minute instead of about seven. Fragment retries keep the default budget. Pass `downloadRetryPolicy` to keep the previous behavior.
55
+ - `redactText`, `redactArgs`, and `excerpt` accept an optional `env` argument (default `process.env`). Setting `YTDLP_MCP_UNREDACTED=true` disables redaction and excerpt truncation for local debugging. `NODE_ENV` has no effect on redaction.
56
+
57
+ ## What changed in 0.2.0
58
+
59
+ `0.2.0` keeps `planWorkflow` as the single planner, but separates the public input surface into two paths:
60
+
61
+ - Use caller-owned inputs (`ProbeWorkflowInput`, `CallerMediaWorkflowInput`, `CallerSubtitlesWorkflowInput`) when your app owns job state, output folders, format selectors, subtitle mode, resume metadata, and user-facing workflow policy.
62
+ - Use managed Zod schemas (`WorkflowDownloadInputSchema`, `WorkflowInspectInputSchema`, `WorkflowPostprocessInputSchema`, `WorkflowExpertInputSchema`) when a server integration wants the bridge to normalize config, auth, network, output roots, and yt-dlp workflow options from request-like input.
63
+
64
+ Migration notes for `0.1.x` consumers:
65
+
66
+ - If you used managed download schemas only to plan app-owned downloads, map your app job to `CallerMediaWorkflowInput` or `CallerSubtitlesWorkflowInput` instead.
67
+ - `WorkflowInputSchema` validates the managed/server schema family. Caller-owned app inputs are TypeScript interfaces, so validate local job state before mapping it into the bridge.
68
+ - Media planning now includes app-oriented resume hardening, including `--continue`, chunk sizing, temp info JSON output, and `resume.loadInfoJsonPath` planning that omits the original URL.
69
+ - Embedded subtitle planning forces the safe MKV merge container and suppresses thumbnail embedding when needed for subtitle muxing.
70
+ - App runtime decisions still stay outside the package: binary selection, token minting, retries, queue lifecycle, cancellation UI, progress display, and settings persistence remain caller-owned.
71
+
60
72
  ## Usage
61
73
 
62
- ### Inspect metadata
74
+ Choose the input style based on ownership. App integrations should usually start with caller-owned inputs. MCP/server-style integrations should usually start with managed schemas.
75
+
76
+ ### Plan from a caller-owned app adapter
63
77
 
64
- Use the Zod schemas to fill defaults before passing inputs into the low-level builders.
78
+ App adapters should map local state into `WorkflowInput`, then keep app-owned orchestration outside the bridge.
65
79
 
66
80
  ```ts
67
- import { InspectInputSchema, buildMetadataArgs } from "yt-dlp-bridge";
81
+ import { planWorkflow, type CallerMediaWorkflowInput } from "yt-dlp-bridge";
82
+
83
+ function toWorkflowInput(job: PreparedJob): CallerMediaWorkflowInput {
84
+ return {
85
+ kind: "media",
86
+ url: job.url,
87
+ output: { directory: job.outputDir, tempDirectory: job.tempDir },
88
+ selection: {
89
+ formatSelector: job.formatSelector,
90
+ mergeOutputFormat: job.mergeOutputFormat
91
+ },
92
+ subtitles: job.embedSubtitles
93
+ ? { embed: true, languages: job.subtitleLanguages, writeAuto: job.writeAutoSubtitles }
94
+ : undefined,
95
+ embed: {
96
+ metadata: job.embedMetadata,
97
+ thumbnail: job.embedThumbnail,
98
+ chapters: job.embedChapters
99
+ },
100
+ resume: job.infoJsonPath ? { loadInfoJsonPath: job.infoJsonPath } : undefined
101
+ };
102
+ }
68
103
 
69
- const input = InspectInputSchema.parse({
70
- url: "https://www.youtube.com/watch?v=BaW_jenozKc"
71
- });
72
- const args = buildMetadataArgs(input);
73
- // ["--ignore-config", "--no-warnings", "--dump-json", "https://www.youtube.com/watch?v=BaW_jenozKc"]
104
+ const plan = planWorkflow(toWorkflowInput(job));
105
+
106
+ console.log(plan.args);
107
+ console.log(plan.facts.sideEffects);
74
108
  ```
75
109
 
76
- ### Plan a download
110
+ The bridge owns yt-dlp semantics such as audio conversion arguments, subtitle/embed container rules, playlist sentinel logic, dependency reasons, risk/side-effect facts, output path facts, resume argv, and redaction. The adapter owns local job models, runtime selection, retries, token minting, cancellation, progress policy, and user-facing status.
77
111
 
78
- `planDownload` resolves output policy, builds the final argv, redacts secrets, checks available dependencies, and explains risks and side effects before anything is downloaded.
112
+ ### Plan a managed workflow
113
+
114
+ Use managed schemas when a consumer accepts request-like input and wants the bridge to normalize defaults before planning a stable `yt-dlp` argv plus facts.
79
115
 
80
116
  ```ts
81
- import { PlanDownloadInputSchema, loadConfig, planDownload } from "yt-dlp-bridge";
117
+ import { WorkflowDownloadInputSchema, planWorkflow } from "yt-dlp-bridge";
118
+ import { loadConfig } from "yt-dlp-bridge/config";
82
119
 
83
120
  const config = loadConfig({
84
121
  YTDLP_MCP_OUTPUT_ROOT: "/srv/downloads",
85
122
  YTDLP_MCP_TEMP_ROOT: "/srv/downloads/.tmp"
86
123
  });
87
- const input = PlanDownloadInputSchema.parse({
124
+
125
+ const input = WorkflowDownloadInputSchema.parse({
126
+ kind: "audio",
88
127
  url: "https://www.youtube.com/watch?v=BaW_jenozKc",
89
- kind: "video",
90
- selection: { downloadArchive: "archive.txt" },
91
- format: { format: "bv*+ba/b" },
92
- output: { outputTemplate: "%(playlist_index)s-%(title).180B [%(id)s].%(ext)s" }
128
+ postprocess: { audioFormat: "mp3" },
129
+ output: { outputTemplate: "%(title).180B [%(id)s].%(ext)s" }
93
130
  });
94
131
 
95
- const plan = await planDownload(input, config);
96
- console.log(plan.argv);
97
- console.log(plan.risks);
98
- console.log(plan.sideEffects);
132
+ const plan = planWorkflow(input, {
133
+ config,
134
+ configFiles: { mode: "disabled" },
135
+ plugins: { mode: "native" }
136
+ });
137
+
138
+ console.log(plan.args);
139
+ console.log(plan.redactedArgs);
140
+ console.log(plan.facts.dependencies.required);
141
+ ```
142
+
143
+ Managed schemas are the runtime-validation layer for this path:
144
+
145
+ ```ts
146
+ import { WorkflowInputSchema } from "yt-dlp-bridge";
147
+
148
+ const input = WorkflowInputSchema.parse(request.body);
149
+ const plan = planWorkflow(input, { config });
150
+ ```
151
+
152
+ `WorkflowPlan` has this shape:
153
+
154
+ ```ts
155
+ {
156
+ kind: "probe" | "inspect" | "media" | "audio" | "subtitles" | "thumbnail" | "playlist" | "postprocess" | "expert",
157
+ args: string[],
158
+ redactedArgs: string[],
159
+ facts: {
160
+ isMediaDownload: boolean,
161
+ output?: {
162
+ directory?: string,
163
+ tempDirectory?: string,
164
+ outputRoot?: string,
165
+ tempRoot?: string,
166
+ template?: string,
167
+ allowOverwrite?: boolean
168
+ },
169
+ playlistScope?: {
170
+ requestedCount: number,
171
+ sentinel: boolean,
172
+ ytDlpFlag: "--playlist-end" | "--playlist-items",
173
+ ytDlpValue: string
174
+ },
175
+ effectiveSubtitleFormat?: "srt" | "vtt" | "ass",
176
+ dependencies: { required: unknown[], optional: unknown[] },
177
+ risks: string[],
178
+ sideEffects: string[]
179
+ }
180
+ }
181
+ ```
182
+
183
+ ### Probe playlists
184
+
185
+ Probe planning owns playlist mode and sentinel range behavior.
186
+
187
+ ```ts
188
+ import { planWorkflow } from "yt-dlp-bridge";
189
+
190
+ const probe = planWorkflow({
191
+ kind: "probe",
192
+ url: "https://www.youtube.com/playlist?list=...",
193
+ selection: {
194
+ playlistMode: "playlist",
195
+ playlistScope: { items: { kind: "first", count: 50 } }
196
+ }
197
+ });
198
+
199
+ console.log(probe.args);
200
+ console.log(probe.facts.playlistScope);
201
+ ```
202
+
203
+ ### Parse live yt-dlp output
204
+
205
+ Use `parseYtDlpOutputLine` to turn noisy output into stable events. App-specific adapters can map these events to progress bars, queue resume evidence, logs, or MCP notifications.
206
+
207
+ ```ts
208
+ import { parseYtDlpOutputLine } from "yt-dlp-bridge/parsers";
209
+
210
+ const event = parseYtDlpOutputLine('[Merger] Merging formats into "/tmp/video.mp4"');
211
+
212
+ if (event?.kind === "merge") {
213
+ console.log(event.path);
214
+ }
99
215
  ```
100
216
 
101
217
  ### Run a planned command
102
218
 
103
- The runner uses `spawn` with `shell: false`, bounded output capture, timeouts, and structured failures.
219
+ The bridge runner is low-level. It executes a command path plus argv; it does not resolve binaries or decide retries.
104
220
 
105
221
  ```ts
106
- import { PlanDownloadInputSchema, loadConfig, planDownload, runCommand, toStructuredError } from "yt-dlp-bridge";
222
+ import { planWorkflow } from "yt-dlp-bridge";
223
+ import { runCommand } from "yt-dlp-bridge/runner";
224
+ import { toStructuredError } from "yt-dlp-bridge/errors";
107
225
 
108
- const config = loadConfig();
109
- const input = PlanDownloadInputSchema.parse({
110
- url: "https://www.youtube.com/watch?v=BaW_jenozKc",
111
- kind: "video"
226
+ const plan = planWorkflow({
227
+ kind: "media",
228
+ url: "https://www.youtube.com/watch?v=BaW_jenozKc"
112
229
  });
113
- const plan = await planDownload(input, config);
114
230
 
115
231
  try {
116
- const result = await runCommand(plan.commandPreview.command, plan.commandPreview.args, {
117
- timeoutMs: config.defaultTimeoutMs,
118
- maxOutputBytes: config.maxOutputBytes
232
+ const result = await runCommand("yt-dlp", plan.args, {
233
+ timeoutMs: 900_000,
234
+ maxOutputBytes: 4 * 1024 * 1024
119
235
  });
120
236
  console.log(result.stdout);
121
237
  } catch (error) {
@@ -128,27 +244,39 @@ try {
128
244
  The bundled catalog is generated from `yt_dlp.options.create_parser` for yt-dlp `2026.03.17` and contains 323 upstream options.
129
245
 
130
246
  ```ts
131
- import { UPSTREAM_OPTION_CATALOG, findOptionByFlag, listLongFlags, optionMetadata } from "yt-dlp-bridge";
247
+ import { UPSTREAM_OPTION_CATALOG, findOptionByFlag, listLongFlags, optionMetadata } from "yt-dlp-bridge/option-catalog";
132
248
 
133
249
  const format = findOptionByFlag("--format");
134
- console.log(UPSTREAM_OPTION_CATALOG.optionCount); // 323
135
- console.log(listLongFlags().includes("--dump-json")); // true
250
+ console.log(UPSTREAM_OPTION_CATALOG.optionCount);
251
+ console.log(listLongFlags().includes("--dump-json"));
136
252
  console.log(format ? optionMetadata(format) : undefined);
137
253
  ```
138
254
 
139
255
  ## API
140
256
 
257
+ Prefer the root export for workflow planning and subpaths for lower-level utilities.
258
+
141
259
  | Area | Exports |
142
260
  | --- | --- |
143
- | Argv builders | `ytdlpCommand`, `buildMetadataArgs`, `buildSingleJsonArgs`, `buildListArgs`, `buildSimulationArgs`, `buildDownloadArgs` |
144
- | Planning | `planDownload`, `planPostprocess` |
145
- | Config and environment | `loadConfig`, `CONFIG`, `checkEnvironment` |
146
- | Filesystem policy | `resolveOutputPolicy`, `resolveManagedPath`, `ensureWithinRoot`, `isPathInside`, `isPathInsideWith`, `hasParentTraversal`, `ensureDirectory`, `readArchive` |
147
- | Option catalog | `UPSTREAM_OPTION_CATALOG`, `listLongFlags`, `findOptionByFlag`, `optionMetadata` |
148
- | Parsers | `parseJsonLines`, `sanitizeMetadataItem`, `sanitizeMetadataItems`, `paginate`, `parseFormats`, `parseSubtitles`, `parseThumbnails`, `parseProgress`, `parseFinalPaths` |
149
- | Process and errors | `runCommand`, `CommandExecutionError`, `toStructuredError` |
150
- | Redaction | `redactArgs`, `redactText`, `excerpt` |
151
- | Schemas and types | Zod input schemas and TypeScript interfaces |
261
+ | Workflow planning | `planWorkflow`, workflow input/plan/fact types |
262
+ | Schemas | `WorkflowInspectInputSchema`, `WorkflowDownloadInputSchema`, `WorkflowExecutionInputSchema`, `WorkflowPostprocessInputSchema`, `WorkflowExpertInputSchema`, `WorkflowInputSchema`, `ValidateWorkflowInputSchema` |
263
+ | Config | `loadConfig`, `CONFIG` from `yt-dlp-bridge/config` |
264
+ | Environment | `checkEnvironment` from `yt-dlp-bridge/environment` |
265
+ | Filesystem policy | `resolveOutputPolicy`, `resolveManagedPath`, `ensureWithinRoot`, `isPathInside`, `isPathInsideWith`, `hasParentTraversal`, `ensureDirectory`, `readArchive` from `yt-dlp-bridge/filesystem` |
266
+ | Option catalog | `UPSTREAM_OPTION_CATALOG`, `listLongFlags`, `findOptionByFlag`, `optionMetadata` from `yt-dlp-bridge/option-catalog` |
267
+ | Parsers | `parseYtDlpOutputLine`, `parseJsonLines`, `sanitizeMetadataItem`, `sanitizeMetadataItems`, `paginate`, `parseFormats`, `parseSubtitles`, `parseThumbnails`, `parseProgress`, `parseFinalPaths` from `yt-dlp-bridge/parsers` |
268
+ | Runner | `runCommand`, `CommandExecutionError` from `yt-dlp-bridge/runner` |
269
+ | Redaction | `redactArgs`, `redactText`, `excerpt` from `yt-dlp-bridge/redaction` |
270
+ | Structured errors | `toStructuredError` from `yt-dlp-bridge/errors` |
271
+
272
+ Example imports:
273
+
274
+ ```ts
275
+ import { planWorkflow } from "yt-dlp-bridge";
276
+ import { parseYtDlpOutputLine } from "yt-dlp-bridge/parsers";
277
+ import { runCommand } from "yt-dlp-bridge/runner";
278
+ import { redactArgs } from "yt-dlp-bridge/redaction";
279
+ ```
152
280
 
153
281
  ## Configuration
154
282
 
@@ -170,42 +298,44 @@ console.log(format ? optionMetadata(format) : undefined);
170
298
  | `YTDLP_MCP_TIMEOUT_MS` | `900000` | Default command timeout (ms). |
171
299
  | `YTDLP_MCP_MAX_OUTPUT_BYTES` | `4194304` | Maximum retained stdout/stderr bytes. |
172
300
  | `YTDLP_MCP_JS_RUNTIMES` | `deno,node,bun,quickjs` | JavaScript runtimes considered for extractor challenges. |
301
+ | `YTDLP_MCP_UNREDACTED` | `false` | Disable secret redaction and excerpt truncation. Local debugging only: logs will contain tokens, cookies, and credentials. |
173
302
 
174
303
  ## Requirements
175
304
 
176
305
  - Node.js >= 22.13, ESM.
177
- - `yt-dlp` installed when planning or running yt-dlp workflows.
306
+ - `yt-dlp` installed when running yt-dlp workflows.
178
307
  - `ffmpeg` and `ffprobe` for audio extraction, merge, remux, recode, segment editing, subtitle/thumbnail conversion, and embedding workflows.
179
- - Python `yt_dlp` package (or `PYTHONPATH` pointing at a yt-dlp checkout) when regenerating or verifying the option catalog.
308
+ - Python `yt_dlp` package, or `PYTHONPATH` pointing at a yt-dlp checkout, when regenerating or verifying the option catalog.
180
309
 
181
310
  ## Development
182
311
 
183
312
  ```bash
184
- git clone https://github.com/antonio-orionus/yt-dlp-bridge.git
185
- cd yt-dlp-bridge
186
- pnpm install
313
+ git clone https://github.com/antonio-orionus/Arroxy.git
314
+ cd Arroxy
315
+ mise install # recommended; skip if you manually activated the pinned tools
316
+ bun run bootstrap
187
317
  ```
188
318
 
189
319
  | Command | Description |
190
320
  | --- | --- |
191
- | `pnpm run build` | Compile TypeScript and copy generated assets into `dist`. |
192
- | `pnpm test` | Run Vitest tests. |
193
- | `pnpm run typecheck` | TypeScript check without emitting. |
194
- | `pnpm run generate:options` | Regenerate the option catalog from Python `yt_dlp`. |
195
- | `pnpm run verify:options` | Verify the catalog matches Python `yt_dlp`. |
196
- | `pnpm pack --dry-run` | Preview package contents before publishing. |
321
+ | `bun run --filter yt-dlp-bridge build` | Compile TypeScript and copy generated assets into `dist`. |
322
+ | `bun run --filter yt-dlp-bridge test` | Run Vitest tests. |
323
+ | `bun run --filter yt-dlp-bridge typecheck` | TypeScript check without emitting. |
324
+ | `bun run --filter yt-dlp-bridge generate:options` | Regenerate the option catalog from Python `yt_dlp`. |
325
+ | `bun run --filter yt-dlp-bridge verify:options` | Verify the catalog matches Python `yt_dlp`. |
326
+ | `bun run bridge:check` | Run the package gate used by Arroxy CI and registry publishing. |
197
327
 
198
328
  ## Troubleshooting
199
329
 
200
- **`yt-dlp` is missing** — Install `yt-dlp` or set `YTDLP_MCP_YTDLP_PATH`. `checkEnvironment()` reports detected command, status, version, and notes.
330
+ **`yt-dlp` is missing** - Install `yt-dlp` or set `YTDLP_MCP_YTDLP_PATH`. `checkEnvironment()` reports detected command, status, version, and notes.
201
331
 
202
- **`ffmpeg`/`ffprobe` missing** — Install the binary or set `YTDLP_MCP_FFMPEG_PATH` / `YTDLP_MCP_FFPROBE_PATH`. Planning still returns dependency info so callers can surface which workflow requires the missing tool.
332
+ **`ffmpeg`/`ffprobe` missing** - Install the binary or set `YTDLP_MCP_FFMPEG_PATH` / `YTDLP_MCP_FFPROBE_PATH`. Planning still returns dependency info so callers can surface which workflow requires the missing tool.
203
333
 
204
- **`auth.cookiesFile requires YTDLP_MCP_ALLOW_CONFIG_FILES=true`** — User-provided config-file paths are blocked by default. Prefer server-level `YTDLP_MCP_COOKIES_FILE` or enable `YTDLP_MCP_ALLOW_CONFIG_FILES=true` only after reviewing the trust boundary.
334
+ **`auth.cookiesFile requires YTDLP_MCP_ALLOW_CONFIG_FILES=true`** - User-provided config-file paths are blocked by default. Prefer server-level `YTDLP_MCP_COOKIES_FILE` or enable `YTDLP_MCP_ALLOW_CONFIG_FILES=true` only after reviewing the trust boundary.
205
335
 
206
- **`outputTemplate must not contain parent-directory traversal`** — Relative templates cannot contain `..` segments unless `YTDLP_MCP_ALLOW_ARBITRARY_OUTPUT_PATHS=true`.
336
+ **`outputTemplate must not contain parent-directory traversal`** - Relative templates cannot contain `..` segments unless `YTDLP_MCP_ALLOW_ARBITRARY_OUTPUT_PATHS=true`.
207
337
 
208
- Bugs and feature requests: [GitHub Issues](https://github.com/antonio-orionus/yt-dlp-bridge/issues).
338
+ Bugs and feature requests: [GitHub Issues](https://github.com/antonio-orionus/Arroxy/issues).
209
339
 
210
340
  ## License
211
341
 
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,yBAAyB,EAAE,OAAO,CAAC;IACnC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,eAAe,EAAE,OAAO,CAAC;IACzB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,kBAAkB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAWD,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED,wBAAgB,UAAU,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,YAAY,CAiB7E;AAED,eAAO,MAAM,MAAM,cAAe,CAAC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAChC,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,yBAAyB,EAAE,OAAO,CAAA;IAClC,gBAAgB,EAAE,OAAO,CAAA;IACzB,eAAe,EAAE,OAAO,CAAA;IACxB,gBAAgB,EAAE,OAAO,CAAA;IACzB,gBAAgB,EAAE,MAAM,CAAA;IACxB,cAAc,EAAE,MAAM,CAAA;IACtB,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAChC,kBAAkB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,UAAU,EAAE,MAAM,EAAE,CAAA;CACpB;AA2BD,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED,wBAAgB,UAAU,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,YAAY,CAoB7E;AAED,eAAO,MAAM,MAAM,cAAe,CAAA"}
package/dist/config.js CHANGED
@@ -1,35 +1,54 @@
1
- import os from "node:os";
2
- import path from "node:path";
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
3
  function bool(value) {
4
- return value ? ["1", "true", "yes", "on"].includes(value.toLowerCase()) : false;
4
+ const normalized = blankToUndefined(value);
5
+ return normalized ? ['1', 'true', 'yes', 'on'].includes(normalized.toLowerCase()) : false;
5
6
  }
6
7
  function int(value, fallback) {
7
- const parsed = value ? Number.parseInt(value, 10) : Number.NaN;
8
+ const normalized = blankToUndefined(value);
9
+ const parsed = normalized ? Number.parseInt(normalized, 10) : Number.NaN;
8
10
  return Number.isFinite(parsed) ? parsed : fallback;
9
11
  }
12
+ function blankToUndefined(value) {
13
+ const normalized = value?.trim();
14
+ if (!normalized)
15
+ return undefined;
16
+ return normalized;
17
+ }
18
+ function firstNonBlank(...values) {
19
+ for (const value of values) {
20
+ const normalized = blankToUndefined(value);
21
+ if (normalized !== undefined)
22
+ return normalized;
23
+ }
24
+ return undefined;
25
+ }
10
26
  export function expandHome(input) {
11
- if (input === "~")
27
+ if (input === '~')
12
28
  return os.homedir();
13
- if (input.startsWith("~/") || input.startsWith("~\\"))
29
+ if (input.startsWith('~/') || input.startsWith('~\\'))
14
30
  return path.join(os.homedir(), input.slice(2));
15
31
  return input;
16
32
  }
17
33
  export function loadConfig(env = process.env) {
18
34
  return {
19
- ytdlpPath: env.YTDLP_MCP_YTDLP_PATH ?? env.YTDLP_PATH ?? "yt-dlp",
20
- ffmpegPath: env.YTDLP_MCP_FFMPEG_PATH ?? env.FFMPEG_PATH,
21
- ffprobePath: env.YTDLP_MCP_FFPROBE_PATH ?? env.FFPROBE_PATH,
22
- outputRoot: path.resolve(expandHome(env.YTDLP_MCP_OUTPUT_ROOT ?? path.join(os.homedir(), "Downloads", "yt-dlp-mcp"))),
23
- tempRoot: path.resolve(expandHome(env.YTDLP_MCP_TEMP_ROOT ?? path.join(os.tmpdir(), "yt-dlp-mcp"))),
35
+ ytdlpPath: firstNonBlank(env.YTDLP_MCP_YTDLP_PATH, env.YTDLP_PATH) ?? 'yt-dlp',
36
+ ffmpegPath: firstNonBlank(env.YTDLP_MCP_FFMPEG_PATH, env.FFMPEG_PATH),
37
+ ffprobePath: firstNonBlank(env.YTDLP_MCP_FFPROBE_PATH, env.FFPROBE_PATH),
38
+ outputRoot: path.resolve(expandHome(firstNonBlank(env.YTDLP_MCP_OUTPUT_ROOT) ?? path.join(os.homedir(), 'Downloads', 'yt-dlp-mcp'))),
39
+ tempRoot: path.resolve(expandHome(firstNonBlank(env.YTDLP_MCP_TEMP_ROOT) ?? path.join(os.tmpdir(), 'yt-dlp-mcp'))),
24
40
  allowArbitraryOutputPaths: bool(env.YTDLP_MCP_ALLOW_ARBITRARY_OUTPUT_PATHS),
25
41
  allowConfigFiles: bool(env.YTDLP_MCP_ALLOW_CONFIG_FILES),
26
42
  allowPluginDirs: bool(env.YTDLP_MCP_ALLOW_PLUGIN_DIRS),
27
43
  enableExpertMode: bool(env.YTDLP_MCP_ENABLE_EXPERT),
28
44
  defaultTimeoutMs: int(env.YTDLP_MCP_TIMEOUT_MS, 15 * 60 * 1000),
29
45
  maxOutputBytes: int(env.YTDLP_MCP_MAX_OUTPUT_BYTES, 4 * 1024 * 1024),
30
- cookiesFile: env.YTDLP_MCP_COOKIES_FILE,
31
- cookiesFromBrowser: env.YTDLP_MCP_COOKIES_FROM_BROWSER,
32
- jsRuntimes: (env.YTDLP_MCP_JS_RUNTIMES ?? "deno,node,bun,quickjs").split(",").map((v) => v.trim()).filter(Boolean)
46
+ cookiesFile: firstNonBlank(env.YTDLP_MCP_COOKIES_FILE),
47
+ cookiesFromBrowser: firstNonBlank(env.YTDLP_MCP_COOKIES_FROM_BROWSER),
48
+ jsRuntimes: (firstNonBlank(env.YTDLP_MCP_JS_RUNTIMES) ?? 'deno,node,bun,quickjs').split(',').flatMap(v => {
49
+ const runtime = v.trim();
50
+ return runtime ? [runtime] : [];
51
+ })
33
52
  };
34
53
  }
35
54
  export const CONFIG = loadConfig();
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAmB7B,SAAS,IAAI,CAAC,KAAyB;IACrC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AAClF,CAAC;AAED,SAAS,GAAG,CAAC,KAAyB,EAAE,QAAgB;IACtD,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;IAC/D,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,IAAI,KAAK,KAAK,GAAG;QAAE,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC;IACvC,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACtG,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAyB,OAAO,CAAC,GAAG;IAC7D,OAAO;QACL,SAAS,EAAE,GAAG,CAAC,oBAAoB,IAAI,GAAG,CAAC,UAAU,IAAI,QAAQ;QACjE,UAAU,EAAE,GAAG,CAAC,qBAAqB,IAAI,GAAG,CAAC,WAAW;QACxD,WAAW,EAAE,GAAG,CAAC,sBAAsB,IAAI,GAAG,CAAC,YAAY;QAC3D,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,qBAAqB,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC;QACrH,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,mBAAmB,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC;QACnG,yBAAyB,EAAE,IAAI,CAAC,GAAG,CAAC,sCAAsC,CAAC;QAC3E,gBAAgB,EAAE,IAAI,CAAC,GAAG,CAAC,4BAA4B,CAAC;QACxD,eAAe,EAAE,IAAI,CAAC,GAAG,CAAC,2BAA2B,CAAC;QACtD,gBAAgB,EAAE,IAAI,CAAC,GAAG,CAAC,uBAAuB,CAAC;QACnD,gBAAgB,EAAE,GAAG,CAAC,GAAG,CAAC,oBAAoB,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;QAC/D,cAAc,EAAE,GAAG,CAAC,GAAG,CAAC,0BAA0B,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;QACpE,WAAW,EAAE,GAAG,CAAC,sBAAsB;QACvC,kBAAkB,EAAE,GAAG,CAAC,8BAA8B;QACtD,UAAU,EAAE,CAAC,GAAG,CAAC,qBAAqB,IAAI,uBAAuB,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;KACnH,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { type ServerConfig } from "./config.js";
2
- import type { DetectedDependency } from "./types.js";
1
+ import { type ServerConfig } from './config.js';
2
+ import type { DetectedDependency } from './types.js';
3
3
  export interface EnvironmentReport {
4
4
  platform: NodeJS.Platform;
5
5
  arch: string;
@@ -1 +1 @@
1
- {"version":3,"file":"environment.d.ts","sourceRoot":"","sources":["../src/environment.ts"],"names":[],"mappings":"AAEA,OAAO,EAAU,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAExD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE;QACJ,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,YAAY,EAAE,kBAAkB,EAAE,CAAC;IACnC,OAAO,EAAE;QACP,cAAc,EAAE,OAAO,CAAC;QACxB,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,iBAAiB,EAAE,OAAO,CAAC;QAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,MAAM,EAAE;QACN,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,yBAAyB,EAAE,OAAO,CAAC;QACnC,gBAAgB,EAAE,OAAO,CAAC;QAC1B,eAAe,EAAE,OAAO,CAAC;QACzB,gBAAgB,EAAE,OAAO,CAAC;KAC3B,CAAC;CACH;AAED,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,YAAqB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAiChG"}
1
+ {"version":3,"file":"environment.d.ts","sourceRoot":"","sources":["../src/environment.ts"],"names":[],"mappings":"AAEA,OAAO,EAAS,KAAK,YAAY,EAAC,MAAM,aAAa,CAAA;AAErD,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,YAAY,CAAA;AAElD,MAAM,WAAW,iBAAiB;IACjC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAA;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAC,CAAA;IACzC,YAAY,EAAE,kBAAkB,EAAE,CAAA;IAClC,OAAO,EAAE;QAAC,cAAc,EAAE,OAAO,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAC;QAAC,iBAAiB,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAC,CAAA;IACtG,MAAM,EAAE;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,yBAAyB,EAAE,OAAO,CAAC;QAAC,gBAAgB,EAAE,OAAO,CAAC;QAAC,eAAe,EAAE,OAAO,CAAC;QAAC,gBAAgB,EAAE,OAAO,CAAA;KAAC,CAAA;CAClK;AAED,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,YAAqB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAuBhG"}
@@ -1,80 +1,54 @@
1
- import fs from "node:fs";
2
- import os from "node:os";
3
- import { CONFIG } from "./config.js";
4
- import { runCommand } from "./runner.js";
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import { CONFIG } from './config.js';
4
+ import { runCommand } from './runner.js';
5
5
  export async function checkEnvironment(config = CONFIG) {
6
6
  const dependencies = await Promise.all([
7
- detectBinary("yt-dlp", config.ytdlpPath, ["--version"], ["all yt-dlp operations"], true, config),
8
- detectBinary("ffmpeg", config.ffmpegPath ?? "ffmpeg", ["-version"], ["merge", "remux", "recode", "extract audio", "embed assets"], false, config),
9
- detectBinary("ffprobe", config.ffprobePath ?? "ffprobe", ["-version"], ["audio extraction", "media probing"], false, config),
10
- detectBinary("deno", "deno", ["--version"], ["yt-dlp JavaScript challenges"], false, config),
11
- detectBinary("node", process.execPath, ["--version"], ["yt-dlp JavaScript challenges"], false, config),
12
- detectBinary("bun", "bun", ["--version"], ["yt-dlp JavaScript challenges"], false, config),
13
- detectBinary("qjs", "qjs", ["--version"], ["yt-dlp JavaScript challenges"], false, config),
14
- detectBinary("AtomicParsley", "AtomicParsley", ["--version"], ["legacy thumbnail embedding"], false, config),
15
- detectBinary("aria2c", "aria2c", ["--version"], ["external downloader"], false, config),
16
- detectBinary("curl", "curl", ["--version"], ["external downloader"], false, config),
17
- detectBinary("wget", "wget", ["--version"], ["external downloader"], false, config)
7
+ detectBinary('yt-dlp', config.ytdlpPath, ['--version'], ['all yt-dlp operations'], true, config),
8
+ detectBinary('ffmpeg', config.ffmpegPath ?? 'ffmpeg', ['-version'], ['merge', 'remux', 'recode', 'extract audio', 'embed assets'], false, config),
9
+ detectBinary('ffprobe', config.ffprobePath ?? 'ffprobe', ['-version'], ['audio extraction', 'media probing'], false, config),
10
+ detectBinary('deno', 'deno', ['--version'], ['yt-dlp JavaScript challenges'], false, config),
11
+ detectBinary('node', 'node', ['--version'], ['yt-dlp JavaScript challenges'], false, config),
12
+ detectBinary('bun', 'bun', ['--version'], ['yt-dlp JavaScript challenges'], false, config),
13
+ detectBinary('qjs', 'qjs', ['--version'], ['yt-dlp JavaScript challenges'], false, config),
14
+ detectBinary('AtomicParsley', 'AtomicParsley', ['--version'], ['legacy thumbnail embedding'], false, config),
15
+ detectBinary('aria2c', 'aria2c', ['--version'], ['external downloader'], false, config),
16
+ detectBinary('curl', 'curl', ['--version'], ['external downloader'], false, config),
17
+ detectBinary('wget', 'wget', ['--version'], ['external downloader'], false, config)
18
18
  ]);
19
19
  return {
20
20
  platform: process.platform,
21
21
  arch: os.arch(),
22
- node: {
23
- version: process.version,
24
- execPath: process.execPath
25
- },
22
+ node: { version: process.version, execPath: process.execPath },
26
23
  dependencies,
27
24
  cookies: cookieStatus(config),
28
- policy: {
29
- outputRoot: config.outputRoot,
30
- tempRoot: config.tempRoot,
31
- allowArbitraryOutputPaths: config.allowArbitraryOutputPaths,
32
- allowConfigFiles: config.allowConfigFiles,
33
- allowPluginDirs: config.allowPluginDirs,
34
- enableExpertMode: config.enableExpertMode
35
- }
25
+ policy: { outputRoot: config.outputRoot, tempRoot: config.tempRoot, allowArbitraryOutputPaths: config.allowArbitraryOutputPaths, allowConfigFiles: config.allowConfigFiles, allowPluginDirs: config.allowPluginDirs, enableExpertMode: config.enableExpertMode }
36
26
  };
37
27
  }
38
28
  async function detectBinary(name, command, args, requiredFor, required, config) {
39
29
  try {
40
- const result = await runCommand(command, args, {
41
- timeoutMs: Math.min(config.defaultTimeoutMs, 10_000),
42
- maxOutputBytes: 64 * 1024
43
- });
44
- return {
45
- name,
46
- status: "available",
47
- command,
48
- version: firstLine(result.stdout || result.stderr),
49
- requiredFor,
50
- notes: required ? [] : ["Optional dependency; required only for matching workflows."]
51
- };
30
+ const result = await runCommand(command, args, { timeoutMs: Math.min(config.defaultTimeoutMs, 10_000), maxOutputBytes: 64 * 1024 });
31
+ return { name, status: 'available', command, version: firstLine(result.stdout || result.stderr), requiredFor, notes: required ? [] : ['Optional dependency; required only for matching workflows.'] };
52
32
  }
53
33
  catch (error) {
54
- return {
55
- name,
56
- status: "missing",
57
- command,
58
- requiredFor,
59
- notes: [required ? "Required dependency is not available." : "Optional dependency is not available.", error instanceof Error ? error.message : String(error)]
60
- };
34
+ return { name, status: 'missing', command, requiredFor, notes: [required ? 'Required dependency is not available.' : 'Optional dependency is not available.', error instanceof Error ? error.message : String(error)] };
61
35
  }
62
36
  }
63
37
  function cookieStatus(config) {
64
- const status = {
65
- fileConfigured: Boolean(config.cookiesFile),
66
- browserConfigured: Boolean(config.cookiesFromBrowser)
67
- };
38
+ const status = { fileConfigured: Boolean(config.cookiesFile), browserConfigured: Boolean(config.cookiesFromBrowser) };
68
39
  if (config.cookiesFile)
69
40
  status.fileExists = fs.existsSync(config.cookiesFile);
70
41
  if (config.cookiesFromBrowser) {
71
- const browser = config.cookiesFromBrowser.split(":")[0];
42
+ const browser = config.cookiesFromBrowser.split(':')[0];
72
43
  if (browser)
73
44
  status.browser = browser;
74
45
  }
75
46
  return status;
76
47
  }
77
48
  function firstLine(value) {
78
- return value.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? "";
49
+ return (value
50
+ .split(/\r?\n/)
51
+ .map(line => line.trim())
52
+ .find(Boolean) ?? '');
79
53
  }
80
54
  //# sourceMappingURL=environment.js.map