subx-cli 2.0.0

AI subtitle processing CLI tool, which automatically matches, renames, and converts subtitle files.
Documentation
====================================================================================================
### Requirement: Core-Owned Input Collection


The input collection algorithm SHALL be owned by the core library, not by the argument-parsing layer. Specifically:

- `InputPathHandler` and `CollectedFiles`, together with every associated item (`from_args`, `merge_paths_from_multiple_sources`, `with_extensions`, `with_no_extract`, `validate`, `get_directories`, `collect_files`, and the private `matches_extension` / `extract_and_collect` / `scan_directory_flat` / `scan_directory_recursive` helpers; `CollectedFiles::{new, with_archives, archive_origin, into_paths}` and its `Deref` / `AsRef` impls), SHALL be defined in `src/core/input/mod.rs` and reachable as `crate::core::input::{InputPathHandler, CollectedFiles}`.
- The module SHALL NOT reference `clap`, `crate::cli`, or any other argument-parsing type. Its only permitted dependencies are the standard library, `log`, `tempfile`, `crate::core::archive`, and `crate::error`.
- `crate::cli` SHALL continue to re-export both types so that existing consumers written against `crate::cli::{InputPathHandler, CollectedFiles}` keep compiling. The re-export SHALL be documented in rustdoc as a legacy alias naming the new location; it SHALL NOT carry a `#[deprecated]` attribute, because the project forbids introducing new ones.
- No in-crate call site SHALL reach the types through the legacy alias; every `use` inside `src/` SHALL name `crate::core::input`.
- Rustdoc examples inside the module SHALL be expressible without any argument-parsing type, so that they remain compilable once the module ships in a library crate that cannot depend on the binary crate.

#### Scenario: Core module has no argument-parser coupling
- **GIVEN** the file `src/core/input/mod.rs`
- **WHEN** its imports and rustdoc examples are inspected
- **THEN** they SHALL contain no reference to `clap`, to `crate::cli`, or to any `*Args` type

#### Scenario: Legacy CLI alias still resolves
- **GIVEN** a consumer that writes `use subx_cli::cli::{CollectedFiles, InputPathHandler};`
- **WHEN** the crate is compiled
- **THEN** the import SHALL resolve to the `crate::core::input` types and SHALL produce no deprecation warning

#### Scenario: In-crate call sites use the core path
- **GIVEN** the source tree under `src/`
- **WHEN** it is searched for `cli::InputPathHandler` and `cli::CollectedFiles`
- **THEN** the only matches SHALL be the legacy re-export declarations themselves
====================================================================================================
### Requirement: Unified Path Merging


The system SHALL provide `InputPathHandler::merge_paths_from_multiple_sources(optional_paths, input_paths, string_paths)` — defined in `src/core/input/mod.rs` and reachable as `crate::core::input::InputPathHandler::merge_paths_from_multiple_sources` — so that each command can combine its positional `Option<PathBuf>`, its repeated `-i` arguments, and any additional string-path arguments into one deduplicated `Vec<PathBuf>`. The function SHALL take plain `&[Option<PathBuf>]`, `&[PathBuf]` and `&[String]` slices so that it is callable without any argument-parser type; the CLI's `*Args::get_input_handler` methods SHALL be thin adapters that extract those slices from their clap structs.

#### Scenario: Positional and `-i` paths merged
- **GIVEN** the user runs `subx match ./dirA -i ./dirB -i ./dirC`
- **WHEN** `MatchArgs::get_input_handler` resolves paths
- **THEN** the resulting handler SHALL contain `./dirA`, `./dirB`, and `./dirC`

#### Scenario: No input at all is rejected
- **GIVEN** a command that requires at least one input source and the user supplies none
- **WHEN** `merge_paths_from_multiple_sources` is called with empty inputs
- **THEN** the call SHALL return an error (for example `SubXError::NoInputSpecified`) rather than returning an empty list silently

#### Scenario: Merging is callable without a clap struct
- **GIVEN** a caller that is not a CLI command — for example a GUI front end or a unit test
- **WHEN** it calls `merge_paths_from_multiple_sources` with hand-built slices
- **THEN** the call SHALL compile and behave identically to the same call made from a `*Args::get_input_handler` adapter
====================================================================================================
### Requirement: Extension Filtering


The system SHALL provide `with_extensions(&[&str])` to restrict collected files to a whitelist of extensions, and each command SHALL apply the whitelist appropriate to its domain (for example `match` uses video + subtitle extensions, `convert` uses subtitle extensions, `detect-encoding` uses subtitle + `txt`).

#### Scenario: Non-subtitle files ignored by convert
- **GIVEN** a directory containing `movie.srt`, `movie.mp4`, and `notes.txt`, and the convert command
- **WHEN** `ConvertArgs::get_input_handler().collect_files()` runs
- **THEN** the returned list SHALL include `movie.srt` and SHALL NOT include `movie.mp4` or `notes.txt`
====================================================================================================
### Requirement: Recursive vs Flat Traversal


The system SHALL collect files from directory inputs recursively when the `--recursive` flag is passed, and non-recursively (single directory level) otherwise.

#### Scenario: Recursive traversal
- **GIVEN** a directory tree with subtitle files at multiple nesting depths and `--recursive` passed
- **WHEN** `collect_files` runs
- **THEN** subtitle files from every depth SHALL be returned

#### Scenario: Flat traversal
- **GIVEN** the same tree without `--recursive`
- **WHEN** `collect_files` runs
- **THEN** only subtitle files directly inside the specified directories SHALL be returned
====================================================================================================
### Requirement: No-Extract CLI Flag


Archive expansion SHALL be controlled by a core builder method and surfaced by a CLI flag, with the two kept distinct:

- `InputPathHandler::with_no_extract(bool)` (`src/core/input/mod.rs`) SHALL be the core-level switch. When it is set to `true`, `collect_files()` SHALL treat archive files as opaque regular files, subject to the normal extension filter.
- Each command that uses `InputPathHandler` (`match`, `convert`, `sync`, `detect-encoding`) SHALL accept a `--no-extract` boolean flag (default `false`) and SHALL forward its value to `with_no_extract` when building the handler. The flag definition remains a CLI concern; the behaviour it selects remains a core concern.

#### Scenario: --no-extract disables archive expansion
- **GIVEN** the user runs `subx match -i subs.zip --no-extract`
- **WHEN** `collect_files()` runs
- **THEN** `subs.zip` SHALL NOT be extracted and SHALL be subject to the
  normal extension filter

#### Scenario: Non-CLI caller selects the same behaviour
- **GIVEN** a caller that builds an `InputPathHandler` directly and calls `.with_no_extract(true)` without any command-line parsing
- **WHEN** `collect_files()` runs over an archive input
- **THEN** the archive SHALL be treated as a regular file, identically to the `--no-extract` invocation
====================================================================================================
### Requirement: CollectedFiles Additional APIs


`CollectedFiles` SHALL implement `into_paths() -> Vec<PathBuf>` for call
sites that consume paths by value, and `AsRef<[PathBuf]>` for slice
access. Call sites such as `DetectEncodingArgs::get_file_paths()` that
currently return `Vec<PathBuf>` SHALL be updated accordingly.

#### Scenario: into_paths consumes CollectedFiles
- **WHEN** `collected_files.into_paths()` is called
- **THEN** a `Vec<PathBuf>` SHALL be returned and the `CollectedFiles`
  SHALL be consumed (temp dirs are dropped)