veodl 1.12.1 → 2.0.1

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 (61) hide show
  1. package/AGENTS.md +12 -12
  2. package/CHANGELOG.md +404 -373
  3. package/LICENSE +21 -21
  4. package/README.md +1040 -910
  5. package/bin/veo.js +47 -2
  6. package/docs/AGENT_GUIDE.md +135 -122
  7. package/native/android-arm64/veo +0 -0
  8. package/native/darwin-arm64/veo +0 -0
  9. package/native/darwin-x64/veo +0 -0
  10. package/native/linux-arm64/veo +0 -0
  11. package/native/linux-x64/veo +0 -0
  12. package/native/win32-arm64/veo.exe +0 -0
  13. package/native/win32-x64/veo.exe +0 -0
  14. package/package.json +53 -57
  15. package/src/alias.js +0 -258
  16. package/src/backend-update.js +0 -191
  17. package/src/backend.js +0 -572
  18. package/src/changes.js +0 -278
  19. package/src/cli.js +0 -785
  20. package/src/compatibility.js +0 -39
  21. package/src/config-clipboard.js +0 -67
  22. package/src/config-diagnostics.js +0 -155
  23. package/src/config-editor.js +0 -404
  24. package/src/config-errors.js +0 -64
  25. package/src/config-reset.js +0 -39
  26. package/src/config-template.js +0 -227
  27. package/src/config.js +0 -307
  28. package/src/disk-space.js +0 -68
  29. package/src/doctor.js +0 -306
  30. package/src/download-cache.js +0 -31
  31. package/src/downloader.js +0 -704
  32. package/src/execution.js +0 -68
  33. package/src/experimental-fragments.js +0 -29
  34. package/src/flush.js +0 -66
  35. package/src/history.js +0 -173
  36. package/src/inline-region.js +0 -72
  37. package/src/inspect-media.js +0 -179
  38. package/src/interactive.js +0 -120
  39. package/src/jobs.js +0 -218
  40. package/src/legacy-config-comments.js +0 -106
  41. package/src/media-assets.js +0 -272
  42. package/src/naming.js +0 -54
  43. package/src/open-file.js +0 -15
  44. package/src/option-suggestions.js +0 -43
  45. package/src/output.js +0 -81
  46. package/src/path-links.js +0 -53
  47. package/src/paths.js +0 -26
  48. package/src/playlist.js +0 -31
  49. package/src/progress.js +0 -341
  50. package/src/run-archive.js +0 -148
  51. package/src/runs.js +0 -346
  52. package/src/source-discovery.js +0 -305
  53. package/src/state.js +0 -27
  54. package/src/stats.js +0 -63
  55. package/src/terminal-size.js +0 -8
  56. package/src/terminal-title.js +0 -19
  57. package/src/tool-setup.js +0 -289
  58. package/src/uninstall.js +0 -222
  59. package/src/updater.js +0 -229
  60. package/src/utils.js +0 -208
  61. package/src/version.js +0 -38
