The edit form tells users to put `~` in a field to delete its value.
report:create split the Stream ID on `@` without checking for that, so
an edit request that removes the stream ID from an existing link was
listed as invalid_channel_id, since there is no channel called `~`.
Skip the channel lookup when the stream ID is marked as deleted, as
issue:validate already does.
playlist:edit saved the playlist without the raw option, so every title
was rebuilt from the API channel name and country instead of being
written back as read. For a stream whose channel is not in the database,
that becomes "undefined (undefined)". playlist:format, playlist:update
and playlist:test already save with { public: false, raw: true }.
playlist:validate decides which streams belong to the selected files by
comparing the raw command-line arguments with the paths returned by
Storage.list(). A path such as ./streams/us.m3u never matches
streams/us.m3u, so the file's own streams are put in the duplicate buffer
and every link is reported as already being in that same file.
Resolve the arguments with getStoragePath(), as playlist:format does.
getStreamInfo() already ignores .m3u8 responses that hls-parser cannot read, but the .mpd branch let parseManifest() throw. A stream URL ending in .mpd that answers 200 with an HTML page makes mpd-parser throw DASH_INVALID_XML, which rejects inside eachLimit and kills playlist:format before anything is saved. playlist:update hits the same path when adding a stream.