Complete Phase C (custom commands & yt-dlp updater) and Phase D (library, UX & notifications)

Phase C had been implemented in the working tree but never committed:
named custom-command templates (CRUD + per-download override), command
preview, and the in-app yt-dlp self-updater.

Phase D adds: history search/kind-filter/multi-select/bulk-delete/
re-download; native OS notifications on completion/failure; a private/
incognito download mode that skips history; settings+template backup/
restore via JSON; and a persistent error log surfaced as a Diagnostics
report in Settings.

See ROADMAP.md for the full per-item breakdown.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 06:19:45 -04:00
parent 67133f13b5
commit a822a2bb52
24 changed files with 1678 additions and 129 deletions
+36 -1
View File
@@ -1,7 +1,7 @@
import { execFile } from 'child_process'
import { existsSync } from 'fs'
import { getYtdlpPath } from './binaries'
import type { YtdlpVersionResult } from '@shared/ipc'
import type { YtdlpVersionResult, YtdlpUpdateChannel, YtdlpUpdateResult } from '@shared/ipc'
/**
* Step-1 spike: spawn the bundled yt-dlp and read back `--version`.
@@ -30,3 +30,38 @@ export function getYtdlpVersion(): Promise<YtdlpVersionResult> {
})
})
}
/**
* Self-update the bundled yt-dlp.exe via its built-in `--update-to <channel>`
* (stable releases or the nightly build). Requires write access to the file
* yt-dlp.exe lives in, which holds for both the dev resources/bin checkout and
* a per-user install; it would fail under a locked-down system install.
*/
export function updateYtdlp(channel: YtdlpUpdateChannel): Promise<YtdlpUpdateResult> {
const ytdlpPath = getYtdlpPath()
if (!existsSync(ytdlpPath)) {
return Promise.resolve({
ok: false,
error: `yt-dlp.exe not found at ${ytdlpPath}\nDownload it into resources/bin/ (see the README there).`
})
}
return new Promise((resolve) => {
execFile(
ytdlpPath,
['--update-to', channel],
{ windowsHide: true, timeout: 60_000 },
(err, stdout, stderr) => {
if (err) {
const msg = (err as { killed?: boolean }).killed
? 'Timed out updating yt-dlp.'
: (stderr || err.message).trim()
resolve({ ok: false, error: msg })
return
}
resolve({ ok: true, output: stdout.trim() })
}
)
})
}