package/README.md CHANGED
@@ -1,910 +1,1040 @@
1
- # veo
2
-
3
- A small, clean video-downloading CLI powered by [yt-dlp](https://github.com/yt-dlp/yt-dlp). No banners, just progress and the saved file path.
4
-
5
- Automating with an agent? Start with the [agent guide](docs/AGENT_GUIDE.md) for JSON output, run inspection and audio verification.
6
-
7
- **Only download content you own or are authorized or legally permitted to download.** Respect copyright, website terms, and access restrictions. veo does not bypass DRM, private-content access controls, or paywalls.
8
-
9
- ## Install and run
10
-
11
- Requires **Node.js 22+** and npm. Internet access is needed for installation and first-use backend setup.
12
-
13
- The npm package is `veodl`; the installed commands are `veo` and `veodl`.
14
-
15
- ```bash
16
- npx veodl "https://example.com/video.mp4"
17
- ```
18
-
19
- Or install globally:
20
-
21
- ```bash
22
- npm install -g veodl
23
- veo "https://example.com/video.mp4"
24
- # or: veodl "https://example.com/video.mp4"
25
- ```
26
-
27
- The first download automatically prepares missing yt-dlp, FFmpeg and FFprobe.
28
- On Android/Termux this includes installing the native packages and yt-dlp's
29
- JavaScript support with `pkg install -y`. On desktop systems, missing media tools
30
- are downloaded into veo's own cache. Run `veo doctor fix` to prepare everything
31
- before downloading. Plain `veo doctor`, help and version do not install tools.
32
-
33
- From this checkout, without publishing:
34
-
35
- ```bash
36
- npm install
37
- node bin/veo.js --help
38
- npm link
39
- veo "https://example.com/video.mp4"
40
- ```
41
-
42
- `package.json` maps `"veo": "./bin/veo.js"`; npm creates the executable/shim automatically. No custom command prefix is needed. Use only trusted URLs, and quote URLs to protect query strings from your shell.
43
-
44
- If something does not work, run `veo doctor` first: it inspects the whole local setup and prints one line per check.
45
-
46
- Run `veo` without arguments in an interactive terminal for a guided download: choose a
47
- profile, enter a link, select video or audio, choose available resolution and output
48
- directory, and start. The wizard checks the link and asks about playlist mode only when
49
- it finds a collection; configured playlist and entry selection values are preselected.
50
- Playlist mode also lists entries for selection. The wizard enables
51
- resume and offers skipping previously downloaded files. With redirected input, use the
52
- regular command arguments; veo never starts a prompt in a script.
53
-
54
- ## Usage
55
-
56
- ```text
57
- veo <url> [<url>...] [options]
58
-
59
- Options:
60
- -q, --quality <quality> best, 2160p, 1440p, 1080p, 720p, 480p, 360p
61
- -o, --output <path> Output directory (default: current working directory)
62
- -r, --rename <name> Filename without extension; * inserts the original title
63
- --closest-quality Nearest available resolution instead of an upper bound
64
- --open Open the saved file with your default app
65
- --audio Audio only (MP3 by default)
66
- --format <format> Video: mp4, mkv, webm, mov
67
- Audio: mp3, m4a, aac, opus, flac, wav
68
- --recode Explicit video conversion (requires --format)
69
- --playlist-concurrency <n> Parallel playlist entries, 1-4 (default: 2)
70
- --playlist Download every entry of a playlist or channel URL
71
- -N, --concurrent-fragments <n> Parallel fragments, 1-64 (default: 8; 17-64 experimental)
72
- --experimental-fragments Explicitly accept experimental values above 16
73
- --subs Download subtitles (default languages: en)
74
- --auto-subs Include automatically generated subtitles
75
- --sub-langs <langs> Subtitle languages, e.g. "de,en" (manual unless --auto-subs)
76
- --sub-format <format> Prefer subtitle formats, e.g. srt/vtt/best
77
- --list-subs Show manual and automatic subtitle availability
78
- --embed-subs Embed subtitles into the video file
79
- --embed-metadata Embed title, date and other metadata
80
- --embed-thumbnail Embed the thumbnail
81
- --sponsorblock-remove <categories> e.g. "sponsor,selfpromo"
82
- --section <range> Only a time range, e.g. "*10:00-12:00"
83
- --cookies <file> Netscape cookie file, for content you may access
84
- --cookies-from-browser <browser[:profile]>
85
- --resume Keep partial data and continue an interrupted download
86
- --incognito Save media without run ID, history, stats or retry data
87
- --neutral-filename Optional random filename instead of title and templates
88
- --list-qualities Show available video resolutions and exit
89
- --list-formats Show available formats and exit
90
- --dry-run Show what would be downloaded and exit
91
- --verify Probe saved media files with FFprobe (default: off)
92
- --json One JSON object per URL instead of prose
93
- --profile <name> Use a named profile from the config file
94
- --batch-file <file> Read one URL per line (blank lines and # comments ignored)
95
- --retry-failed <id|file> Retry failed/unfinished downloads by run ID or job file
96
- --playlist-items <list> Select entries, e.g. 1,3-5 (implies --playlist)
97
- --skip-existing Skip matching downloads that are still on disk
98
- --no-<boolean-option> Disable a default, e.g. --no-open or --no-audio
99
- -v, --version Show installed version (also: veo version)
100
- -h, --help Show help
101
- ```
102
-
103
- ```bash
104
- veo "https://youtube.com/watch?v=VIDEO_ID" -q 1080p
105
- veo "https://x.com/USER/status/STATUS_ID" -q best
106
- veo "https://example.com/video.mp4" --audio
107
- veo "https://example.com/video.mp4" --audio --format flac
108
- veo "https://example.com/video.mp4" --format webm --recode -o ./videos
109
- veo "https://example.com/video.mp4" -r "My Video" --format mp4 --open
110
- veo "https://example.com/video.mp4" --audio -r "My Music"
111
- veo <url1> <url2> <url3> --embed-metadata --subs
112
- veo "https://youtube.com/playlist?list=LIST_ID" --playlist -q 720p
113
- veo "https://example.com/video.mp4" --resume
114
- npx veodl "https://example.com/video.mp4" --output ./downloads
115
- ```
116
-
117
- ### Several URLs and playlists
118
-
119
- Playlist downloads use two concurrent entries by default; use `--playlist-concurrency 1` for sequential downloads. Each entry uses up to eight concurrent DASH/HLS fragments by default (`-N 1` disables fragment parallelism). Fragment counts between 1 and 16 need no confirmation. Values from 17 to 64 are experimental and prompt for `y/N` confirmation in terminals, or require `--experimental-fragments` (or `"experimentalFragments": true` in configuration/profiles) in non-interactive environments and JSON mode. Values above 64 are rejected. More connections help only when the source and connection have spare capacity, and excessive connections may trigger server limits. Video format changes preserve the encoded streams unless `--recode` is explicitly enabled; audio extraction and precise section cuts retain their existing conversion behavior.
120
-
121
- - Pass any number of URLs; up to two run concurrently by default. Use
122
- `--concurrent-downloads 1` for sequential processing. A failure is reported
123
- with its URL and does not stop the remaining URLs; the exit status is `1` if anything
124
- failed.
125
- - Playlists, channels and other collections are refused by default with a hint. Add
126
- `--playlist` to download every entry. Each entry keeps its own title; `--rename` applies
127
- a name prefix per entry (`My Name - 001`), or substitutes each title with `movie_*`.
128
- - Use `--playlist-items 1,3-5` to select entries by their original, one-based index.
129
- Playlist entries run with bounded parallelism: a failed entry does not discard successful
130
- files or stop the remaining entries. Before downloading, veo displays the selected count
131
- and a size estimate when the source supplies sizes. Unknown sizes are labeled explicitly;
132
- metadata estimates do not predict conversion size.
133
- - `--batch-file links.txt` accepts a UTF-8 URL list, optionally alongside URLs on the command
134
- line. A final summary counts saved, skipped and failed videos. Successful files are opened
135
- with `--open` even if another URL or playlist entry fails.
136
- - Failed or cancelled jobs print a ready-to-use `veo --retry-failed <runId>` command. Saved job file paths are also accepted.
137
- - Use `veo retry --last` to retry the newest failed or unfinished job without copying its path. Active runs are ignored; use `veo runs` to inspect them.
138
- Jobs are stored under the per-user veo cache's `jobs` directory. Retries retain resolved
139
- output directories and settings, even from a different working directory, and explicit
140
- flags can override them. Completed playlist jobs retry only failed indices; interrupted
141
- playlists revisit their selection and skip completed entries through resume history.
142
- Playlist indices refer to the playlist's current order, so changes to that order can
143
- change what an index selects. Cookie paths and browser-session settings are not saved in
144
- retry jobs; pass those flags again if needed. Job files contain URLs, settings and local
145
- file paths and can be deleted when no longer needed.
146
-
147
- ```bash
148
- veo --batch-file links.txt --profile archive
149
- veo "https://example.com/playlist" --playlist-items 1,3-5 --resume
150
- veo "https://example.com/playlist" --playlist --skip-existing
151
- veo --retry-failed "C:\path\to\job.json"
152
- veo --retry-failed abc123
153
- veo retry --last
154
- ```
155
-
156
- ### Quality
157
-
158
- - Numeric qualities are an **upper bound**: `-q 720p` downloads the best resolution at or
159
- below 720p, so a metered connection can never accidentally receive a 2160p file. If a
160
- source offers nothing at or below the request, veo stops before downloading and lists
161
- the resolutions that do exist.
162
- - `--closest-quality` restores the former "nearest available resolution" rule, which may
163
- pick a resolution *above* the request. It needs a numeric `--quality`.
164
- - Sources that report no resolution metadata fall back to the best available stream.
165
- Collections resolve and enforce the quality limit separately for each entry.
166
- - Other positive numeric resolutions, such as `-q 540p`, are also accepted. The interactive
167
- wizard offers the resolutions reported by the source; without resolution metadata it
168
- offers `best` rather than inventing available streams.
169
- - `--quality` does not apply to audio.
170
- - MP4-compatible codecs are preferred at the selected resolution; merged video prefers MP4
171
- with MKV fallback. A single-file source may retain its original container. Use
172
- `--format mp4` to require MP4 through lossless remuxing. Incompatible codecs cause an error.
173
- - `--format` on video remuxes without re-encoding. Add `--recode` to explicitly allow conversion, which may be slow or lossy. Audio formats require `--audio`.
174
-
175
- ### Playback compatibility
176
-
177
- H.264/AAC sources are preferred at the selected quality, including explicit MP4 output.
178
- Original codecs are retained by default; a playback note identifies media needing extra
179
- player codecs. MP4 is a container, not a promise of a particular codec.
180
-
181
- Use `veo URL --compatible` to ensure MP4 with H.264 8-bit 4:2:0 and AAC.
182
- Already compatible streams are copied; only incompatible streams are encoded (H.264 CRF 18,
183
- AAC 192 kbit/s), which takes time and may lose quality. HDR requiring video conversion
184
- is refused rather than silently losing correct colors. Embedded subtitles/thumbnails are
185
- not supported in this mode; separate subtitle files remain supported.
186
- New configs include a `kompatibel` profile; existing configs receive a commented profile
187
- example through `veo config edit`. Enable it with `veo URL --profile kompatibel`.
188
-
189
- ### Download controls and diagnostics
190
-
191
- - URL lists and batch files use `--concurrent-downloads 2` by default (range 1–4).
192
- Each playlist independently uses `--playlist-concurrency 2` (range 1–4).
193
- The limits multiply when several playlists run at once. JSON emits one complete
194
- object per URL in completion order; the `url` field identifies each result.
195
- - Adaptive concurrency is on by default. After the backend's own retries are exhausted,
196
- HTTP 429/5xx and temporary connection failures trigger at most two further attempts,
197
- after 2 and 4 seconds. Fragment parallelism and future URL/playlist workers are reduced
198
- for the rest of the run. Active downloads finish normally; format and quality stay fixed.
199
- Partials are continued on retry. Use `--no-adaptive-concurrency` to disable this.
200
- - `--check-space` checks cache and destination free space before media transfer. Known
201
- sizes include conservative room for merge output and a destination copy, plus space
202
- reserved by concurrent downloads in the same process. Unknown sizes are reported as
203
- unknown; conversion sizes and other processes' disk usage cannot be predicted exactly.
204
- Use `--no-check-space` to disable this conservative check.
205
- - `--timings` shows phase times at the end of the run. Single downloads show
206
- timing without a summary; multiple downloads show timing below the summary.
207
- The phases cover setup, metadata, download/backend, processing, saving and retry waits.
208
- Download/backend includes the backend's own startup and extraction overhead;
209
- these are summed per-download times, not a diagnosis of network speed.
210
- JSON results include `timings` in milliseconds, or `entryTimings` for playlists.
211
- Use `--no-timings` to hide/omit them.
212
- - Terminal details are gray, titles bold, saves green and errors red. The same styling
213
- applies to stats, history, runs/stop, doctor, flush, updates, config prose, help and
214
- interactive prompts. For example, `veo stats --no-color` disables it for one command. Redirected output
215
- and JSON have no color escapes. `--no-color`, `"color": false`, `NO_COLOR`,
216
- and `TERM=dumb` disable colors.
217
- Terminal reports shorten long URLs, paths and detail values to the current window
218
- width. Titles can use up to three lines before an ellipsis is added. This also
219
- applies with `--no-color`; JSON and redirected output retain complete values.
220
- Downloads stay in the normal terminal flow. Only the current progress block is
221
- updated in place; narrow windows put the statistics below the progress bar.
222
- Existing logs remain in scrollback and are not replayed or rewritten on resize.
223
- This can be set independently per profile, for example
224
- `"profiles": { "default": { "color": true }, "plain": { "color": false } }`.
225
- Use `veo stats --profile plain` or `veo URL --profile plain`; `--color` or
226
- `--no-color` overrides the profile for one invocation (except `NO_COLOR`).
227
- External npm/editor output and Node runtime warnings keep their own formatting.
228
-
229
- ### Filename templates and folders
230
-
231
- `--filename-template` controls the name without extension; `--folder-template`
232
- creates relative subfolders under `--output`. Both support `{title}`, `{id}`,
233
- `{channel}`, `{year}`, `{playlist}` and `{index}` (three-digit playlist index).
234
- Missing metadata uses readable fallbacks. `{year}` is the upload year when available.
235
- Subtitles follow the media name. Existing files are never overwritten. Templates cannot
236
- escape the output folder, and symlink/junction subfolders are refused. Use forward slashes
237
- for folders. `--rename` and `--filename-template` are mutually exclusive.
238
-
239
- ```powershell
240
- veo "VIDEO-URL" --folder-template "{channel}/{year}" --filename-template "{title} - {id}"
241
- veo "PLAYLIST-URL" --playlist --folder-template "{playlist}" --filename-template "{index} - {title}"
242
- veo config check
243
- veo config check --profile fast
244
- veo config show --profile fast
245
- ```
246
-
247
- Config checks validate every effective profile (or the selected one), including unknown
248
- settings, ranges and conflicting options. Show prints merged built-in, global and profile
249
- settings as JSON, with credential paths/browser profiles redacted. Neither command contacts
250
- video sites. All new options are also documented in `veo config guide`.
251
-
252
- ### Files, names and resume
253
-
254
- - Original titles are preserved by default. Use `-r "My Video"` or `--rename "My Video"`
255
- for video or audio. Supply the name **without an extension**; the actual media extension
256
- is appended automatically. Use `-o` for the directory. Invalid filename characters are
257
- sanitized and overly long names shortened. Existing files are never intentionally
258
- overwritten: duplicates get ` (1)`, ` (2)`, etc.
259
- - Use `-r "movie_*"` or `"rename": "movie_*"` in your config/profile to insert the
260
- original title: `My Film` becomes `movie_My Film.mp4`. Every `*` is substituted.
261
- Quote the pattern in your shell. Patterns also work with URL batches and playlists.
262
- - Subtitles and thumbnails are saved beside the media file under the same base name
263
- (`My Video.mp4` → `My Video.en.vtt`).
264
- - Downloads and media processing first finish in the local per-user veo cache, under
265
- `downloads` (`%LOCALAPPDATA%\veo\downloads` on Windows). The destination is only written
266
- after the media is ready. Saving uses a hard link when supported or an exclusive copy
267
- across drives/cloud mounts; copied file sizes are checked. Existing files are never
268
- overwritten. The local original is removed only after files and history are saved.
269
- Local disk space is therefore needed for the complete download and processing files.
270
- - If saving fails or is cancelled after media processing completes, the local original and
271
- sidecars are kept for **15 minutes**, even without `--resume`. Repeat the same command or
272
- use the printed `--retry-failed` command to retry the transfer. Individual completed
273
- downloads can be recovered without contacting their source again. The same source,
274
- media settings and destination identify a cached transfer; changing those starts a new
275
- download. Expired copies are removed on the next veo invocation, not by a background
276
- timer while veo is closed. Active transfers are never expired. Another failed transfer
277
- starts a fresh 15-minute retention period.
278
- - `--resume` also keeps unfinished downloads in local `.veo-part-<request-hash>` folders
279
- until resumed; unfinished data has no automatic expiry. Without `--resume`, unfinished
280
- downloads are discarded on failure or cancellation. The hash separates source URLs,
281
- playlist entries, destinations, quality, media type, format, sections and subtitle/metadata
282
- options. A manifest records backend-confirmed
283
- completion and saved files, so an unprocessed file is not mistaken for a finished video.
284
- Each playlist entry has its own state. Completed entries survive later failures and are
285
- skipped when resuming the same selection. Partial downloads are never resumed without
286
- `--resume`. Legacy partial folders in the output directory are left untouched; the new
287
- local cache does not automatically migrate them.
288
- - A lock prevents two resume processes from using the same partial folder. Normal failures
289
- and Ctrl+C release it. After a force kill, remove the named `.lock` file only after making
290
- sure no veo process still uses that folder.
291
- - Successful downloads record their source, output settings and saved paths in
292
- `.veo-history` inside the output directory. `--skip-existing` uses these records and checks
293
- that the files still exist. A different quality/format or deleted output is downloaded
294
- again. Files downloaded before this history existed are not recognized automatically.
295
- Deleting history removes duplicate detection, not downloaded media. Records whose files
296
- no longer exist are removed on the next download into that folder, and `.veo-history`
297
- itself is removed with its last record; `veo doctor` reports stale records.
298
-
299
- ### Metadata and subtitles
300
-
301
- - `veo subs <URL>` lists manual and automatically generated subtitles separately, including
302
- their languages and source formats. `--json` returns one object without subtitle URLs.
303
- - `--subs` writes manual subtitle files. `--auto-subs` adds generated subtitles, or selects
304
- only generated subtitles when used alone. `--sub-langs de,en` selects languages;
305
- `--sub-format srt/vtt/best` prefers available source formats in that order. A format
306
- preference does not convert subtitles. `--embed-subs` embeds selected subtitles into
307
- the video container.
308
- - `--embed-metadata` and `--embed-thumbnail` use FFmpeg to write metadata and cover art.
309
- - `--sponsorblock-remove sponsor,selfpromo` cuts sponsor segments (YouTube) and requires
310
- FFmpeg; the cut re-encodes the affected parts.
311
- - `--section "*10:00-12:00"` downloads only a time range.
312
- - `-N 8` downloads several fragments in parallel, which is noticeably faster on HLS/DASH
313
- sources and heavier on the network.
314
-
315
- ### Optional post-download verification
316
-
317
- Use `veo <URL> --verify` or set `"verify": true` in a config profile to check
318
- saved media with FFprobe before recording a successful download. It checks that
319
- the container can be probed and contains a video track (or an audio track for
320
- `--audio`). Verification is off by default; `--no-verify` overrides a profile.
321
- This quick check does not decode the whole file or prove that the audio is
322
- audible. Use `veo inspect <file> --check-audio` for a full audio signal check.
323
- If verification fails, veo reports failure and keeps the saved file for review.
324
-
325
- ### Access and credentials
326
-
327
- veo downloads publicly accessible content. It does not bypass access controls, and it does
328
- not expose options that defeat them. If you need your own session for content you are
329
- authorized to view:
330
-
331
- - `--cookies ./cookies.txt` passes a Netscape-format cookie file. The file is validated
332
- before any network work, and on Unix a file readable by other users produces a warning.
333
- - `--cookies-from-browser firefox:Work` reads cookies from an installed browser: `brave`,
334
- `chrome`, `chromium`, `edge`, `firefox`, `opera`, `safari`, `vivaldi`, `whale`, optionally
335
- with `+gnomekeyring`/`+kwallet`/`+basic` and a profile or container.
336
-
337
- Credentials are used for that single invocation only and are never stored by veo. When a
338
- download fails because a login is required, the error message points at these flags.
339
-
340
- ### Inspection and scripting
341
-
342
- - `veo <page-url> --list-sources` opens the page in a temporary headless Chrome,
343
- Edge or Chromium profile, repeatedly checks for visible Play controls (including
344
- controls added later or inside frames), and watches media requests after interaction.
345
- Use `--json` for numbered source
346
- metadata. Each result includes its title, host, HLS/DASH/direct type, known
347
- resolutions, estimated MiB size and Mbit/s bitrate when the source provides them. Signed media
348
- URLs are not printed. Set `VEO_BROWSER_PATH` if the browser is not found.
349
- - Add `--deep-scan` to verify every media candidate observed during the source
350
- search. The normal search verifies at most 30 candidates. `--timeout 30s`
351
- sets a deadline for browser observation and candidate verification; plain
352
- numbers mean seconds, and `m` means minutes (5 seconds to 10 minutes).
353
- Defaults are 45 seconds, or 2 minutes with `--deep-scan`. If the deadline
354
- is reached, verified sources found so far are shown with a timeout notice.
355
- A deep scan covers observed requests; it cannot discover sources that the
356
- page never loads. The timeout does not stop a subsequent download.
357
- - For an unsupported single page URL, veo searches for media automatically and
358
- uses the only verified source when there is one. If several sources are found,
359
- the terminal asks which one to use; scripts receive a numbered source list and
360
- a nonzero exit. `veo <page-url> --source 2 --dry-run --json` previews a chosen
361
- source, and `veo <page-url> --source 2 --json` downloads it. Scripts never
362
- receive an interactive prompt.
363
- Retry jobs retain the page URL and selected number and discover a fresh media
364
- URL, because player links may expire. Browser discovery uses a temporary
365
- profile without copying browser cookies, and cannot unlock DRM content.
366
- Misspelled flags and commands show a nearby valid spelling, for example
367
- `--deepscan` suggests `--deep-scan`.
368
- - `veo <url> --list-qualities` lists available video resolutions, highest first,
369
- without downloading media. `--list-qualitys` is an alias. Add `--json` for
370
- a `qualities` array (e.g. `["1080p", "720p"]`); use `--quality 720p` to select
371
- an upper bound. Unknown resolutions are reported without guessing. Audio-only,
372
- storyboard and DRM formats are excluded. Supports `--source <number>` for embedded players.
373
- - `--list-formats` prints the backend's own format table for one URL and exits.
374
- - `--dry-run` prints the title, the resolved quality and the destination path that would be
375
- used — without creating the output directory or downloading anything.
376
- - `--json` prints one JSON object per line on stdout:
377
- `{"url":…,"status":"saved","title":…,"files":[…]}` or
378
- `{"url":…,"status":"failed","error":…}`. Progress and status still go to stderr. Without
379
- `--json`, every saved file is printed as `Saved: <path>` on stdout.
380
- Duplicate detection can return `status: "skipped"`; cancellation returns `"cancelled"`.
381
- Playlist results also include `saved`, `skipped` and `failures` (with original indices).
382
- A failed or cancelled playlist can still report files saved before the failure.
383
- - `veo runs [id] --json` reports active run metadata and per-item progress. Finished attempts
384
- remain available through `veo history --json`.
385
- - `veo inspect <file> --json` reports local container and stream metadata. Add
386
- `--check-audio` to decode all audio tracks and measure whether any peak exceeds
387
- -60 dBFS. A track can exist without a detectable signal. The check reads the full
388
- media file and uses only local FFmpeg/FFprobe tools.
389
- - Download results and history retain a `runId`. `veo inspect run <id> --json` reads
390
- a finished run and checks every saved file before probing it. Renamed media is
391
- found by file identity, size and a sampled SHA-256 fingerprint within the original output tree;
392
- add `--search <directory>` after moving it elsewhere. Ambiguous or missing files
393
- are reported without probing another file. Old runs lack this persistent record.
394
- Finished run records live in the current device's veo cache. On Windows, macOS,
395
- Linux and Termux, `inspect` uses bundled media tools where supported and otherwise
396
- uses local `ffmpeg` and `ffprobe` (or `VEO_FFMPEG_PATH`); it does not install them.
397
- Copying only the media to another device does not copy its run ID record.
398
-
399
- ### Config file
400
-
401
- Defaults can be stored in a config file, so a long list of flags is not needed for every
402
- call. `veo doctor` prints the exact path; `VEO_CONFIG` overrides it.
403
- The file accepts JSON with `//` line comments and `/* ... */` block comments. Strings
404
- (including URLs and Windows paths) keep their normal JSON escaping rules. Trailing commas
405
- are not allowed.
406
-
407
- - Windows: `%APPDATA%\veo\config.json`
408
- - macOS: `~/Library/Application Support/veo/config.json`
409
- - Linux: `$XDG_CONFIG_HOME/veo/config.json` (or `~/.config/veo/config.json`)
410
-
411
- ```json
412
- {
413
- "output": "D:\\Videos",
414
- "quality": "1080p",
415
- "embedMetadata": true,
416
- "subLangs": "de,en",
417
- "concurrentFragments": 4
418
- }
419
- ```
420
-
421
- Supported keys: `output`, `quality`, `format`, `rename`, `audio`, `open`, `resume`, `incognito`, `neutralFilename`,
422
- `closestQuality`, `cookies`, `cookiesFromBrowser`, `playlist`, `concurrentFragments`,
423
- `experimentalFragments`, `playlistConcurrency`, `recode`, `compatible`, `concurrentDownloads`, `adaptiveConcurrency`,
424
- `filenameTemplate`, `folderTemplate`, `checkSpace`, `timings`, `color`,
425
- `subs`, `autoSubs`, `subLangs`, `subFormat`, `embedSubs`, `embedMetadata`, `embedThumbnail`, `sponsorblockRemove`, `verify`,
426
- `section`, `json`. An explicit command-line flag always wins over a stored default. An
427
- unknown key produces a warning; invalid JSON or a wrong value type is an error, because
428
- silently ignoring a typo would be worse.
429
-
430
- Source discovery defaults are `deepScan` (boolean), `timeout` (string such as
431
- `"30s"` or `"2m"`), `listSources` (boolean) and `autoListSources` (boolean).
432
- `listSources: true` makes a single URL list sources and exit; use
433
- `--no-list-sources` to download for one invocation. `autoListSources` defaults
434
- to `true` and searches when ordinary extraction finds no downloadable video.
435
- Set it to `false` or pass `--no-auto-list-sources` to disable that fallback.
436
- A single verified source is selected automatically; multiple sources require
437
- a choice. `deepScan` and
438
- `timeout` apply to either kind of source search.
439
-
440
- Additional defaults are `skipExisting` and `playlistItems`. Boolean defaults can be disabled
441
- with `--no-open`, `--no-audio`, `--no-resume`, `--no-embed-metadata`, etc. `--no-subs` also
442
- disables stored subtitle languages and subtitle embedding for that invocation.
443
-
444
- ### Incognito downloads
445
-
446
- Use `veo <url> --incognito` or set `"incognito": true` in a named profile. The
447
- downloaded media is saved normally. veo does not assign a run ID or write a
448
- retry job, run archive, download history, output `.veo-history` record, or
449
- statistics for that attempt. It uses a private temporary staging directory and
450
- removes it after success, failure, or cancellation. It also disables yt-dlp's
451
- filesystem cache. `--no-incognito` overrides
452
- a profile for one request. Incognito cannot be combined with `--resume`,
453
- `--skip-existing`, or `--retry-failed`; there is no veo retry for an incognito
454
- attempt. A force-killed process may leave temporary files that the OS can
455
- clean later. The destination file, shell history, network service, and source
456
- website can still reveal the download.
457
-
458
- Example profile: `"profiles": { "private": { "incognito": true } }`.
459
- Add `"neutralFilename": true` to that profile, or pass `--neutral-filename`,
460
- to save files as names such as `video-a1b2c3d4e5f6.mp4`. This is optional:
461
- incognito keeps the normal title by default. Neutral naming overrides `--rename`,
462
- `--filename-template`, and `--folder-template` for that request. The selected
463
- output directory, media contents, and embedded metadata are unaffected.
464
-
465
- ### Named profiles
466
-
467
- If no profile is selected, `profiles.default` is applied automatically. `veo profile music`
468
- selects another named profile for future commands and stores it as `activeProfile` in the
469
- config; `veo profile` shows the current choice, and `veo profile list` marks it in
470
- the list. `veo profile reset` removes the stored choice and returns to `default`.
471
- You can also use `veo profile default` to select it explicitly.
472
- The names `list` and `reset` are reserved for these commands and cannot be used as profile names.
473
- `--profile NAME` overrides this choice for one command. Named profiles use global defaults
474
- rather than inheriting `default`. The wizard preselects the active profile.
475
- New configurations include an empty `default` profile and the example profiles. Existing configurations are
476
- left unchanged when opened. An empty profile does not change download behavior.
477
-
478
- ```json
479
- {
480
- "output": "D:\\Videos",
481
- "profiles": {
482
- "default": { "quality": "1080p", "resume": true },
483
- "music": { "audio": true, "format": "mp3", "output": "D:\\Music" },
484
- "archive": { "quality": "1080p", "embedMetadata": true, "subLangs": "de,en" }
485
- }
486
- }
487
- ```
488
-
489
- `veo <url> --profile music` merges global defaults, then the selected profile, then explicit
490
- CLI flags. `veo config profiles` lists profile names; `veo config path` prints the file path.
491
- The download status shows `Profile: NAME` (or `global (no profile)` when none is configured).
492
- `veo config edit` creates a small, valid configuration for new or empty files. Opening an
493
- existing configuration does not add or update comments. The current option guide is kept in
494
- the program; `veo config guide` prints it without changing the file. In the built-in editor,
495
- F3 offers **A** to add or update the guide at the top and **R** to remove a previously
496
- generated guide. Ctrl+S saves that choice. This can also clean up the long duplicate block
497
- inserted by older versions. Personal settings and notes below a generated block are preserved.
498
- In an interactive terminal, it opens the built-in editor
499
- unless `VISUAL` or `EDITOR` is configured (an executable path, without shell arguments).
500
- The editor provides syntax colors, line numbers and live validation. Syntax errors mark
501
- the affected line; unknown properties and invalid values are highlighted directly, with
502
- spelling suggestions, expected types and allowed values in the status area. Validation uses
503
- the same rules as the CLI, including profile overrides and audio/video formats.
504
- Press F2 on a property or value to open suggestions, use Up/Down to choose, Enter to apply
505
- or Esc to cancel. Suggestions require valid JSON syntax; free-text values and custom numeric
506
- resolutions such as `900p` remain supported. Ctrl+S validates and saves;
507
- Ctrl+Z undoes edits, Ctrl+Y redoes them, and Ctrl+A selects the whole file.
508
- Esc or Ctrl+Q exits, asking before discarding changes. Use arrows, Home/End and PageUp/PageDown
509
- to navigate. Invalid configurations cannot be saved. `--no-color` disables syntax colors.
510
- In terminals supporting SGR mouse reporting, left-click positions the cursor and dragging
511
- selects text, including across lines. Holding the pointer at the top or bottom while dragging
512
- scrolls the file and extends the selection. Backspace/Delete removes the selection; typing replaces
513
- it. The mouse wheel scrolls without changing the editing position; Ctrl+Up/Down also scrolls.
514
- Ctrl+C copies selected text to the system clipboard, and Ctrl+V pastes clipboard text at the
515
- cursor or replaces the selection. Ctrl+Q or Esc exits the editor. The editor disables mouse
516
- reporting again when it exits. Keyboard editing remains available in terminals without mouse
517
- support.
518
- Clipboard access uses the system clipboard on Windows, `pbcopy`/`pbpaste` on macOS, and
519
- `wl-clipboard` (Wayland), `xclip` or `xsel` (X11) on Linux. Linux requires one of those tools
520
- and access to a graphical session. The editor reports when no clipboard is available; terminal
521
- paste shortcuts can still insert text directly when supported by the terminal.
522
- On Termux, editor clipboard shortcuts use `termux-clipboard-set`/`termux-clipboard-get`
523
- when the Termux:API app and `termux-api` package are installed.
524
-
525
- Use `veo config edit --external` to use `VISUAL`, then `EDITOR`, then Notepad on Windows
526
- or `vi` elsewhere. `veo config edit --terminal` explicitly selects the built-in editor.
527
- Set `VEO_CONFIG_EDITOR=external` to disable the built-in editor by default (`auto` restores
528
- automatic selection, `terminal` forces it). For a persistent Windows preference:
529
-
530
- ```powershell
531
- [Environment]::SetEnvironmentVariable('VEO_CONFIG_EDITOR', 'external', 'User')
532
- $env:VEO_CONFIG_EDITOR = 'external'
533
- ```
534
-
535
- Non-interactive sessions use the external editor unless `--terminal` is explicitly selected,
536
- in which case an interactive-terminal error is reported. The wizard also offers configured profiles.
537
- Generated guides and app messages are in English. Existing comments, profile names and
538
- settings are preserved when opening the editor.
539
-
540
- ### Terminal output
541
-
542
- - During a download, the terminal/tab title shows `veo | 50% | My Video` (original title or
543
- your `-r` name), plus setup/processing status. It ends with `Done`, `Failed`, or
544
- `Cancelled`; the shell may replace it at the next prompt. This uses the native console
545
- title on Windows (including PowerShell/Windows Terminal) and OSC title sequences on
546
- compatible Linux/macOS terminals. Titles are not changed when stderr is redirected or
547
- `TERM=dumb`. Terminal settings that enforce a fixed tab title can override this feature.
548
- - Progress shows percentage, speed, downloaded/total size and ETA on stderr; unknown values
549
- appear as `?`. Separate audio/video streams each have their own progress. Non-interactive
550
- output is throttled.
551
- - Progress labels include the current item (`[3/12]`), source title, video/audio/media stream,
552
- postprocessing (merging, conversion, subtitles or metadata) and saving. Stream percentages
553
- describe that stream, not the entire multi-step job. Sources without codec metadata use
554
- the neutral `Media` label.
555
- - `--open` launches the completed file in your default app, including renamed files and
556
- audio. With several URLs, the primary file of each URL is opened. Uses `explorer.exe` on
557
- Windows, `open` on macOS, and `xdg-open` on Linux (requires a graphical desktop,
558
- xdg-utils and a file association). The CLI does not wait for the player to close. If the
559
- opener cannot be launched, a warning is printed and the successful download still exits
560
- with `0`; later errors inside the detached opener/player are not monitored.
561
- - After each successful command, veo checks the npm registry at most once per day for a
562
- newer version and prints a one-line notice on stderr (`Update available: veo X
563
- (you have Y). Run: veo up`). Help, version and the update commands themselves
564
- stay quiet. Disable it with `VEO_NO_UPDATE_CHECK=1`;
565
- `VEO_REGISTRY`/`npm_config_registry` are respected.
566
- - Exit status is `0` on success, `1` on errors, and `130` on cancellation. With several
567
- URLs, `1` means at least one URL failed.
568
-
569
- ## Commands
570
-
571
- ```bash
572
- veo version # installed version (also: veo --version or veo -v)
573
- veo # interactive download wizard (terminal only)
574
- veo config edit # create/open config; F3 offers the optional guide
575
- veo config guide # print the current option guide
576
- veo config reset # confirm with y; back up config and restore the current template
577
- veo config profiles # list available profiles
578
- veo profile # show the currently selected profile
579
- veo profile list # list all profiles and mark the active one
580
- veo profile music # use music as the default for future commands
581
- veo profile reset # return to default (or global settings if absent)
582
- veo config path # show the config file location
583
- veo doctor # diagnose the local setup; exit 1 if a check fails
584
- veo doctor fix # restore missing or damaged managed tools
585
- veo doctor --offline # skip the network checks
586
- veo stats # persistent download totals; --json for scripting
587
- veo history # the last 5 downloads; --json for scripting
588
- veo history --failed --limit 20 # recent failed/cancelled attempts
589
- veo changes # released versions, newest first; the installed one is marked
590
- veo changes 1.10.1 # notes of one release (also: --version 1.10.1)
591
- veo changes --since 1.10.1 # everything newer than a version
592
- veo changes --latest --json # newest release as JSON
593
- veo retry --last # retry the newest failed or unfinished job
594
- veo runs # active runs with their id; veo runs <id> for details
595
- veo runs --json # machine-readable active run metadata
596
- veo inspect FILE --check-audio --json # track metadata and audio signal
597
- veo subs URL --json # available manual and automatic subtitles
598
- veo inspect run ID --check-audio --json # finished run and saved media
599
- veo stop [id] # stop one run, or every active run
600
- veo flush # stop runs, clear temporary downloads and retry jobs
601
- veo flush --stats # the same, and reset the statistics
602
- veo update # install the latest veo with npm
603
- veo update --check # only check for a newer veo
604
- veo up # short alias for veo update (also: veo up --check)
605
- veo upgrade # alias for veo update
606
- veo check update # alias for veo update --check
607
- veo backend update # install the newest yt-dlp release
608
- veo backend reset # back to the release pinned in this veo version
609
- veo alias list # shipped (veo, veodl) plus custom wrappers
610
- veo alias add veo-dl # create veo-dl as a wrapper that calls veo
611
- veo alias remove veo-dl # delete a custom wrapper
612
- veo uninstall -p veodl # delete one alias (veo itself stays installed)
613
- veo uninstall # plan the full removal (aliases, package, cache, config)
614
- veo uninstall --yes # remove everything
615
- ```
616
-
617
- `veo update` runs `npm install -g veodl@latest` and then removes yt-dlp backend
618
- caches from older pinned releases, keeping an explicitly installed backend release. It
619
- never uses a shell on Linux/macOS, passes fixed arguments only, and prints the manual npm
620
- command on any failure. The registry can be overridden with `VEO_REGISTRY` (or npm's
621
- `npm_config_registry`) for mirrors and proxies.
622
-
623
- ### `veo alias` and `veo uninstall`
624
-
625
- The npm package is `veodl`; a global install ships the commands `veo`
626
- and `veodl`. `veo alias` manages extra command names without reinstalling:
627
-
628
- ```bash
629
- veo alias list
630
- veo alias add veo-dl
631
- veo alias remove veo-dl
632
- veo uninstall -p veodl
633
- ```
634
-
635
- `add` creates a small wrapper next to the `veo` command found on `PATH` that
636
- forwards to `veo`, so it survives `veo update`. Names use 2-31 lowercase
637
- letters, digits or hyphens. `remove` (also `veo alias rm`) deletes the wrapper
638
- files; `veo` itself can never be removed this way. Removing a shipped name such
639
- as `veodl` only deletes that shortcut — the next `veo update` recreates it.
640
-
641
- `veo uninstall -p <name>` is a shortcut for `veo alias remove <name>`. Bare
642
- `veo uninstall` removes everything: without flags it only prints the plan
643
- (package `veodl` with commands veo, veodl, plus custom wrappers, cache
644
- and config paths); `veo uninstall --yes` deletes all of it — custom wrappers,
645
- the package with `npm uninstall -g veodl`, veo's cache directory (downloads,
646
- jobs, backend tools, history, statistics) and the config file. Use
647
- `--keep-aliases`, `--keep-cache` or `--keep-config` to preserve one part.
648
- The plan shows the active config path, including a `VEO_CONFIG` override.
649
- Add `--json` for scripting.
650
-
651
- ### `veo flush`
652
-
653
- Run `veo flush` to stop active veo runs started with this version, then remove
654
- temporary local downloads, including the 15-minute retained files and unfinished
655
- resume data, and cached retry job JSON files. Those jobs can no longer be retried.
656
- Saved media, output history, config/profiles, the `veo history` list, finished-run
657
- records and backend binaries are kept.
658
- Cleanup waits for cancellation; if a run cannot stop, it fails without deleting
659
- download or job files. Locked folders from older or interrupted processes are
660
- skipped and reported. Only veo's own per-user cache is cleaned. Statistics are
661
- preserved unless `--stats` is given: `veo flush --stats` resets them too.
662
- To end runs without removing their downloads and jobs, use `veo stop [id]` instead.
663
-
664
- ### `veo stats`
665
-
666
- Shows saved videos/audio, failed attempts, skips, cancellations and total time spent
667
- on download requests (including preparation, processing and saving). Parallel run
668
- times are added together. Playlist entries count individually; retries are new
669
- attempts. Active requests are recorded when they finish. Tracking starts with this
670
- version; previous downloads are not imported. `veo stats --json` returns the totals
671
- as JSON. Statistics contain counters and timestamps, not URLs or filenames.
672
-
673
- ### `veo history`
674
-
675
- Shows the **last 5 download attempts** by default, newest first, with title, status, media type,
676
- date, duration, URL and the saved files. Saved, skipped, failed and cancelled items are
677
- recorded, including the reason a failure was reported; playlist entries and retried
678
- attempts count individually. Active downloads appear once they finish. Long file lists
679
- are summarized in the text view.
680
-
681
- Use `veo history --limit N` to show 1-1000 attempts, or `veo history --failed` to
682
- show only failed and cancelled attempts; the flags can be combined. Failed attempts
683
- include their exact retry-job command when one was recorded. New attempts also show
684
- their persistent run ID for `veo inspect run <id>`. `veo history --json`
685
- prints `{"count":N,"entries":[…]}` for scripting. Each entry has
686
- `at`, `url`, `title`, `status`, `media` (`video`/`audio`), `quality`, `format`, the
687
- complete `files` list, `error`, `elapsedMs` and an optional `job` path. Titles and paths are stored without
688
- terminal control characters.
689
-
690
- History is one small JSON file per attempt in the per-user veo cache's `history`
691
- directory, so parallel runs cannot overwrite each other and each record stays small. The
692
- directory keeps one file per attempt and contains URLs, titles and local file paths;
693
- delete it to remove those records. `veo flush` keeps the list (only `veo flush --stats`
694
- resets statistics, not history).
695
-
696
- ### `veo changes`
697
-
698
- Lists every **released version**, newest first, and marks the installed one. `veo changelog`
699
- is an alias. The notes are parsed from the `CHANGELOG.md` that ships inside the package, so
700
- they always belong to the installed code and the command needs no network access.
701
-
702
- ```bash
703
- veo changes # all versions with their Added/Changed/Fixed counts
704
- veo changes 1.10.1 # the notes of one release
705
- veo changes --version 1.10.1 # the same, spelled out
706
- veo changes 1.10 # newest release starting with 1.10
707
- veo changes --latest # only the newest release
708
- veo changes --since 1.10.0 # everything newer than 1.10.0, up to the newest release
709
- veo changes --range 1.9.0..1.11.0 # a closed range, newest first
710
- veo changes --json # machine-readable
711
- ```
712
-
713
- An unknown version fails with the known versions listed. Long entries wrap to the terminal
714
- width, and `--json` returns the complete text. Because the list already names the released
715
- versions, `veo changes` prints no automatic update hint.
716
-
717
- `veo changes --json` prints `{"current":…,"count":N,"versions":[…]}`, where each entry has
718
- `version`, `date` (currently `null`, since the changelog headings carry no date), `installed`,
719
- `counts` and `summary`. One version adds `"version"`, `"installed"` and `"sections"`; a range
720
- adds `"from"` and `"to"` and keeps the per-release `sections`. Sections are
721
- `{"type":"Added","items":[{"text":"…","items":[…]}]}`, and nested items are preserved.
722
-
723
- ### `veo runs` and `veo stop`
724
-
725
- Every run registers itself while it works, under a **6-character id**, and removes that
726
- record when it ends:
727
-
728
- ```bash
729
- veo runs # active runs: id, PID, state, start time, progress, output directory
730
- veo runs k3f9qa # one run in detail: URLs, settings, job file, per-item state
731
- veo stop k3f9qa # ask that run to stop and wait until it exits
732
- veo stop # stop every active run
733
- ```
734
-
735
- `veo runs` lists the runs of this user, including those started in another terminal, and a
736
- run disappears from the list as soon as it finishes. Progress such as
737
- `1/3 done, 1 running` is read from the run's job file, which is created before the first
738
- download starts, so a run that is still preparing the backend shows `starting`. `veo stop
739
- <id>` writes a stop request that the run itself polls, so it needs no signals or PIDs and
740
- works the same on every platform. A stopped run exits like Ctrl+C (`130`) and keeps its
741
- partial data and retry job, so the printed `veo --retry-failed` command still works —
742
- unlike `veo flush`, which also removes that data. If a run does not stop within 15 seconds,
743
- `veo stop` reports it and exits with `1`.
744
-
745
- A run that crashed without cleaning up leaves its record behind: `veo runs` marks it
746
- `stale` and `veo stop` removes it, with or without its id. Records contain the id, process
747
- id, start time, URLs, output directory and job path — never cookie files, browser sessions
748
- or other credentials. Unknown or damaged record files are ignored instead of breaking veo,
749
- because a single stray file in the cache must never stop later runs.
750
-
751
- ### `veo doctor`
752
-
753
- Prints one line per check: `ok`, `warn` or `fail`. It inspects Node.js, the platform, the
754
- output directory, the backend cache, yt-dlp (including the SHA-256 of the cached binary and
755
- the version it reports), FFmpeg/FFprobe, a system FFmpeg fallback, the config file, leftover
756
- partial downloads, stale duplicate-detection records, the npm registry and the yt-dlp release host. It downloads no backend and
757
- only creates its own probe files plus the backend cache directory. Exit status is `1` when
758
- at least one check fails.
759
-
760
- Run `veo doctor fix` to restore missing or damaged managed tools and check again.
761
- It stages bundled FFmpeg/FFprobe and downloads verified yt-dlp when needed. Missing
762
- desktop media tools are installed in veo's cache using npm and the pinned media
763
- packages. On Termux it installs missing native tools with the package manager.
764
- With `--offline`, it only uses local binaries; missing tools are reported for an online retry.
765
- Use `-o PATH` to create and check an output directory. Config errors and invalid overrides
766
- are reported for manual correction; PATH and config values are never rewritten.
767
- A missing system FFmpeg is not a warning when the selected media tools work.
768
-
769
- ## Supported sites and backend
770
-
771
- YouTube, X/Twitter, TikTok, Vimeo, Reddit, Instagram, and [many other yt-dlp sites](https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md) are supported **when publicly accessible and technically available**. Support changes with websites, regions, rate limits, and backend versions; it is not a guarantee that every URL will work.
772
-
773
- Official standalone yt-dlp is acquired on first download and cached outside the package directory; help, version and plain doctor never download the backend. The release is pinned and SHA-256 verified against hashes shipped with this package. The npm package has no runtime dependencies and runs no installation scripts.
774
-
775
- When no trusted local FFmpeg/FFprobe pair is available, veo downloads both tools directly from the upstream [ffmpeg-static binary releases](https://github.com/eugeneware/ffmpeg-static/releases/tag/b6.1.1). Release URLs, compressed and executable SHA-256 hashes, and byte counts are pinned in the package. The complete pair and its license and README must pass verification before either executable is started. Later uses verify the cached pair again; legacy or damaged cached binaries are not accepted merely because they are executable. Most targets use FFmpeg 6.1.1; Windows ia32 uses the last available pair, FFmpeg 6.0. Old `FFMPEG_BINARIES_URL`, `FFMPEG_BINARY_RELEASE` and `FFMPEG_BIN` variables do not change these downloads. Use `VEO_FFMPEG_PATH` to choose a trusted installed pair explicitly.
776
-
777
- The downloaded binaries retain their separate upstream licenses. `FFmpeg.LICENSE` and `FFmpeg.README` are saved next to the cached tools, including upstream source and build information. The JavaScript CLI remains MIT licensed. No Python installation is needed on supported standalone platforms.
778
-
779
- The existing Node executable is explicitly enabled as yt-dlp's JavaScript runtime for YouTube. Local yt-dlp configuration and plugins are disabled for predictable execution. Arguments are passed without a shell.
780
-
781
- Windows ARM64 uses an FFmpeg/FFprobe pair found on `PATH`, including the usual
782
- WinGet, Chocolatey and `C:\ffmpeg\bin` locations, or automatically downloads x64
783
- media tools for Windows 11's x64 emulation.
784
-
785
- ### Android / Termux
786
-
787
- With Node.js 22+ and npm already installed in Termux:
788
-
789
- ```sh
790
- npm install -g veodl
791
- veo "URL"
792
- ```
793
-
794
- On Android, veo automatically finds `yt-dlp`, `ffmpeg` and `ffprobe` on PATH
795
- (also checking `$PREFIX/bin`). If tools or the `yt-dlp-ejs` Python module are
796
- missing, it runs `pkg install -y python-yt-dlp yt-dlp-ejs ffmpeg` automatically,
797
- then checks the installed programs before continuing the download. Python is
798
- installed as a dependency. Termux with the pacman package manager uses
799
- `--noconfirm` instead. This needs a working Termux repository and internet access.
800
- No root access is needed. Later runs reuse the installed tools.
801
-
802
- The npm installation does not pull in desktop FFmpeg packages.
803
- The system yt-dlp is maintained by Termux and is
804
- not pinned or hash-verified by veo. Explicit `VEO_YT_DLP_PATH` and
805
- `VEO_FFMPEG_PATH` overrides still take precedence.
806
-
807
- `veo doctor fix` also performs this automatic setup; `veo doctor fix --offline`
808
- never invokes a package manager. Update Termux packages with `pkg upgrade`;
809
- `veo backend update` prints this platform's update instructions.
810
-
811
- To save files in Android's shared Downloads folder, run `termux-setup-storage`,
812
- grant the storage permission, then use `veo "URL" -o ~/storage/downloads`.
813
- Keep veo and its tools in Termux's private storage.
814
-
815
- For existing published veo 1.6.1 installations (before this automatic setup), run:
816
-
817
- ```sh
818
- pkg install python-yt-dlp yt-dlp-ejs ffmpeg
819
- export VEO_YT_DLP_PATH="$(command -v yt-dlp)"
820
- export VEO_FFMPEG_PATH="$PREFIX/bin"
821
- veo doctor
822
- ```
823
-
824
- These exports apply to the current shell; add them to your shell startup file
825
- if you need them on subsequent launches of 1.6.1.
826
-
827
- ### Advanced overrides
828
-
829
- - `VEO_YT_DLP_PATH`: absolute path to a trusted, current yt-dlp executable. It always takes
830
- precedence over the managed backend, including one installed by `veo backend update`.
831
- - `VEO_FFMPEG_PATH`: directory containing both `ffmpeg` and `ffprobe` executables (with
832
- `.exe` on Windows).
833
- - `VEO_CONFIG`: path to the config file.
834
-
835
- Automatic setup covers Windows x64/ia32/ARM64, macOS x64/ARM64, Linux x64/ARM64,
836
- and Android through Termux. Windows ARM64 media tools require x64 emulation.
837
- `--open` uses `termux-open` in Termux; another Android app must be available to view the file.
838
- Other systems should supply trusted native binaries. Node.js 22+, npm, internet
839
- access, executable private storage and working OS libraries are prerequisites;
840
- network blocks or unavailable repositories can still prevent setup. Android's
841
- shared-storage permission must be granted by the user. Maintainers should update
842
- the pinned yt-dlp release and hashes as websites change,
843
- or users can install a newer release themselves.
844
-
845
- ### Installing a newer backend
846
-
847
- ```bash
848
- veo backend update --check # is a newer yt-dlp release available?
849
- veo backend update # install it
850
- veo backend reset # forget it, use the pinned release again
851
- ```
852
-
853
- The pinned backend is hash-verified against values compiled into this veo version. That
854
- guarantee cannot cover a release that did not exist when this version was built, so
855
- `veo backend update` verifies the download against the `SHA2-256SUMS` file published with
856
- that release over HTTPS: the trust anchor becomes HTTPS and GitHub instead of the npm
857
- package. It is opt-in, it says so on stderr when it runs, and `veo backend reset` (plus the
858
- next `veo update`) returns to the pinned release. An installed release is used only while it
859
- is newer than the pinned one and its bytes still match the recorded hash; anything else
860
- silently falls back to the pinned release.
861
-
862
- ## Develop and publish
863
-
864
- ```bash
865
- npm install
866
- npm test # unit tests, no network
867
- npm run test:smoke # real downloads: local HTTP server, real yt-dlp, real FFmpeg
868
- npm run test:open # real download plus a controlled desktop-opener replacement
869
- npm pack
870
- npm run check:package # inspect the packed tarball
871
- npm link
872
- veo --version
873
- # Then, authenticated as the account that owns the name:
874
- npm publish
875
- ```
876
-
877
- The smoke test generates a two-second synthetic video with FFmpeg, serves it on loopback,
878
- and runs real yt-dlp downloads. It checks default output, the quality cap, duplicate names,
879
- audio extraction, conversion, dry-run, format listing, JSON output, metadata embedding,
880
- resume, batch behaviour, playlist selection and nonzero failures, and verifies that a
881
- finished download whose destination was unavailable is retried from the local cache
882
- without contacting the source again. It also confirms that real runs leave `veo history`
883
- entries, that finished runs clean up their record, and that `veo runs`/`veo stop` report
884
- nothing left behind. It needs network access once for yt-dlp acquisition; it downloads no
885
- third-party video. Prepare media tools with `veo doctor fix` before `npm test` on a fresh checkout; the tests themselves use local fixtures and require no network. CI explicitly prepares and verifies the native binary pair before testing.
886
-
887
- The published tarball only includes `bin/`, `src/`, package metadata, README and LICENSE.
888
- The lockfile is kept for reproducible development. There is no build step. npm makes the
889
- shebang-bearing bin executable on installation (on Unix, `chmod +x bin/veo.js` also enables
890
- direct checkout execution).
891
-
892
- Continuous integration runs the unit tests on Windows, macOS and Linux for Node 22 and 24,
893
- runs the end-to-end smoke test on Node 22, and verifies the packed tarball.
894
-
895
- Modules separate argument parsing (`src/cli.js`), configuration (`src/config.js`,
896
- `src/config-errors.js`, `src/config-template.js`), backend setup (`src/backend.js`),
897
- backend updates (`src/backend-update.js`), download orchestration (`src/downloader.js`,
898
- `src/download-cache.js`), retry jobs (`src/jobs.js`), playlists (`src/playlist.js`),
899
- statistics, download history, the run registry and cleanup (`src/stats.js`,
900
- `src/history.js`, `src/runs.js`, `src/flush.js`), diagnostics (`src/doctor.js`),
901
- progress (`src/progress.js`), and helpers (`src/utils.js`, `src/paths.js`,
902
- `src/state.js`, `src/version.js`) so new options and providers can be added without
903
- replacing the CLI.
904
-
905
- ## License
906
-
907
- MIT for this CLI. yt-dlp and FFmpeg/FFprobe retain their respective upstream licenses.
908
- # Clickable paths
909
-
910
- In supported terminals (Windows Terminal, iTerm2, WezTerm, Kitty, VS Code and recent VTE terminals), the `Saved:` path, `veo config path` and the config editor's header link to the containing folder. Use the terminal's link gesture, usually Ctrl+click or Cmd+click. The terminal and operating system decide whether file links open the file manager; unsupported terminals and redirected output show plain paths. Missing paths are not linked. JSON output remains plain.
1
+ # veo
2
+
3
+ A small, clean video-downloading CLI powered by [yt-dlp](https://github.com/yt-dlp/yt-dlp). No banners, just progress and the saved file path.
4
+
5
+ Automating with an agent? Start with the [agent guide](docs/AGENT_GUIDE.md) for JSON output, run inspection and audio verification.
6
+
7
+ **Only download content you own or are authorized or legally permitted to download.** Respect copyright, website terms, and access restrictions. veo does not bypass DRM, private-content access controls, or paywalls.
8
+
9
+ ## Install and run
10
+
11
+ veo is a single native executable (written in Rust) for Windows, macOS, Linux and
12
+ Android/Termux. Internet access is needed for installation and first-use backend setup.
13
+
14
+ The npm package is `veodl`; the installed commands are `veo` and `veodl`. npm installs the
15
+ prebuilt binary for your platform from a matching `veodl-<platform>-<arch>` package; no
16
+ install scripts run. Node.js is only needed for npm itself, not to run downloads.
17
+
18
+ ```bash
19
+ npx veodl "https://example.com/video.mp4"
20
+ ```
21
+
22
+ Or install globally:
23
+
24
+ ```bash
25
+ npm install -g veodl
26
+ veo "https://example.com/video.mp4"
27
+ # or: veodl "https://example.com/video.mp4"
28
+ ```
29
+
30
+ The first download automatically prepares missing yt-dlp, FFmpeg and FFprobe.
31
+ On Android/Termux this includes installing the native packages and yt-dlp's
32
+ JavaScript support with `pkg install -y`. On desktop systems, missing media tools
33
+ are downloaded into veo's own cache. Run `veo doctor fix` to prepare everything
34
+ before downloading. Plain `veo doctor`, help and version do not install tools.
35
+
36
+ Without npm, download the binary for your platform from the GitHub releases page, or
37
+ build it with Rust 1.85+:
38
+
39
+ ```bash
40
+ cargo install --git https://github.com/Mailo037/veodl
41
+ ```
42
+
43
+ From this checkout:
44
+
45
+ ```bash
46
+ cargo build --release # target/release/veo
47
+ cargo test # unit and offline end-to-end tests
48
+ cargo test --test smoke -- --ignored # real downloads with yt-dlp and FFmpeg
49
+ ```
50
+
51
+ Use only trusted URLs, and quote URLs to protect query strings from your shell.
52
+
53
+ If something does not work, run `veo doctor` first: it inspects the whole local setup and prints one line per check.
54
+
55
+ Run `veo` without arguments in an interactive terminal for a guided download: choose a
56
+ profile, enter a link, select video or audio, choose available resolution and output
57
+ directory, and start. The wizard checks the link and asks about playlist mode only when
58
+ it finds a collection; configured playlist and entry selection values are preselected.
59
+ Playlist mode also lists entries for selection. The wizard enables
60
+ resume and offers skipping previously downloaded files. With redirected input, use the
61
+ regular command arguments; veo never starts a prompt in a script.
62
+
63
+ ## Usage
64
+
65
+ ```text
66
+ Usage:
67
+ veo <url> [<url>...] [options]
68
+
69
+ Download:
70
+ -q, --quality <quality> Video quality (best, 2160p, 1440p, 1080p, 720p, 480p, 360p)
71
+ Numeric qualities are an upper bound: -q 720p never
72
+ downloads 2160p.
73
+ --closest-quality Pick the nearest available resolution, above or below
74
+ -o, --output <path> Output directory (default: current directory)
75
+ -r, --rename <name> Filename without extension; * inserts the original title
76
+ --audio Download audio only (default: mp3)
77
+ --format <format> Video: mp4, mkv, webm, mov; audio: mp3, m4a, aac, opus, flac, wav
78
+ Video uses lossless remux; incompatible codecs fail.
79
+ --compatible Ensure MP4 H.264/AAC; converts only when needed (may lose quality)
80
+ --recode Allow video conversion (requires --format; may lose quality)
81
+ --open Open the saved file with your default app
82
+ --resume Keep partial data and continue an interrupted download
83
+ --skip-existing Skip matching downloads still present on disk
84
+ --section <range> Download only a time range, e.g. "*10:00-12:00"
85
+ --sponsorblock-remove <categories>
86
+ Remove sponsor segments, e.g. "sponsor,selfpromo"
87
+
88
+ Subtitles and metadata:
89
+ --subs Download subtitles (default languages: en)
90
+ --auto-subs Include automatically generated subtitles
91
+ --sub-langs <langs> Subtitle languages, e.g. "de,en" (manual unless --auto-subs)
92
+ --sub-format <format> Preferred subtitle format, e.g. srt/vtt/best
93
+ --list-subs Show manual and automatic subtitle languages, then exit
94
+ --embed-subs Embed subtitles into the video file
95
+ --embed-metadata Embed title, date and other metadata
96
+ --embed-thumbnail Embed the thumbnail
97
+
98
+ Playlists and batches:
99
+ --playlist Download every entry of a playlist or channel URL
100
+ --playlist-items <list> Select playlist entries, e.g. 1,3-5 (implies --playlist)
101
+ --playlist-concurrency <n> Simultaneous playlist downloads (1-4; default: 2)
102
+ --concurrent-downloads <n> Parallel URLs/batch entries (1-4; default: 2)
103
+ --batch-file <file> Read URLs from a file (one per line; # comments)
104
+ --retry-failed <id|file> Retry failed/unfinished items by run ID or job file
105
+
106
+ Naming and privacy:
107
+ --filename-template <s> Filename without extension, e.g. {index} - {title}
108
+ --folder-template <s> Relative folders, e.g. {channel}/{year}
109
+ --neutral-filename Use a random video name; omit title and naming templates
110
+ --incognito Save media without run IDs, history, stats or retry data
111
+
112
+ Inspect before downloading:
113
+ --list-qualities Show available video resolutions and exit
114
+ --list-qualitys Alias for --list-qualities
115
+ --list-formats Show the available formats and exit
116
+ --list-sources Find video sources loaded by a web page and exit
117
+ --auto-list-sources Search after no video is found (default: on)
118
+ --source <n> Select source number from that page (also for scripts)
119
+ --deep-scan Check every media candidate found during source search
120
+ --timeout <duration> Source search deadline, e.g. 30, 30s or 2m (5s-10m)
121
+ --dry-run Show what would be downloaded and exit
122
+
123
+ Network and performance:
124
+ -N, --concurrent-fragments <n>
125
+ Parallel fragments (1-64; default: 8); 17-64 are experimental
126
+ --experimental-fragments Explicitly accept experimental values above 16
127
+ Otherwise requires terminal confirmation before download
128
+ --adaptive-concurrency Reduce connections and retry temporary failures (default: on)
129
+ --check-space Estimate cache/output space before downloading (default: on)
130
+ --cookies <file> Netscape cookie file, for content you may access
131
+ --cookies-from-browser <browser[:profile]>
132
+ Read cookies from an installed browser
133
+
134
+ Output and scripting:
135
+ --json Print one JSON object per URL instead of prose
136
+ --verify Probe saved media files with FFprobe (default: off)
137
+ --timings Show phase timings (default: on; --no-timings disables)
138
+ --no-color Disable terminal colors (also respects NO_COLOR)
139
+ --profile <name> Apply a named config profile
140
+ --no-<boolean-option> Disable a stored boolean default, e.g. --no-open
141
+ -v, --version Show installed version (also: veo version)
142
+ -h, --help Show help; veo help all lists every option and command
143
+
144
+ Commands:
145
+ veo doctor Diagnose the local setup
146
+ veo update|up [--check] Update veo itself with npm (up is a short alias)
147
+ veo history Show the last 5 downloads (--json for scripting)
148
+ veo history --failed --limit 20
149
+ Filter and extend download history
150
+ veo retry --last Retry the newest failed or unfinished job
151
+ veo runs [id] List active runs; add --json for metadata and progress
152
+ veo stop [id] Stop one run, or every active run
153
+ veo stats Show persistent download statistics
154
+ veo changes [<version>] List release notes; --since <version> for newer changes
155
+ veo subs <url> List available manual and automatic subtitles
156
+ veo inspect <file> Read media metadata; --check-audio measures audio signal
157
+ veo inspect run <id> Inspect saved files from a finished run by its id
158
+ veo backend update Install a newer yt-dlp release (see veo backend --help)
159
+ veo flush Stop veo runs and clear temporary downloads and jobs
160
+ veo config edit|path|profiles|guide|check|show|reset
161
+ Manage defaults and named profiles
162
+ veo config edit [--external|--terminal]
163
+ Choose the configuration editor
164
+ veo profile [list|reset|name]
165
+ Show, list or change the default profile
166
+ veo alias list|add|remove Manage extra command names (wrappers calling veo)
167
+ veo uninstall [-p <name>] Remove one alias, or everything with --yes
168
+ veo version Show the installed version
169
+ ```
170
+
171
+ ```bash
172
+ veo "https://youtube.com/watch?v=VIDEO_ID" -q 1080p
173
+ veo "https://x.com/USER/status/STATUS_ID" -q best
174
+ veo "https://example.com/video.mp4" --audio
175
+ veo "https://example.com/video.mp4" --audio --format flac
176
+ veo "https://example.com/video.mp4" --format webm --recode -o ./videos
177
+ veo "https://example.com/video.mp4" -r "My Video" --format mp4 --open
178
+ veo "https://example.com/video.mp4" --audio -r "My Music"
179
+ veo <url1> <url2> <url3> --embed-metadata --subs
180
+ veo "https://youtube.com/playlist?list=LIST_ID" --playlist -q 720p
181
+ veo "https://example.com/video.mp4" --resume
182
+ npx veodl "https://example.com/video.mp4" --output ./downloads
183
+ ```
184
+
185
+ ### Several URLs and playlists
186
+
187
+ Playlist downloads use two concurrent entries by default; use `--playlist-concurrency 1` for sequential downloads. Each entry uses up to eight concurrent DASH/HLS fragments by default (`-N 1` disables fragment parallelism). Fragment counts between 1 and 16 need no confirmation. Values from 17 to 64 are experimental and prompt for `y/N` confirmation in terminals, or require `--experimental-fragments` (or `"experimentalFragments": true` in configuration/profiles) in non-interactive environments and JSON mode. Values above 64 are rejected. More connections help only when the source and connection have spare capacity, and excessive connections may trigger server limits. Video format changes preserve the encoded streams unless `--recode` is explicitly enabled; audio extraction and precise section cuts retain their existing conversion behavior.
188
+
189
+ - Pass any number of URLs; up to two run concurrently by default. Use
190
+ `--concurrent-downloads 1` for sequential processing. A failure is reported
191
+ with its URL and does not stop the remaining URLs; the exit status is `1` if anything
192
+ failed.
193
+ - Playlists, channels and other collections are refused by default with a hint. Add
194
+ `--playlist` to download every entry. Each entry keeps its own title; `--rename` applies
195
+ a name prefix per entry (`My Name - 001`), or substitutes each title with `movie_*`.
196
+ - Use `--playlist-items 1,3-5` to select entries by their original, one-based index.
197
+ Playlist entries run with bounded parallelism: a failed entry does not discard successful
198
+ files or stop the remaining entries. Before downloading, veo displays the selected count
199
+ and a size estimate when the source supplies sizes. Unknown sizes are labeled explicitly;
200
+ metadata estimates do not predict conversion size.
201
+ - `--batch-file links.txt` accepts a UTF-8 URL list, optionally alongside URLs on the command
202
+ line. A final summary counts saved, skipped and failed videos. Successful files are opened
203
+ with `--open` even if another URL or playlist entry fails.
204
+ - Failed or cancelled jobs print a ready-to-use `veo --retry-failed <runId>` command. Saved job file paths are also accepted.
205
+ - Use `veo retry --last` to retry the newest failed or unfinished job without copying its path. Active runs are ignored; use `veo runs` to inspect them.
206
+ Jobs are stored under the per-user veo cache's `jobs` directory. Retries retain resolved
207
+ output directories and settings, even from a different working directory, and explicit
208
+ flags can override them. Completed playlist jobs retry only failed indices; interrupted
209
+ playlists revisit their selection and skip completed entries through resume history.
210
+ Playlist indices refer to the playlist's current order, so changes to that order can
211
+ change what an index selects. Cookie paths and browser-session settings are not saved in
212
+ retry jobs; pass those flags again if needed. Job files contain URLs, settings and local
213
+ file paths and can be deleted when no longer needed.
214
+
215
+ ```bash
216
+ veo --batch-file links.txt --profile archive
217
+ veo "https://example.com/playlist" --playlist-items 1,3-5 --resume
218
+ veo "https://example.com/playlist" --playlist --skip-existing
219
+ veo --retry-failed "C:\path\to\job.json"
220
+ veo --retry-failed abc123
221
+ veo retry --last
222
+ ```
223
+
224
+ ### Quality
225
+
226
+ - Numeric qualities are an **upper bound**: `-q 720p` downloads the best resolution at or
227
+ below 720p, so a metered connection can never accidentally receive a 2160p file. If a
228
+ source offers nothing at or below the request, veo stops before downloading and lists
229
+ the resolutions that do exist.
230
+ - `--closest-quality` restores the former "nearest available resolution" rule, which may
231
+ pick a resolution *above* the request. It needs a numeric `--quality`.
232
+ - Sources that report no resolution metadata fall back to the best available stream.
233
+ Collections resolve and enforce the quality limit separately for each entry.
234
+ - Other positive numeric resolutions, such as `-q 540p`, are also accepted. The interactive
235
+ wizard offers the resolutions reported by the source; without resolution metadata it
236
+ offers `best` rather than inventing available streams.
237
+ - `--quality` does not apply to audio.
238
+ - MP4-compatible codecs are preferred at the selected resolution; merged video prefers MP4
239
+ with MKV fallback. A single-file source may retain its original container. Use
240
+ `--format mp4` to require MP4 through lossless remuxing. Incompatible codecs cause an error.
241
+ - `--format` on video remuxes without re-encoding. Add `--recode` to explicitly allow conversion, which may be slow or lossy. Audio formats require `--audio`.
242
+
243
+ ### Playback compatibility
244
+
245
+ H.264/AAC sources are preferred at the selected quality, including explicit MP4 output.
246
+ Original codecs are retained by default; a playback note identifies media needing extra
247
+ player codecs. MP4 is a container, not a promise of a particular codec.
248
+
249
+ Use `veo URL --compatible` to ensure MP4 with H.264 8-bit 4:2:0 and AAC.
250
+ Already compatible streams are copied; only incompatible streams are encoded (H.264 CRF 18,
251
+ AAC 192 kbit/s), which takes time and may lose quality. HDR requiring video conversion
252
+ is refused rather than silently losing correct colors. Embedded subtitles/thumbnails are
253
+ not supported in this mode; separate subtitle files remain supported.
254
+ New configs include a `kompatibel` profile; existing configs receive a commented profile
255
+ example through `veo config edit`. Enable it with `veo URL --profile kompatibel`.
256
+
257
+ ### Download controls and diagnostics
258
+
259
+ - URL lists and batch files use `--concurrent-downloads 2` by default (range 1–4).
260
+ Each playlist independently uses `--playlist-concurrency 2` (range 1–4).
261
+ The worker limits multiply when several playlists run at once, while media
262
+ connections share a host-local budget (normally `concurrentFragments`, or the
263
+ larger of that and `httpConnections` for eligible native HTTP transfers).
264
+ JSON emits one complete
265
+ object per URL in completion order; the `url` field identifies each result.
266
+ - Adaptive concurrency is on by default. After the backend's own retries are exhausted,
267
+ HTTP 429/5xx and temporary connection failures trigger at most two further attempts,
268
+ after 2 and 4 seconds. Capacity is reduced for that host, then recovers one step
269
+ after four successful operations. Workers wait for capacity and remain available
270
+ for later items. Active backend transfers retain their connection count until
271
+ the next attempt; format and quality stay fixed.
272
+ Partials are continued on retry. Use `--no-adaptive-concurrency` to disable this.
273
+ - `--native-http` is on by default for anonymous, direct HTTP(S) video files that
274
+ require no subtitle, metadata or section processing. Rust reuses HTTP connections
275
+ and writes byte ranges into disjoint offsets of one file, avoiding an assembly
276
+ copy. `--http-connections 4` controls native connections (1–16); the shared host
277
+ budget may reduce the effective count. Files smaller than 8 MiB use one range.
278
+ A strong ETag and valid byte-range responses are required for ranged resume;
279
+ changed ETags discard partial data. Servers without range support or a strong
280
+ validator use a full stream. Unsupported responses fall back to yt-dlp.
281
+ Cookies, custom authentication headers, multiple formats and transformations
282
+ continue through yt-dlp. Use `--no-native-http` to force that path.
283
+ - Single-video extraction results are reused through a private temporary info
284
+ file, removed when yt-dlp exits. Expired media information is refreshed. Flat
285
+ playlists use direct entry page URLs when available, preserving entry indices
286
+ for naming, selection and retry. Raw extraction results are never archived.
287
+ - `--staging-dir output` stages under `<output>/.veo-staging`, so the final save can
288
+ use a hard link on supported filesystems instead of copying across drives.
289
+ `--staging-dir D:/veo-cache` chooses a specific staging directory. The default
290
+ remains local-cache staging, which can work better for slow network/cloud drives.
291
+ Explicit roots are registered for expiration cleanup and `veo flush`; flush
292
+ removes only veo partial directories and keeps saved media. Incognito creates
293
+ an unregistered private child directory and removes it after the attempt.
294
+ - `--section "*10:00-12:00" --fast-cut` skips forced keyframes for a faster cut
295
+ aligned to nearby existing keyframes. Start/end positions can be less precise.
296
+ Omit `--fast-cut` to keep precise cuts. This setting changes download identity,
297
+ so a previous precise-cut result cannot be reused as a fast-cut result.
298
+ - `--verify` reuses the compatibility probe only when saving preserves the exact
299
+ file identity, size and modification time. Converted files, copies and retained
300
+ transfer retries receive a fresh probe.
301
+ - `--check-space` checks cache and destination free space before media transfer. Known
302
+ sizes include conservative room for merge output and a destination copy, plus space
303
+ reserved by concurrent downloads in the same process. Unknown sizes are reported as
304
+ unknown; conversion sizes and other processes' disk usage cannot be predicted exactly.
305
+ Use `--no-check-space` to disable this conservative check.
306
+ - `--timings` shows phase times at the end of the run. Single downloads show
307
+ timing without a summary; multiple downloads show timing below the summary.
308
+ The phases cover setup, metadata, download/backend, processing, saving and retry waits.
309
+ Download/backend includes the backend's own startup and extraction overhead;
310
+ these are summed per-download times, not a diagnosis of network speed.
311
+ JSON results include `timings` in milliseconds, or `entryTimings` for playlists.
312
+ Use `--no-timings` to hide/omit them.
313
+ - Terminal details are gray, titles bold, saves green and errors red. The same styling
314
+ applies to stats, history, runs/stop, doctor, flush, updates, config prose, help and
315
+ interactive prompts. For example, `veo stats --no-color` disables it for one command. Redirected output
316
+ and JSON have no color escapes. `--no-color`, `"color": false`, `NO_COLOR`,
317
+ and `TERM=dumb` disable colors.
318
+ Terminal reports shorten long URLs, paths and detail values to the current window
319
+ width. Titles can use up to three lines before an ellipsis is added. This also
320
+ applies with `--no-color`; JSON and redirected output retain complete values.
321
+ Downloads stay in the normal terminal flow. Only the current progress block is
322
+ updated in place; narrow windows put the statistics below the progress bar.
323
+ Existing logs remain in scrollback and are not replayed or rewritten on resize.
324
+ This can be set independently per profile, for example
325
+ `"profiles": { "default": { "color": true }, "plain": { "color": false } }`.
326
+ Use `veo stats --profile plain` or `veo URL --profile plain`; `--color` or
327
+ `--no-color` overrides the profile for one invocation (except `NO_COLOR`).
328
+ External npm and editor output keeps its own formatting.
329
+
330
+ ### Filename templates and folders
331
+
332
+ `--filename-template` controls the name without extension; `--folder-template`
333
+ creates relative subfolders under `--output`. Both support `{title}`, `{id}`,
334
+ `{channel}`, `{year}`, `{playlist}` and `{index}` (three-digit playlist index).
335
+ Missing metadata uses readable fallbacks. `{year}` is the upload year when available.
336
+ Subtitles follow the media name. Existing files are never overwritten. Templates cannot
337
+ escape the output folder, and symlink/junction subfolders are refused. Use forward slashes
338
+ for folders. `--rename` and `--filename-template` are mutually exclusive.
339
+
340
+ ```powershell
341
+ veo "VIDEO-URL" --folder-template "{channel}/{year}" --filename-template "{title} - {id}"
342
+ veo "PLAYLIST-URL" --playlist --folder-template "{playlist}" --filename-template "{index} - {title}"
343
+ veo config check
344
+ veo config check --profile fast
345
+ veo config show --profile fast
346
+ ```
347
+
348
+ Config checks validate every effective profile (or the selected one), including unknown
349
+ settings, ranges and conflicting options. Show prints merged built-in, global and profile
350
+ settings as JSON, with credential paths/browser profiles redacted. Neither command contacts
351
+ video sites. All new options are also documented in `veo config guide`.
352
+
353
+ ### Files, names and resume
354
+
355
+ - Original titles are preserved by default. Use `-r "My Video"` or `--rename "My Video"`
356
+ for video or audio. Supply the name **without an extension**; the actual media extension
357
+ is appended automatically. Use `-o` for the directory. Invalid filename characters are
358
+ sanitized and overly long names shortened. Existing files are never intentionally
359
+ overwritten: duplicates get ` (1)`, ` (2)`, etc.
360
+ - Use `-r "movie_*"` or `"rename": "movie_*"` in your config/profile to insert the
361
+ original title: `My Film` becomes `movie_My Film.mp4`. Every `*` is substituted.
362
+ Quote the pattern in your shell. Patterns also work with URL batches and playlists.
363
+ - Subtitles and thumbnails are saved beside the media file under the same base name
364
+ (`My Video.mp4` → `My Video.en.vtt`).
365
+ - Downloads and media processing first finish in the local per-user veo cache, under
366
+ `downloads` (`%LOCALAPPDATA%\veo\downloads` on Windows). The destination is only written
367
+ after the media is ready. Saving uses a hard link when supported or an exclusive copy
368
+ across drives/cloud mounts; copied file sizes are checked. Existing files are never
369
+ overwritten. The local original is removed only after files and history are saved.
370
+ Local disk space is therefore needed for the complete download and processing files.
371
+ - If saving fails or is cancelled after media processing completes, the local original and
372
+ sidecars are kept for **15 minutes**, even without `--resume`. Repeat the same command or
373
+ use the printed `--retry-failed` command to retry the transfer. Individual completed
374
+ downloads can be recovered without contacting their source again. The same source,
375
+ media settings and destination identify a cached transfer; changing those starts a new
376
+ download. Expired copies are removed on the next veo invocation, not by a background
377
+ timer while veo is closed. Active transfers are never expired. Another failed transfer
378
+ starts a fresh 15-minute retention period.
379
+ - `--resume` also keeps unfinished downloads in local `.veo-part-<request-hash>` folders
380
+ until resumed; unfinished data has no automatic expiry. Without `--resume`, unfinished
381
+ downloads are discarded on failure or cancellation. The hash separates source URLs,
382
+ playlist entries, destinations, quality, media type, format, sections and subtitle/metadata
383
+ options. A manifest records backend-confirmed
384
+ completion and saved files, so an unprocessed file is not mistaken for a finished video.
385
+ Each playlist entry has its own state. Completed entries survive later failures and are
386
+ skipped when resuming the same selection. Partial downloads are never resumed without
387
+ `--resume`. Legacy partial folders in the output directory are left untouched; the new
388
+ local cache does not automatically migrate them.
389
+ - A lock prevents two resume processes from using the same partial folder. Normal failures
390
+ and Ctrl+C release it. After a force kill, remove the named `.lock` file only after making
391
+ sure no veo process still uses that folder.
392
+ - Successful downloads record their source, output settings and saved paths in
393
+ `.veo-history` inside the output directory. `--skip-existing` uses these records and checks
394
+ that the files still exist. A different quality/format or deleted output is downloaded
395
+ again. Files downloaded before this history existed are not recognized automatically.
396
+ Deleting history removes duplicate detection, not downloaded media. Records whose files
397
+ no longer exist are removed on the next download into that folder, and `.veo-history`
398
+ itself is removed with its last record; `veo doctor` reports stale records.
399
+
400
+ ### Metadata and subtitles
401
+
402
+ - `veo subs <URL>` lists manual and automatically generated subtitles separately, including
403
+ their languages and source formats. `--json` returns one object without subtitle URLs.
404
+ - `--subs` writes manual subtitle files. `--auto-subs` adds generated subtitles, or selects
405
+ only generated subtitles when used alone. `--sub-langs de,en` selects languages;
406
+ `--sub-format srt/vtt/best` prefers available source formats in that order. A format
407
+ preference does not convert subtitles. `--embed-subs` embeds selected subtitles into
408
+ the video container.
409
+ - `--embed-metadata` and `--embed-thumbnail` use FFmpeg to write metadata and cover art.
410
+ - `--sponsorblock-remove sponsor,selfpromo` cuts sponsor segments (YouTube) and requires
411
+ FFmpeg; the cut re-encodes the affected parts.
412
+ - `--section "*10:00-12:00"` downloads only a time range.
413
+ - `-N 8` downloads several fragments in parallel, which is noticeably faster on HLS/DASH
414
+ sources and heavier on the network.
415
+
416
+ ### Optional post-download verification
417
+
418
+ Use `veo <URL> --verify` or set `"verify": true` in a config profile to check
419
+ saved media with FFprobe before recording a successful download. It checks that
420
+ the container can be probed and contains a video track (or an audio track for
421
+ `--audio`). Verification is off by default; `--no-verify` overrides a profile.
422
+ This quick check does not decode the whole file or prove that the audio is
423
+ audible. Use `veo inspect <file> --check-audio` for a full audio signal check.
424
+ If verification fails, veo reports failure and keeps the saved file for review.
425
+
426
+ ### Access and credentials
427
+
428
+ veo downloads publicly accessible content. It does not bypass access controls, and it does
429
+ not expose options that defeat them. If you need your own session for content you are
430
+ authorized to view:
431
+
432
+ - `--cookies ./cookies.txt` passes a Netscape-format cookie file. The file is validated
433
+ before any network work, and on Unix a file readable by other users produces a warning.
434
+ - `--cookies-from-browser firefox:Work` reads cookies from an installed browser: `brave`,
435
+ `chrome`, `chromium`, `edge`, `firefox`, `opera`, `safari`, `vivaldi`, `whale`, optionally
436
+ with `+gnomekeyring`/`+kwallet`/`+basic` and a profile or container.
437
+
438
+ Credentials are used for that single invocation only and are never stored by veo. When a
439
+ download fails because a login is required, the error message points at these flags.
440
+
441
+ ### Inspection and scripting
442
+
443
+ - `veo <page-url> --list-sources` opens the page in a temporary headless Chrome,
444
+ Edge or Chromium profile, repeatedly checks for visible Play controls (including
445
+ controls added later or inside frames), and watches media requests after interaction.
446
+ Use `--json` for numbered source
447
+ metadata. Each result includes its title, host, HLS/DASH/direct type, known
448
+ resolutions, estimated MiB size and Mbit/s bitrate when the source provides them. Signed media
449
+ URLs are not printed. Set `VEO_BROWSER_PATH` if the browser is not found.
450
+ - Add `--deep-scan` to verify every media candidate observed during the source
451
+ search. The normal search verifies at most 30 candidates. `--timeout 30s`
452
+ sets a deadline for browser observation and candidate verification; plain
453
+ numbers mean seconds, and `m` means minutes (5 seconds to 10 minutes).
454
+ Defaults are 45 seconds, or 2 minutes with `--deep-scan`. If the deadline
455
+ is reached, verified sources found so far are shown with a timeout notice.
456
+ A deep scan covers observed requests; it cannot discover sources that the
457
+ page never loads. The timeout does not stop a subsequent download.
458
+ - For an unsupported single page URL, veo searches for media automatically and
459
+ uses the only verified source when there is one. If several sources are found,
460
+ the terminal asks which one to use; scripts receive a numbered source list and
461
+ a nonzero exit. `veo <page-url> --source 2 --dry-run --json` previews a chosen
462
+ source, and `veo <page-url> --source 2 --json` downloads it. Scripts never
463
+ receive an interactive prompt.
464
+ Retry jobs retain the page URL and selected number and discover a fresh media
465
+ URL, because player links may expire. Browser discovery uses a temporary
466
+ profile without copying browser cookies, and cannot unlock DRM content.
467
+ Misspelled flags and commands show a nearby valid spelling, for example
468
+ `--deepscan` suggests `--deep-scan`.
469
+ - `veo <url> --list-qualities` lists available video resolutions, highest first,
470
+ without downloading media. `--list-qualitys` is an alias. Add `--json` for
471
+ a `qualities` array (e.g. `["1080p", "720p"]`); use `--quality 720p` to select
472
+ an upper bound. Unknown resolutions are reported without guessing. Audio-only,
473
+ storyboard and DRM formats are excluded. Supports `--source <number>` for embedded players.
474
+ - `--list-formats` prints the backend's own format table for one URL and exits.
475
+ - `--dry-run` prints the title, the resolved quality and the destination path that would be
476
+ used — without creating the output directory or downloading anything.
477
+ - `--json` prints one JSON object per line on stdout:
478
+ `{"url":…,"status":"saved","title":…,"files":[…]}` or
479
+ `{"url":…,"status":"failed","error":…}`. Progress and status still go to stderr. Without
480
+ `--json`, every saved file is printed as `Saved: <path>` on stdout.
481
+ Duplicate detection can return `status: "skipped"`; cancellation returns `"cancelled"`.
482
+ Playlist results also include `saved`, `skipped` and `failures` (with original indices).
483
+ A failed or cancelled playlist can still report files saved before the failure.
484
+ - `veo runs [id] --json` reports active run metadata and per-item progress. Finished attempts
485
+ remain available through `veo history --json`.
486
+ - `veo inspect <file> --json` reports local container and stream metadata. Add
487
+ `--check-audio` to decode all audio tracks and measure whether any peak exceeds
488
+ -60 dBFS. A track can exist without a detectable signal. The check reads the full
489
+ media file and uses only local FFmpeg/FFprobe tools.
490
+ - Download results and history retain a `runId`. `veo inspect run <id> --json` reads
491
+ a finished run and checks every saved file before probing it. Renamed media is
492
+ found by file identity, size and a sampled SHA-256 fingerprint within the original output tree;
493
+ add `--search <directory>` after moving it elsewhere. Ambiguous or missing files
494
+ are reported without probing another file. Old runs lack this persistent record.
495
+ Finished run records live in the current device's veo cache. On Windows, macOS,
496
+ Linux and Termux, `inspect` uses bundled media tools where supported and otherwise
497
+ uses local `ffmpeg` and `ffprobe` (or `VEO_FFMPEG_PATH`); it does not install them.
498
+ Copying only the media to another device does not copy its run ID record.
499
+
500
+ ### Config file
501
+
502
+ Defaults can be stored in a config file, so a long list of flags is not needed for every
503
+ call. `veo doctor` prints the exact path; `VEO_CONFIG` overrides it.
504
+ The file accepts JSON with `//` line comments and `/* ... */` block comments. Strings
505
+ (including URLs and Windows paths) keep their normal JSON escaping rules. Trailing commas
506
+ are not allowed.
507
+
508
+ - Windows: `%APPDATA%\veo\config.json`
509
+ - macOS: `~/Library/Application Support/veo/config.json`
510
+ - Linux: `$XDG_CONFIG_HOME/veo/config.json` (or `~/.config/veo/config.json`)
511
+
512
+ ```json
513
+ {
514
+ "output": "D:\\Videos",
515
+ "quality": "1080p",
516
+ "embedMetadata": true,
517
+ "subLangs": "de,en",
518
+ "concurrentFragments": 4
519
+ }
520
+ ```
521
+
522
+ Supported keys: `output`, `quality`, `format`, `rename`, `audio`, `open`, `resume`, `incognito`, `neutralFilename`,
523
+ `closestQuality`, `cookies`, `cookiesFromBrowser`, `playlist`, `concurrentFragments`,
524
+ `experimentalFragments`, `playlistConcurrency`, `recode`, `compatible`, `concurrentDownloads`, `adaptiveConcurrency`,
525
+ `filenameTemplate`, `folderTemplate`, `checkSpace`, `timings`, `color`,
526
+ `nativeHttp`, `httpConnections`, `stagingDir`, `fastCut`,
527
+ `subs`, `autoSubs`, `subLangs`, `subFormat`, `embedSubs`, `embedMetadata`, `embedThumbnail`, `sponsorblockRemove`, `verify`,
528
+ `section`, `json`. An explicit command-line flag always wins over a stored default. An
529
+ unknown key produces a warning; invalid JSON or a wrong value type is an error, because
530
+ silently ignoring a typo would be worse.
531
+
532
+ Source discovery defaults are `deepScan` (boolean), `timeout` (string such as
533
+ `"30s"` or `"2m"`), `listSources` (boolean) and `autoListSources` (boolean).
534
+ `listSources: true` makes a single URL list sources and exit; use
535
+ `--no-list-sources` to download for one invocation. `autoListSources` defaults
536
+ to `true` and searches when ordinary extraction finds no downloadable video.
537
+ Set it to `false` or pass `--no-auto-list-sources` to disable that fallback.
538
+ A single verified source is selected automatically; multiple sources require
539
+ a choice. `deepScan` and
540
+ `timeout` apply to either kind of source search.
541
+
542
+ Additional defaults are `skipExisting` and `playlistItems`. Boolean defaults can be disabled
543
+ with `--no-open`, `--no-audio`, `--no-resume`, `--no-embed-metadata`, etc. `--no-subs` also
544
+ disables stored subtitle languages and subtitle embedding for that invocation.
545
+
546
+ ### Incognito downloads
547
+
548
+ Use `veo <url> --incognito` or set `"incognito": true` in a named profile. The
549
+ downloaded media is saved normally. veo does not assign a run ID or write a
550
+ retry job, run archive, download history, output `.veo-history` record, or
551
+ statistics for that attempt. It uses a private temporary staging directory and
552
+ removes it after success, failure, or cancellation. It also disables yt-dlp's
553
+ filesystem cache. `--no-incognito` overrides
554
+ a profile for one request. Incognito cannot be combined with `--resume`,
555
+ `--skip-existing`, or `--retry-failed`; there is no veo retry for an incognito
556
+ attempt. A force-killed process may leave temporary files that the OS can
557
+ clean later. The destination file, shell history, network service, and source
558
+ website can still reveal the download.
559
+
560
+ Example profile: `"profiles": { "private": { "incognito": true } }`.
561
+ Add `"neutralFilename": true` to that profile, or pass `--neutral-filename`,
562
+ to save files as names such as `video-a1b2c3d4e5f6.mp4`. This is optional:
563
+ incognito keeps the normal title by default. Neutral naming overrides `--rename`,
564
+ `--filename-template`, and `--folder-template` for that request. The selected
565
+ output directory, media contents, and embedded metadata are unaffected.
566
+
567
+ ### Named profiles
568
+
569
+ If no profile is selected, `profiles.default` is applied automatically. `veo profile music`
570
+ selects another named profile for future commands and stores it as `activeProfile` in the
571
+ config; `veo profile` shows the current choice, and `veo profile list` marks it in
572
+ the list. `veo profile reset` removes the stored choice and returns to `default`.
573
+ You can also use `veo profile default` to select it explicitly.
574
+ The names `list` and `reset` are reserved for these commands and cannot be used as profile names.
575
+ `--profile NAME` overrides this choice for one command. Named profiles use global defaults
576
+ rather than inheriting `default`. The wizard preselects the active profile.
577
+ New configurations include an empty `default` profile and the example profiles. Existing configurations are
578
+ left unchanged when opened. An empty profile does not change download behavior.
579
+
580
+ ```json
581
+ {
582
+ "output": "D:\\Videos",
583
+ "profiles": {
584
+ "default": { "quality": "1080p", "resume": true },
585
+ "music": { "audio": true, "format": "mp3", "output": "D:\\Music" },
586
+ "archive": { "quality": "1080p", "embedMetadata": true, "subLangs": "de,en" }
587
+ }
588
+ }
589
+ ```
590
+
591
+ `veo <url> --profile music` merges global defaults, then the selected profile, then explicit
592
+ CLI flags. `veo config profiles` lists profile names; `veo config path` prints the file path.
593
+ The download status shows `Profile: NAME` (or `global (no profile)` when none is configured).
594
+ `veo config edit` creates a small, valid configuration for new or empty files. Opening an
595
+ existing configuration does not add or update comments. The current option guide is kept in
596
+ the program; `veo config guide` prints it without changing the file. In the built-in editor,
597
+ F3 offers **A** to add or update the guide at the top and **R** to remove a previously
598
+ generated guide. Ctrl+S saves that choice. This can also clean up the long duplicate block
599
+ inserted by older versions. Personal settings and notes below a generated block are preserved.
600
+ In an interactive terminal, it opens the built-in editor
601
+ unless `VISUAL` or `EDITOR` is configured (an executable path, without shell arguments).
602
+ The editor provides syntax colors, line numbers and live validation. Syntax errors mark
603
+ the affected line; unknown properties and invalid values are highlighted directly, with
604
+ spelling suggestions, expected types and allowed values in the status area. Validation uses
605
+ the same rules as the CLI, including profile overrides and audio/video formats.
606
+ Press F2 on a property or value to open suggestions, use Up/Down to choose, Enter to apply
607
+ or Esc to cancel. Suggestions require valid JSON syntax; free-text values and custom numeric
608
+ resolutions such as `900p` remain supported. Ctrl+S validates and saves;
609
+ Ctrl+Z undoes edits, Ctrl+Y redoes them, and Ctrl+A selects the whole file.
610
+ Esc or Ctrl+Q exits, asking before discarding changes. Use arrows, Home/End and PageUp/PageDown
611
+ to navigate. Invalid configurations cannot be saved. `--no-color` disables syntax colors.
612
+ In terminals supporting SGR mouse reporting, left-click positions the cursor and dragging
613
+ selects text, including across lines. Holding the pointer at the top or bottom while dragging
614
+ scrolls the file and extends the selection. Backspace/Delete removes the selection; typing replaces
615
+ it. The mouse wheel scrolls without changing the editing position; Ctrl+Up/Down also scrolls.
616
+ Ctrl+C copies selected text to the system clipboard, and Ctrl+V pastes clipboard text at the
617
+ cursor or replaces the selection. Ctrl+Q or Esc exits the editor. The editor disables mouse
618
+ reporting again when it exits. Keyboard editing remains available in terminals without mouse
619
+ support.
620
+ Clipboard access uses the system clipboard on Windows, `pbcopy`/`pbpaste` on macOS, and
621
+ `wl-clipboard` (Wayland), `xclip` or `xsel` (X11) on Linux. Linux requires one of those tools
622
+ and access to a graphical session. The editor reports when no clipboard is available; terminal
623
+ paste shortcuts can still insert text directly when supported by the terminal.
624
+ On Termux, editor clipboard shortcuts use `termux-clipboard-set`/`termux-clipboard-get`
625
+ when the Termux:API app and `termux-api` package are installed.
626
+
627
+ Use `veo config edit --external` to use `VISUAL`, then `EDITOR`, then Notepad on Windows
628
+ or `vi` elsewhere. `veo config edit --terminal` explicitly selects the built-in editor.
629
+ Set `VEO_CONFIG_EDITOR=external` to disable the built-in editor by default (`auto` restores
630
+ automatic selection, `terminal` forces it). For a persistent Windows preference:
631
+
632
+ ```powershell
633
+ [Environment]::SetEnvironmentVariable('VEO_CONFIG_EDITOR', 'external', 'User')
634
+ $env:VEO_CONFIG_EDITOR = 'external'
635
+ ```
636
+
637
+ Non-interactive sessions use the external editor unless `--terminal` is explicitly selected,
638
+ in which case an interactive-terminal error is reported. The wizard also offers configured profiles.
639
+ Generated guides and app messages are in English. Existing comments, profile names and
640
+ settings are preserved when opening the editor.
641
+
642
+ ### Terminal output
643
+
644
+ - During a download, the terminal/tab title shows `veo | 50% | My Video` (original title or
645
+ your `-r` name), plus setup/processing status. It ends with `Done`, `Failed`, or
646
+ `Cancelled`; the shell may replace it at the next prompt. This uses the native console
647
+ title on Windows (including PowerShell/Windows Terminal) and OSC title sequences on
648
+ compatible Linux/macOS terminals. Titles are not changed when stderr is redirected or
649
+ `TERM=dumb`. Terminal settings that enforce a fixed tab title can override this feature.
650
+ - Progress shows percentage, speed, downloaded/total size and ETA on stderr; unknown values
651
+ appear as `?`. Separate audio/video streams each have their own progress. Non-interactive
652
+ output is throttled.
653
+ - Progress labels include the current item (`[3/12]`), source title, video/audio/media stream,
654
+ postprocessing (merging, conversion, subtitles or metadata) and saving. Stream percentages
655
+ describe that stream, not the entire multi-step job. Sources without codec metadata use
656
+ the neutral `Media` label.
657
+ - `--open` launches the completed file in your default app, including renamed files and
658
+ audio. With several URLs, the primary file of each URL is opened. Uses `explorer.exe` on
659
+ Windows, `open` on macOS, and `xdg-open` on Linux (requires a graphical desktop,
660
+ xdg-utils and a file association). The CLI does not wait for the player to close. If the
661
+ opener cannot be launched, a warning is printed and the successful download still exits
662
+ with `0`; later errors inside the detached opener/player are not monitored.
663
+ - After each successful command, veo checks the npm registry at most once per day for a
664
+ newer version and prints a one-line notice on stderr (`Update available: veo X
665
+ (you have Y). Run: veo up`). Help, version and the update commands themselves
666
+ stay quiet. Disable it with `VEO_NO_UPDATE_CHECK=1`;
667
+ `VEO_REGISTRY`/`npm_config_registry` are respected.
668
+ - Exit status is `0` on success, `1` on errors, and `130` on cancellation. With several
669
+ URLs, `1` means at least one URL failed.
670
+
671
+ ## Commands
672
+
673
+ ```bash
674
+ veo version # installed version (also: veo --version or veo -v)
675
+ veo # interactive download wizard (terminal only)
676
+ veo config edit # create/open config; F3 offers the optional guide
677
+ veo config guide # print the current option guide
678
+ veo config reset # confirm with y; back up config and restore the current template
679
+ veo config profiles # list available profiles
680
+ veo profile # show the currently selected profile
681
+ veo profile list # list all profiles and mark the active one
682
+ veo profile music # use music as the default for future commands
683
+ veo profile reset # return to default (or global settings if absent)
684
+ veo config path # show the config file location
685
+ veo doctor # diagnose the local setup; exit 1 if a check fails
686
+ veo doctor fix # restore missing or damaged managed tools
687
+ veo doctor --offline # skip the network checks
688
+ veo stats # persistent download totals; --json for scripting
689
+ veo history # the last 5 downloads; --json for scripting
690
+ veo history --failed --limit 20 # recent failed/cancelled attempts
691
+ veo changes # released versions, newest first; the installed one is marked
692
+ veo changes 1.10.1 # notes of one release (also: --version 1.10.1)
693
+ veo changes --since 1.10.1 # everything newer than a version
694
+ veo changes --latest --json # newest release as JSON
695
+ veo retry --last # retry the newest failed or unfinished job
696
+ veo runs # active runs with their id; veo runs <id> for details
697
+ veo runs --json # machine-readable active run metadata
698
+ veo inspect FILE --check-audio --json # track metadata and audio signal
699
+ veo subs URL --json # available manual and automatic subtitles
700
+ veo inspect run ID --check-audio --json # finished run and saved media
701
+ veo stop [id] # stop one run, or every active run
702
+ veo flush # stop runs, clear temporary downloads and retry jobs
703
+ veo flush --stats # the same, and reset the statistics
704
+ veo update # install the latest veo with npm
705
+ veo update --check # only check for a newer veo
706
+ veo up # short alias for veo update (also: veo up --check)
707
+ veo upgrade # alias for veo update
708
+ veo check update # alias for veo update --check
709
+ veo backend update # install the newest yt-dlp release
710
+ veo backend reset # back to the release pinned in this veo version
711
+ veo alias list # shipped (veo, veodl) plus custom wrappers
712
+ veo alias add veo-dl # create veo-dl as a wrapper that calls veo
713
+ veo alias remove veo-dl # delete a custom wrapper
714
+ veo uninstall -p veodl # delete one alias (veo itself stays installed)
715
+ veo uninstall # plan the full removal (aliases, package, cache, config)
716
+ veo uninstall --yes # remove everything
717
+ ```
718
+
719
+ `veo update` runs `npm install -g veodl@latest` and then removes yt-dlp backend
720
+ caches from older pinned releases, keeping an explicitly installed backend release. It
721
+ never uses a shell on Linux/macOS, passes fixed arguments only, and prints the manual npm
722
+ command on any failure. The registry can be overridden with `VEO_REGISTRY` (or npm's
723
+ `npm_config_registry`) for mirrors and proxies.
724
+
725
+ ### `veo alias` and `veo uninstall`
726
+
727
+ The npm package is `veodl`; a global install ships the commands `veo`
728
+ and `veodl`. `veo alias` manages extra command names without reinstalling:
729
+
730
+ ```bash
731
+ veo alias list
732
+ veo alias add veo-dl
733
+ veo alias remove veo-dl
734
+ veo uninstall -p veodl
735
+ ```
736
+
737
+ `add` creates a small wrapper next to the `veo` command found on `PATH` that
738
+ forwards to `veo`, so it survives `veo update`. Names use 2-31 lowercase
739
+ letters, digits or hyphens. `remove` (also `veo alias rm`) deletes the wrapper
740
+ files; `veo` itself can never be removed this way. Removing a shipped name such
741
+ as `veodl` only deletes that shortcut — the next `veo update` recreates it.
742
+
743
+ `veo uninstall -p <name>` is a shortcut for `veo alias remove <name>`. Bare
744
+ `veo uninstall` removes everything: without flags it only prints the plan
745
+ (package `veodl` with commands veo, veodl, plus custom wrappers, cache
746
+ and config paths); `veo uninstall --yes` deletes all of it — custom wrappers,
747
+ the package with `npm uninstall -g veodl`, veo's cache directory (downloads,
748
+ jobs, backend tools, history, statistics) and the config file. Use
749
+ `--keep-aliases`, `--keep-cache` or `--keep-config` to preserve one part.
750
+ The plan shows the active config path, including a `VEO_CONFIG` override.
751
+ Add `--json` for scripting.
752
+
753
+ ### `veo flush`
754
+
755
+ Run `veo flush` to stop active veo runs started with this version, then remove
756
+ temporary local downloads, including the 15-minute retained files and unfinished
757
+ resume data, and cached retry job JSON files. Those jobs can no longer be retried.
758
+ Saved media, output history, config/profiles, the `veo history` list, finished-run
759
+ records and backend binaries are kept.
760
+ Cleanup waits for cancellation; if a run cannot stop, it fails without deleting
761
+ download or job files. Locked folders from older or interrupted processes are
762
+ skipped and reported. Only veo's own per-user cache is cleaned. Statistics are
763
+ preserved unless `--stats` is given: `veo flush --stats` resets them too.
764
+ To end runs without removing their downloads and jobs, use `veo stop [id]` instead.
765
+
766
+ ### `veo stats`
767
+
768
+ Shows saved videos/audio, failed attempts, skips, cancellations and total time spent
769
+ on download requests (including preparation, processing and saving). Parallel run
770
+ times are added together. Playlist entries count individually; retries are new
771
+ attempts. Active requests are recorded when they finish. Tracking starts with this
772
+ version; previous downloads are not imported. `veo stats --json` returns the totals
773
+ as JSON. Statistics contain counters and timestamps, not URLs or filenames.
774
+
775
+ ### `veo history`
776
+
777
+ Shows the **last 5 download attempts** by default, newest first, with title, status, media type,
778
+ date, duration, URL and the saved files. Saved, skipped, failed and cancelled items are
779
+ recorded, including the reason a failure was reported; playlist entries and retried
780
+ attempts count individually. Active downloads appear once they finish. Long file lists
781
+ are summarized in the text view.
782
+
783
+ Use `veo history --limit N` to show 1-1000 attempts, or `veo history --failed` to
784
+ show only failed and cancelled attempts; the flags can be combined. Failed attempts
785
+ include their exact retry-job command when one was recorded. New attempts also show
786
+ their persistent run ID for `veo inspect run <id>`. `veo history --json`
787
+ prints `{"count":N,"entries":[…]}` for scripting. Each entry has
788
+ `at`, `url`, `title`, `status`, `media` (`video`/`audio`), `quality`, `format`, the
789
+ complete `files` list, `error`, `elapsedMs` and an optional `job` path. Titles and paths are stored without
790
+ terminal control characters.
791
+
792
+ History is one small JSON file per attempt in the per-user veo cache's `history`
793
+ directory, so parallel runs cannot overwrite each other and each record stays small. The
794
+ directory keeps one file per attempt and contains URLs, titles and local file paths;
795
+ delete it to remove those records. `veo flush` keeps the list (only `veo flush --stats`
796
+ resets statistics, not history).
797
+
798
+ ### `veo changes`
799
+
800
+ Lists every **released version**, newest first, and marks the installed one. `veo changelog`
801
+ is an alias. The notes are parsed from the `CHANGELOG.md` that ships inside the package, so
802
+ they always belong to the installed code and the command needs no network access.
803
+
804
+ ```bash
805
+ veo changes # all versions with their Added/Changed/Fixed counts
806
+ veo changes 1.10.1 # the notes of one release
807
+ veo changes --version 1.10.1 # the same, spelled out
808
+ veo changes 1.10 # newest release starting with 1.10
809
+ veo changes --latest # only the newest release
810
+ veo changes --since 1.10.0 # everything newer than 1.10.0, up to the newest release
811
+ veo changes --range 1.9.0..1.11.0 # a closed range, newest first
812
+ veo changes --json # machine-readable
813
+ ```
814
+
815
+ An unknown version fails with the known versions listed. Long entries wrap to the terminal
816
+ width, and `--json` returns the complete text. Because the list already names the released
817
+ versions, `veo changes` prints no automatic update hint.
818
+
819
+ `veo changes --json` prints `{"current":…,"count":N,"versions":[…]}`, where each entry has
820
+ `version`, `date` (currently `null`, since the changelog headings carry no date), `installed`,
821
+ `counts` and `summary`. One version adds `"version"`, `"installed"` and `"sections"`; a range
822
+ adds `"from"` and `"to"` and keeps the per-release `sections`. Sections are
823
+ `{"type":"Added","items":[{"text":"…","items":[…]}]}`, and nested items are preserved.
824
+
825
+ ### `veo runs` and `veo stop`
826
+
827
+ Every run registers itself while it works, under a **6-character id**, and removes that
828
+ record when it ends:
829
+
830
+ ```bash
831
+ veo runs # active runs: id, PID, state, start time, progress, output directory
832
+ veo runs k3f9qa # one run in detail: URLs, settings, job file, per-item state
833
+ veo stop k3f9qa # ask that run to stop and wait until it exits
834
+ veo stop # stop every active run
835
+ ```
836
+
837
+ `veo runs` lists the runs of this user, including those started in another terminal, and a
838
+ run disappears from the list as soon as it finishes. Progress such as
839
+ `1/3 done, 1 running` is read from the run's job file, which is created before the first
840
+ download starts, so a run that is still preparing the backend shows `starting`. `veo stop
841
+ <id>` writes a stop request that the run itself polls, so it needs no signals or PIDs and
842
+ works the same on every platform. A stopped run exits like Ctrl+C (`130`) and keeps its
843
+ partial data and retry job, so the printed `veo --retry-failed` command still works —
844
+ unlike `veo flush`, which also removes that data. If a run does not stop within 15 seconds,
845
+ `veo stop` reports it and exits with `1`.
846
+
847
+ A run that crashed without cleaning up leaves its record behind: `veo runs` marks it
848
+ `stale` and `veo stop` removes it, with or without its id. Records contain the id, process
849
+ id, start time, URLs, output directory and job path — never cookie files, browser sessions
850
+ or other credentials. Unknown or damaged record files are ignored instead of breaking veo,
851
+ because a single stray file in the cache must never stop later runs.
852
+
853
+ ### `veo doctor`
854
+
855
+ Prints one line per check: `ok`, `warn` or `fail`. It inspects the JavaScript runtime used by
856
+ yt-dlp, the platform, the
857
+ output directory, the backend cache, yt-dlp (including the SHA-256 of the cached binary and
858
+ the version it reports), FFmpeg/FFprobe, a system FFmpeg fallback, the config file, leftover
859
+ partial downloads, stale duplicate-detection records, the npm registry and the yt-dlp release host. It downloads no backend and
860
+ only creates its own probe files plus the backend cache directory. Exit status is `1` when
861
+ at least one check fails.
862
+
863
+ Run `veo doctor fix` to restore missing or damaged managed tools and check again.
864
+ It stages bundled FFmpeg/FFprobe and downloads verified yt-dlp when needed. Missing
865
+ desktop media tools are downloaded into veo's cache from the pinned media releases and
866
+ verified before use. On Termux it installs missing native tools with the package manager.
867
+ With `--offline`, it only uses local binaries; missing tools are reported for an online retry.
868
+ Use `-o PATH` to create and check an output directory. Config errors and invalid overrides
869
+ are reported for manual correction; PATH and config values are never rewritten.
870
+ A missing system FFmpeg is not a warning when the selected media tools work.
871
+
872
+ ## Supported sites and backend
873
+
874
+ YouTube, X/Twitter, TikTok, Vimeo, Reddit, Instagram, and [many other yt-dlp sites](https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md) are supported **when publicly accessible and technically available**. Support changes with websites, regions, rate limits, and backend versions; it is not a guarantee that every URL will work.
875
+
876
+ Official standalone yt-dlp is acquired on first download and cached outside the package directory; help, version and plain doctor never download the backend. The release is pinned and SHA-256 verified against hashes shipped with this package. The npm package runs no installation scripts; its only dependencies are the optional per-platform packages that carry the veo binary.
877
+
878
+ When no trusted local FFmpeg/FFprobe pair is available, veo downloads both tools directly from the upstream [ffmpeg-static binary releases](https://github.com/eugeneware/ffmpeg-static/releases/tag/b6.1.1). Release URLs, compressed and executable SHA-256 hashes, and byte counts are pinned in the package. The complete pair and its license and README must pass verification before either executable is started. Later uses verify the cached pair again; legacy or damaged cached binaries are not accepted merely because they are executable. Most targets use FFmpeg 6.1.1; Windows ia32 uses the last available pair, FFmpeg 6.0. Old `FFMPEG_BINARIES_URL`, `FFMPEG_BINARY_RELEASE` and `FFMPEG_BIN` variables do not change these downloads. Use `VEO_FFMPEG_PATH` to choose a trusted installed pair explicitly.
879
+
880
+ The downloaded binaries retain their separate upstream licenses. `FFmpeg.LICENSE` and `FFmpeg.README` are saved next to the cached tools, including upstream source and build information. veo itself is MIT licensed. No Python installation is needed on supported standalone platforms.
881
+
882
+ An installed Node.js, Deno or Bun (found on PATH, in that order) is passed to yt-dlp as its JavaScript runtime for YouTube; `VEO_JS_RUNTIME=deno:/path/to/deno` selects one explicitly. Without one, yt-dlp uses its own default. Local yt-dlp configuration and plugins are disabled for predictable execution. Arguments are passed without a shell.
883
+
884
+ Windows ARM64 uses an FFmpeg/FFprobe pair found on `PATH`, including the usual
885
+ WinGet, Chocolatey and `C:\ffmpeg\bin` locations, or automatically downloads x64
886
+ media tools for Windows 11's x64 emulation.
887
+
888
+ ### Android / Termux
889
+
890
+ With npm installed in Termux (`pkg install nodejs`), npm fetches the native
891
+ Android binary:
892
+
893
+ ```sh
894
+ npm install -g veodl
895
+ veo "URL"
896
+ ```
897
+
898
+ On Android, veo automatically finds `yt-dlp`, `ffmpeg` and `ffprobe` on PATH
899
+ (also checking `$PREFIX/bin`). If tools or the `yt-dlp-ejs` Python module are
900
+ missing, it runs `pkg install -y python-yt-dlp yt-dlp-ejs ffmpeg` automatically,
901
+ then checks the installed programs before continuing the download. Python is
902
+ installed as a dependency. Termux with the pacman package manager uses
903
+ `--noconfirm` instead. This needs a working Termux repository and internet access.
904
+ No root access is needed. Later runs reuse the installed tools.
905
+
906
+ The npm installation does not pull in desktop FFmpeg packages.
907
+ The system yt-dlp is maintained by Termux and is
908
+ not pinned or hash-verified by veo. Explicit `VEO_YT_DLP_PATH` and
909
+ `VEO_FFMPEG_PATH` overrides still take precedence.
910
+
911
+ `veo doctor fix` also performs this automatic setup; `veo doctor fix --offline`
912
+ never invokes a package manager. Update Termux packages with `pkg upgrade`;
913
+ `veo backend update` prints this platform's update instructions.
914
+
915
+ To save files in Android's shared Downloads folder, run `termux-setup-storage`,
916
+ grant the storage permission, then use `veo "URL" -o ~/storage/downloads`.
917
+ Keep veo and its tools in Termux's private storage.
918
+
919
+ For existing published veo 1.6.1 installations (before this automatic setup), run:
920
+
921
+ ```sh
922
+ pkg install python-yt-dlp yt-dlp-ejs ffmpeg
923
+ export VEO_YT_DLP_PATH="$(command -v yt-dlp)"
924
+ export VEO_FFMPEG_PATH="$PREFIX/bin"
925
+ veo doctor
926
+ ```
927
+
928
+ These exports apply to the current shell; add them to your shell startup file
929
+ if you need them on subsequent launches of 1.6.1.
930
+
931
+ ### Advanced overrides
932
+
933
+ - `VEO_YT_DLP_PATH`: absolute path to a trusted, current yt-dlp executable. It always takes
934
+ precedence over the managed backend, including one installed by `veo backend update`.
935
+ - `VEO_FFMPEG_PATH`: directory containing both `ffmpeg` and `ffprobe` executables (with
936
+ `.exe` on Windows).
937
+ - `VEO_CONFIG`: path to the config file.
938
+ - `VEO_JS_RUNTIME`: `name:path` of the JavaScript runtime passed to yt-dlp, for example
939
+ `deno:/usr/local/bin/deno` (`node`, `deno`, `bun` or `quickjs`).
940
+ - `VEO_BINARY`: path to a veo executable the npm launcher should start instead of the
941
+ installed platform package (for testing a local build).
942
+
943
+ Automatic setup covers Windows x64/ia32/ARM64, macOS x64/ARM64, Linux x64/ARM64,
944
+ and Android through Termux. Windows ARM64 media tools require x64 emulation.
945
+ `--open` uses `termux-open` in Termux; another Android app must be available to view the file.
946
+ Other systems should supply trusted native binaries. Internet
947
+ access, executable private storage and working OS libraries are prerequisites;
948
+ network blocks or unavailable repositories can still prevent setup. Android's
949
+ shared-storage permission must be granted by the user. Maintainers should update
950
+ the pinned yt-dlp release and hashes as websites change,
951
+ or users can install a newer release themselves.
952
+
953
+ ### Installing a newer backend
954
+
955
+ ```bash
956
+ veo backend update --check # is a newer yt-dlp release available?
957
+ veo backend update # install it
958
+ veo backend reset # forget it, use the pinned release again
959
+ ```
960
+
961
+ The pinned backend is hash-verified against values compiled into this veo version. That
962
+ guarantee cannot cover a release that did not exist when this version was built, so
963
+ `veo backend update` verifies the download against the `SHA2-256SUMS` file published with
964
+ that release over HTTPS: the trust anchor becomes HTTPS and GitHub instead of the npm
965
+ package. It is opt-in, it says so on stderr when it runs, and `veo backend reset` (plus the
966
+ next `veo update`) returns to the pinned release. An installed release is used only while it
967
+ is newer than the pinned one and its bytes still match the recorded hash; anything else
968
+ silently falls back to the pinned release.
969
+
970
+ ## Develop and publish
971
+
972
+ veo is a Rust crate (Rust 1.85+). The npm package `veodl` contains only a small launcher
973
+ (`bin/veo.js`); the binaries ship in `veodl-<platform>-<arch>` packages generated from
974
+ `Cargo.toml` by `scripts/npm-packages.mjs`.
975
+
976
+ ```bash
977
+ cargo build --release # target/release/veo
978
+ cargo test # unit tests and offline CLI tests, no network
979
+ cargo test --test smoke -- --ignored # real downloads: local HTTP server, real yt-dlp, real FFmpeg
980
+ cargo clippy --all-targets -- -D warnings
981
+ node scripts/npm-packages.mjs # sync npm package versions after a version bump
982
+ npm pack && node scripts/check-package.js
983
+ ```
984
+
985
+ The offline tests (`tests/cli.rs`) run the real binary against `tests/fake-backend`, a
986
+ small stand-in for yt-dlp, ffmpeg and ffprobe, in isolated cache and config directories.
987
+ They cover downloads, duplicate names, skip-existing, playlists and entry selection,
988
+ audio, subtitles, naming templates, dry runs and listings, incognito mode, retries by run
989
+ ID, transient-failure back-off, profiles and release notes.
990
+
991
+ The smoke test prepares the pinned tools with `veo doctor fix`, generates a two-second
992
+ synthetic video with FFmpeg, serves it on loopback and runs real yt-dlp downloads. It checks
993
+ default output, the quality cap, duplicate names, audio extraction, conversion, remuxing,
994
+ dry-run, format listing, JSON output, audio inspection, batch failures, profiles with URL
995
+ lists, retry by run ID from another directory, playlist selection, and that a finished
996
+ download whose destination was unavailable is transferred later without contacting the
997
+ source again. It downloads no third-party video.
998
+
999
+ Continuous integration runs formatting, Clippy, the unit and offline tests, and the smoke
1000
+ test on Windows, macOS and Linux, and verifies the npm package. Pushing a tag `vX.Y.Z`
1001
+ (matching `Cargo.toml`) builds the binaries for Windows x64/ARM64, macOS x64/ARM64,
1002
+ static Linux x64/ARM64 (musl) and Android ARM64 and uploads the eight npm tarballs
1003
+ as an Actions artifact. The `rust-port` branch also triggers this build.
1004
+
1005
+ GitHub builds all seven native targets and bundles them into the single veodl npm
1006
+ package. Branch pushes and manual workflow runs build and upload the npm-package
1007
+ artifact without publishing. Pushing a matching version tag (for example,
1008
+ v2.0.1) additionally publishes the verified tarball using npm trusted publishing
1009
+ with OIDC. Configure Mailo037/veodl and release.yml as the npm trusted publisher,
1010
+ with direct npm publishing allowed; no npm token or GitHub environment is needed.
1011
+ The publish job alone has id-token: write permission and uses Node 24 and npm 11.
1012
+
1013
+ After committing and pushing the intended release changes, create and push the tag:
1014
+
1015
+ ```sh
1016
+ git tag -a v2.0.1 -m "Release v2.0.1"
1017
+ git push origin v2.0.1
1018
+ ```
1019
+
1020
+ Wait for the complete workflow, including the publish job, and check npm's latest
1021
+ tag before announcing the release. An already published version cannot be reused.
1022
+ For a local fallback, download the npm-package artifact and publish its verified
1023
+ tarball with npm publish; users always install with npm install -g veodl.
1024
+
1025
+ Modules mirror the commands and stay small (larger ones are directories of focused
1026
+ submodules): argument parsing and dispatch (`src/cli/`), configuration (`src/config*.rs`,
1027
+ `src/config_editor/`), backend setup and updates (`src/backend/`, `src/tool_setup.rs`,
1028
+ `src/backend_update.rs`), downloads (`src/downloader/`, `src/download_cache.rs`,
1029
+ `src/jobs/`, `src/playlist.rs`), records (`src/runs/`, `src/history.rs`, `src/stats.rs`,
1030
+ `src/run_archive.rs`, `src/flush.rs`), diagnostics (`src/doctor.rs`,
1031
+ `src/inspect_media.rs`), source discovery over the Chrome DevTools Protocol
1032
+ (`src/source_discovery/`), terminal output (`src/output.rs`, `src/progress/`,
1033
+ `src/terminal.rs`) and helpers.
1034
+
1035
+ ## License
1036
+
1037
+ MIT for this CLI. yt-dlp and FFmpeg/FFprobe retain their respective upstream licenses.
1038
+ # Clickable paths
1039
+
1040
+ In supported terminals (Windows Terminal, iTerm2, WezTerm, Kitty, VS Code and recent VTE terminals), the `Saved:` path, `veo config path` and the config editor's header link to the containing folder. Use the terminal's link gesture, usually Ctrl+click or Cmd+click. The terminal and operating system decide whether file links open the file manager; unsupported terminals and redirected output show plain paths. Missing paths are not linked. JSON output remains plain.