subx-cli 1.7.4

AI subtitle processing CLI tool, which automatically matches, renames, and converts subtitle files.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
# Command Reference

SubX-CLI provides eight subcommands. The `match`, `convert`, `sync`,
`detect-encoding`, and `translate` commands accept `-i <PATH>` for specifying
multiple input sources and `--recursive` for subdirectory scanning. Positional
path arguments and `-i` inputs are combined (except in `detect-encoding`,
where they are mutually exclusive). The `config`, `cache`, and
`generate-completion` commands have their own argument structures.

## Release Targets

`scripts/install.sh` auto-detects the host operating system, CPU
architecture, and (on Linux) libc, then downloads the matching asset
from the latest GitHub Release. The supported binaries are:

| Platform | Architecture | libc | Asset name                  |
|----------|--------------|------|-----------------------------|
| Linux    | x86_64       | gnu  | `subx-linux-x86_64`         |
| Linux    | aarch64      | gnu  | `subx-linux-aarch64`        |
| Linux    | x86_64       | musl | `subx-linux-x86_64-musl`    |
| Linux    | aarch64      | musl | `subx-linux-aarch64-musl`   |
| macOS    | x86_64       || `subx-macos-x86_64`         |
| macOS    | aarch64      || `subx-macos-aarch64`        |
| Windows  | x86_64       || `subx-windows-x86_64.exe`   |

The installer defaults to the gnu artifact on Linux. Opt into the musl
build via the `SUBX_LIBC=musl` environment variable or the `--musl`
flag; hosts whose `ldd --version` output identifies musl are also
auto-detected on a best-effort basis. An explicit env var or flag
always wins over auto-detection.

```bash
# Default (auto-detect)
curl -fsSL https://raw.githubusercontent.com/jim60105/subx-cli/master/scripts/install.sh | bash

# Force the musl artifact (Alpine, distroless, minimal containers).
# `SUBX_LIBC=musl` must be set on the same command as `bash` — using it
# before `curl ... | bash` would scope the variable to `curl` only.
curl -fsSL https://raw.githubusercontent.com/jim60105/subx-cli/master/scripts/install.sh -o install.sh
SUBX_LIBC=musl bash install.sh
```

### Machine-readable output

Every covered subcommand below additionally supports a stable JSON
output mode for scripting. Pass `--output json` **before** the
subcommand token, or set `SUBX_OUTPUT=json` in the environment:

```bash
subx-cli --output json match ./media
SUBX_OUTPUT=json subx-cli convert --format vtt ./subs/
```

In JSON mode, stdout receives exactly one JSON envelope; progress bars
and status symbols are suppressed. See
[Machine-Readable Output](machine-readable-output.md) for the full
contract, schema-version policy, and per-command payload schemas.

### Archive Input Support

The `match`, `convert`, `sync`, `detect-encoding`, and `translate` commands
accept archive files as direct inputs (positional path or via `-i`).
Supported formats:

| Format | Extension(s) | Notes |
|---|---|---|
| ZIP | `.zip` | Always available (pure Rust) |
| 7-Zip | `.7z` | Always available (pure Rust via `sevenz-rust`) |
| Tar-Gzip | `.tar.gz`, `.tgz` | Always available (pure Rust via `tar` + `flate2`) |
| RAR | `.rar` | Requires the `archive-rar` feature flag (links native `libunrar`) |

Archives are transparently extracted to a temporary directory for the
duration of the command, and the extracted files are processed as if they
had been supplied directly. Temporary directories are cleaned up
automatically when the command finishes.

Extraction is governed by safety limits to prevent decompression bombs
and path-traversal attacks: a maximum total expanded size of 1 GiB and a
maximum of 10,000 entries per archive. Symlink, hardlink, and
path-traversal entries are skipped with a warning.

Only archives passed *directly* as inputs are extracted. Archives
discovered during recursive directory traversal are **not** extracted —
they are treated as ordinary files and filtered by the command's
extension list. To opt out of automatic extraction entirely, pass
`--no-extract`; the archive is then treated as a regular file and is
subject to the same extension filter as any other input.

## `match` — AI Subtitle Matching

Scans input paths for video and subtitle files, uses AI to determine which
subtitles belong to which videos, and renames (or copies/moves) the
subtitles to match the video filenames.

```
subx-cli match [OPTIONS] [PATH]
```

### Options

| Flag | Description |
|------|-------------|
| `[PATH]` | Target folder or file |
| `-i`, `--input <PATH>` | Additional input path (repeatable) |
| `--dry-run` | Preview matches without executing |
| `-r`, `--recursive` | Recurse into subdirectories |
| `--confidence <N>` | Minimum confidence threshold, 0–100 (default: 80) |
| `--backup` | Back up original files before renaming |
| `--copy`, `-c` | Copy matched subtitles into the video's directory |
| `--move`, `-m` | Move matched subtitles into the video's directory |
| `--no-extract` | Skip automatic extraction of archive files (`.zip`, `.7z`, `.tar.gz`, `.tgz`, `.rar`). When set, archive files are treated as regular files and subject to the normal extension filter. |

`--copy` and `--move` are mutually exclusive. They only take effect when
the subtitle and video reside in different directories. When a filename
collision occurs at the target location, SubX compares file content and
either skips the duplicate or appends a numeric suffix (e.g.,
`movie.1.srt`).

### Examples

```bash
# Process a single folder
subx-cli match /path/to/media/

# Preview first, then execute
subx-cli match --dry-run --copy /path/to/media/
subx-cli match --copy /path/to/media/

# Multiple inputs with recursive scanning
subx-cli match -i /media/movies -i /media/tv_shows --recursive --copy

# Mix files and directories with backup
subx-cli match -i ./video1.mp4 -i ./subtitles_dir --recursive --copy --backup

# Move subtitles instead of copying (removes originals)
subx-cli match --recursive --move --backup /media/collection/
```

### File Organization Scenarios

**Scenario: Videos and subtitles in separate trees**

```
Before:
media/
├── movies/
│   ├── Action/
│   │   └── The.Matrix.1999.1080p.BluRay.mkv
│   └── Drama/
│       └── Forrest.Gump.1994.720p.WEB-DL.mp4
└── subtitles/
    ├── english/
    │   ├── Matrix_EN_Sub.srt
    │   └── ForrestGump_English.srt
    └── chinese/
        ├── 駭客任務_中文字幕.srt
        └── 阿甘正傳.繁中.srt
```

After `subx-cli match --copy --recursive media/`:

```
media/
├── movies/
│   ├── Action/
│   │   ├── The.Matrix.1999.1080p.BluRay.mkv
│   │   ├── The.Matrix.1999.1080p.BluRay.srt        ← AI matched Matrix_EN_Sub.srt
│   │   └── The.Matrix.1999.1080p.BluRay.zh.srt     ← AI matched 駭客任務_中文字幕.srt
│   └── Drama/
│       ├── Forrest.Gump.1994.720p.WEB-DL.mp4
│       ├── Forrest.Gump.1994.720p.WEB-DL.srt        ← AI matched ForrestGump_English.srt
│       └── Forrest.Gump.1994.720p.WEB-DL.zh.srt     ← AI matched 阿甘正傳.繁中.srt
└── subtitles/                                         (originals preserved with --copy)
    ├── english/
    │   ├── Matrix_EN_Sub.srt
    │   └── ForrestGump_English.srt
    └── chinese/
        ├── 駭客任務_中文字幕.srt
        └── 阿甘正傳.繁中.srt
```

With `--move` instead of `--copy`, the original subtitle files are removed
after relocation.

### JSON output

```bash
subx-cli --output json match --dry-run ./media
```

Emits the standard envelope with `command: "match"` and a `data` payload
shaped as:

```json
{
  "data": {
    "dry_run": false,
    "confidence_threshold": 80,
    "candidates": [{ "video": "...", "subtitle": "...", "confidence": 92, "accepted": true }],
    "operations": [{ "kind": "rename", "source": "...", "target": "...", "applied": true, "status": "ok" }],
    "summary": { "total_candidates": 1, "accepted": 1, "applied": 1, "skipped": 0, "failed": 0 }
  }
}
```

Each `operations[i]` carries its own `status` (`"ok"` or `"error"`) plus
an optional `error { code, category, message }`; per-item failures keep
the top-level `status == "ok"`. See
[Machine-Readable Output](machine-readable-output.md) for the full
schema.

## `convert` — Format Conversion

Converts subtitle files between SRT, ASS, VTT, and SUB formats. Supports
single-file and batch operations.

```
subx-cli convert [OPTIONS] [INPUT]
```

### Options

| Flag | Description |
|------|-------------|
| `[INPUT]` | Input file or directory |
| `-i`, `--input <PATH>` | Additional input path (repeatable) |
| `--format <FMT>` | Target format: `srt`, `ass`, `vtt`, `sub` (optional; defaults to `formats.default_output` config) |
| `-o`, `--output <FILE>` | Output filename (single-file mode) |
| `--keep-original` | Keep the source file after conversion |
| `--encoding <ENC>` | Character encoding (default: `utf-8`) |
| `-r`, `--recursive` | Recurse into subdirectories |
| `--no-extract` | Skip automatic extraction of archive files (`.zip`, `.7z`, `.tar.gz`, `.tgz`, `.rar`). When set, archive files are treated as regular files and subject to the normal extension filter. |

### Examples

```bash
# Single file
subx-cli convert subtitle.ass --format srt

# Batch conversion
subx-cli convert --format srt /path/to/subtitles/

# Multiple directories with recursive scanning, preserving originals
subx-cli convert -i ./srt_files -i ./more_subtitles --format vtt --recursive --keep-original

# Specify encoding explicitly
subx-cli convert -i movie.srt --format srt --encoding utf-8
```

### JSON output

```bash
subx-cli --output json convert --format srt ./subs/
```

Emits `command: "convert"` with `data.conversions[]`:

```json
{
  "data": {
    "conversions": [
      { "input": "...", "output": "...", "source_format": "srt",
        "target_format": "vtt", "encoding": "UTF-8", "applied": true,
        "entry_count": 412, "status": "ok" }
    ]
  }
}
```

Each conversion entry carries `status` (`"ok"` or `"error"`) plus an
optional `error { code, category, message }` for per-file isolation.
Batch invocations keep top-level `status == "ok"` whenever at least one
file converts. A single-input fatal failure produces a top-level error
envelope (`E_SUBTITLE_FORMAT`, exit code 4). Full schema in
[Machine-Readable Output](machine-readable-output.md).

## `sync` — Timeline Correction

Corrects subtitle timing by computing the offset between audio speech
segments and subtitle timestamps. The primary method uses local Voice
Activity Detection (VAD); a manual mode is available for direct offset
specification.

```
subx-cli sync [OPTIONS] [PATHS]...
```

### Options

| Flag | Description |
|------|-------------|
| `[PATH]...` | Positional video, subtitle, or directory paths |
| `-v`, `--video <VIDEO>` | Video file path |
| `-s`, `--subtitle <SUBTITLE>` | Subtitle file path |
| `-i`, `--input <PATH>` | Additional input path (repeatable) |
| `--offset <SECONDS>` | Manual offset in seconds (bypasses VAD) |
| `-b`, `--batch [DIRECTORY]` | Batch mode; optionally specify a directory path |
| `--method <M>` | Sync method: `vad` or `manual` (omit to auto-select) |
| `-w`, `--window <SECONDS>` | Analysis time window in seconds (default: 30) |
| `--vad-sensitivity <SENSITIVITY>` | VAD sensitivity 0.0–1.0 (overrides config) |
| `-o`, `--output <PATH>` | Output file path |
| `-r`, `--recursive` | Recurse into subdirectories |
| `--dry-run` | Preview sync results without writing |
| `--verbose` | Show detailed processing output |
| `--force` | Overwrite existing output file without confirmation |
| `--no-extract` | Skip automatic extraction of archive files (`.zip`, `.7z`, `.tar.gz`, `.tgz`, `.rar`). When set, archive files are treated as regular files and subject to the normal extension filter. |

Supported audio containers: MP4, MKV, WebM, OGG, WAV. SubX decodes audio
natively via Symphonia — FFmpeg is not required.

### Examples

```bash
# Automatic VAD sync (requires audio/video file + subtitle)
subx-cli sync video.mp4 subtitle.srt

# Manual offset (subtitle file only)
subx-cli sync --offset 2.5 subtitle.srt

# Custom VAD sensitivity for quiet audio
subx-cli sync --vad-sensitivity 0.8 video.mp4 subtitle.srt

# Batch processing with recursive scanning
subx-cli sync -i ./movies -i ./tv_shows --batch --recursive --method vad

# Preview batch results
subx-cli sync -i ./media --batch --recursive --dry-run --verbose
```

### JSON output

```bash
subx-cli --output json sync video.mp4 subtitle.srt
```

Single-pair invocations emit `data` as a flat object with `method`
(`"vad"` / `"manual"` / `"auto"`), `subtitle_path`, optional
`audio_path`, `offset_ms`, optional `confidence`, `applied`, `dry_run`,
optional `output_path`, and optional `vad { sensitivity, padding_ms,
segments }`.

Batch invocations emit `data: { "items": [...] }` where each entry
inlines those fields and adds per-item `status` plus an optional
`error { code, category, message }`. Whole-command failures (e.g.,
`InvalidSyncConfiguration`) produce a top-level error envelope
instead. Full schema in
[Machine-Readable Output](machine-readable-output.md).

## `detect-encoding` — Character Encoding Detection

Identifies the character encoding of subtitle files. Useful for diagnosing
garbled text before conversion.

```
subx-cli detect-encoding [OPTIONS] [FILES]...
```

### Options

| Flag | Description |
|------|-------------|
| `<FILES>...` | Target file(s) (required; mutually exclusive with `-i`) |
| `-i`, `--input <PATH>` | Input directory path (repeatable; mutually exclusive with positional files) |
| `-v`, `--verbose` | Show sample text from each file |
| `-r`, `--recursive` | Recurse into subdirectories |
| `--no-extract` | Skip automatic extraction of archive files (`.zip`, `.7z`, `.tar.gz`, `.tgz`, `.rar`). When set, archive files are treated as regular files and subject to the normal extension filter. |

Positional file arguments and `-i` cannot be used together. Use `-i` for
directory-based scanning, or positional arguments for specific files.

### Examples

```bash
# Check specific files
subx-cli detect-encoding *.srt

# Scan directories recursively with verbose output
subx-cli detect-encoding -i ./subtitles1 -i ./subtitles2 --recursive --verbose
```

### JSON output

```bash
subx-cli --output json detect-encoding *.srt
```

Emits `command: "detect-encoding"` with `data.files[]`:

```json
{
  "data": {
    "files": [
      { "path": "...", "status": "ok", "encoding": "UTF-8",
        "confidence": 1.0, "has_bom": true, "bytes_sampled": 8192 }
    ]
  }
}
```

`encoding`, `confidence`, `has_bom`, and `bytes_sampled` are omitted on
failed entries, which carry an `error { code, category, message }`. A
single-input invocation against a missing file produces the top-level
error envelope. Full schema in
[Machine-Readable Output](machine-readable-output.md).

## `translate` — AI Subtitle Translation

Translates subtitle cue text into a target language while preserving cue
timing, ordering, and the format metadata that the existing parser/writer
pipeline supports. Translation runs in two passes: a terminology extraction
pass first builds a source-to-target term map for recurring proper nouns
(people and place names), then per-cue translation batches are sent with
that map so recurring names stay consistent across the file.

The default behavior is non-destructive: translated output is written beside
the source using a target-language suffix (for example `movie.zh-TW.srt`),
and the original subtitle file is left untouched unless an explicit
overwrite or replace flag is set.

```
subx-cli translate [OPTIONS] [PATH]...
```

### Options

| Flag | Description |
|------|-------------|
| `[PATH]...` | Positional subtitle file or directory paths |
| `-i`, `--input <PATH>` | Additional input path (repeatable) |
| `-t`, `--target-language <LANG>` | **Required.** Target language code (e.g., `zh-TW`, `ja`, `en`, `fr`) |
| `-s`, `--source-language <LANG>` | Optional source language hint; omit to let the model detect |
| `--glossary <PATH>` | UTF-8 text file with terminology guidance; entries override AI-generated terms |
| `--context <TEXT>` | Inline context/tone guidance (e.g., `"Use formal business tone"`) |
| `-o`, `--output <PATH>` | Output file (single input) or output directory (multiple inputs) |
| `--overwrite` | Overwrite an existing translated output file |
| `--replace` | Replace the source subtitle with the translated content (uses `general.backup_enabled` for backup) |
| `-r`, `--recursive` | Recurse into subdirectories |
| `--no-extract` | Skip automatic extraction of archive files (`.zip`, `.7z`, `.tar.gz`, `.tgz`, `.rar`). When set, archive files are treated as regular files and subject to the normal extension filter. |

`--target-language` is required. `--overwrite` and `--replace` are mutually
exclusive — `--overwrite` only affects the translated output file, while
`--replace` rewrites the source. `--context` is always treated as inline
text and is never interpreted as a filesystem path; use `--glossary` for
file-based terminology.

### Output Naming Rules

| Input mode | Default output |
|---|---|
| Single file (no `--output`) | `<stem>.<target-language>.<ext>` next to the source |
| Single file (`--output FILE`) | Written to `FILE` |
| Multiple files / directory (no `--output`) | Each translation written next to its source with target-language suffix |
| Multiple files / directory (`--output DIR/`) | Each translation written under `DIR/` with target-language suffix |
| Multiple files (`--output FILE`) | **Rejected** — batch output requires a directory |
| Archive input (no `--output`) | Translated file written under the archive's parent directory, not the temporary extraction directory |

If the target output already exists, the file is reported as failed unless
`--overwrite` is set. Errors are isolated per input file: one failed file
does not block the remaining files in a batch.

During translation, each accepted translation response logs cue progress as
`Processed cues: <processed>/<total>` so long-running files show how much of
the subtitle has completed.

### Terminology and Context

The terminology extraction pass instructs the AI provider to:

1. Prefer established conventional translations when they exist in the
   target language.
2. When coining a new translation, prefer phonetic transliteration over
   semantic translation.

User-provided glossary entries always override AI-generated terminology.
When the AI response omits a requested cue ID, the command finishes the initial
translation pass, retries the missing cue once, and writes an empty cue text if
the retry still omits it. When a response contains an unknown cue ID, the
command treats that batch as hallucinated, discards the entire batch response,
and retries the same batch once; if the retry still contains an unknown cue ID,
the file fails without writing partial output. Malformed responses and duplicate
cue IDs also fail the file.

Translation requests use UUIDv7 cue IDs generated in cue order with at least
1 ms spacing between adjacent IDs, so each cue ID's `unix_time_ts` is
strictly greater than the previous one. The IDs are request-local and never
appear in the translated subtitle output.

### Examples

```bash
# Translate a single file to Traditional Chinese (writes movie.zh-TW.srt)
subx-cli translate movie.srt --target-language zh-TW

# Specify both source and target language with inline tone guidance
subx-cli translate movie.srt -s en -t ja --context "Use anime fansub conventions"

# Use a glossary file to lock in proper-noun translations
subx-cli translate movie.srt --target-language zh-TW --glossary ./terms.txt

# Batch translate a directory recursively into a separate output folder
subx-cli translate -i ./subs --recursive --target-language fr --output ./subs.fr/

# Translate the contents of an archive (output written under the archive's parent)
subx-cli translate archives/subs.zip --target-language en

# Replace the source file in-place with the translated content (uses backups
# when general.backup_enabled = true)
subx-cli translate movie.srt --target-language ko --replace

# Overwrite an existing translated output
subx-cli translate movie.srt --target-language zh-TW --overwrite
```

### JSON output

```bash
subx-cli --output json translate movie.srt --target-language zh-TW
```

Emits the minimum `translate` envelope:

```json
{
  "data": {
    "translated_files": [
      { "input": "movie.srt", "output": "movie.zh-TW.srt", "applied": true }
    ]
  }
}
```

A fully successful batch returns top-level `status == "ok"`; if any file
fails, the command finishes the batch and then returns a top-level
`E_COMMAND_EXECUTION` error envelope. Rich per-cue payloads are deferred
to a future schema bump. Full schema in
[Machine-Readable Output](machine-readable-output.md).

## `config` — Configuration Management

Reads and writes SubX configuration values. Settings persist to the config
file at `~/.config/subx/config.toml` (Linux/macOS) or
`%APPDATA%\subx\config.toml` (Windows).

```
subx-cli config <SUBCOMMAND>
```

| Subcommand | Description |
|------------|-------------|
| `set <KEY> <VALUE>` | Set a configuration value |
| `get <KEY>` | Get a configuration value |
| `list` | List all configuration values |
| `reset` | Reset configuration to defaults |

### Examples

```bash
subx-cli config set ai.provider openrouter
subx-cli config set ai.model "deepseek/deepseek-r1-0528:free"
subx-cli config get ai.provider
subx-cli config list
subx-cli config reset
```

For all configuration keys and environment variables, see the
[Configuration Guide](configuration-guide.md). For recovering from a
strict-invalid `config.toml` (e.g. `ai.provider = "openai"` with an
`http://` `ai.base_url`), see
[Repairing a strict-invalid configuration](configuration-guide.md#repairing-a-strict-invalid-configuration).

### JSON output

```bash
subx-cli --output json config list
subx-cli --output json config get ai.provider
subx-cli --output json config set ai.provider openrouter
```

`get`, `list`, and `reset` emit `data: { "config": <object> }` (with
sensitive values like `ai.api_key` masked). `set` emits
`data: { "key": "<key>", "value": "<masked-value>" }`. When the persisted
configuration fails strict validation, `get` and `list` additionally
populate the top-level `warnings: ["..."]` field with the underlying
validation error so callers can surface it without parsing stderr; in
text mode the same advisory is emitted as a `warning:` line on stderr.
Errors use the uniform error envelope. Full schema in
[Machine-Readable Output](machine-readable-output.md).

## `cache` — Cache Management

Manages the dry-run result cache and operation journal. SubX caches AI
analysis results so repeated `--dry-run` invocations reuse previous
matches. The journal records every file operation for rollback.

```
subx-cli cache <SUBCOMMAND>
```

| Subcommand | Description |
|------------|-------------|
| `status` | Display cache metadata (size, age, validity) |
| `apply` | Replay cached dry-run results without calling the AI |
| `rollback` | Undo the most recent batch of file operations |
| `clear` | Remove cached data (cache, journal, or both) |

### `cache status`

```
subx-cli cache status [--json]
```

| Flag | Description |
|------|-------------|
| `--json` | Output machine-readable JSON |

### `cache apply`

```
subx-cli cache apply [--yes] [--force] [--confidence <0-100>]
```

| Flag | Description |
|------|-------------|
| `--yes` | Skip interactive confirmation |
| `--force` | Bypass staleness and config hash validation |
| `--confidence <N>` | Minimum confidence threshold (0–100) |

### `cache rollback`

```
subx-cli cache rollback [--force]
```

| Flag | Description |
|------|-------------|
| `--force` | Bypass destination integrity checks |

### `cache clear`

```
subx-cli cache clear [--type <cache|journal|all>]
```

| Flag | Default | Description |
|------|---------|-------------|
| `--type` | `all` | Type of data to clear |

### JSON output

```bash
subx-cli --output json cache status
subx-cli --output json cache clear
subx-cli --output json cache rollback
subx-cli --output json cache apply
```

The `command` field is always `"cache"`; the `data` shape varies by
subcommand:

- `cache status``{ path, exists, journal_present, total, pending,
  applied, … }` (required: `total`, `pending`, `applied`).
- `cache clear``{ removed, kind, cache_path, cache_removed,
  journal_path, journal_removed }`.
- `cache rollback``{ rolled_back }`.
- `cache apply``{ applied, failed, items: [{ id, status,
  error? }] }`. Per-item failures keep the top-level
  `status == "ok"`.

The legacy `cache status --json` flag is preserved as a thin alias for
`--output json cache status` and emits byte-identical output. Full
schema in [Machine-Readable Output](machine-readable-output.md).

## `generate-completion` — Shell Completions

Generates shell completion scripts for tab-completion support.

```
subx-cli generate-completion <SHELL>
```

Supported shells: `bash`, `zsh`, `fish`, `powershell`, `elvish`.

```bash
# Bash
subx-cli generate-completion bash > ~/.local/share/bash-completion/completions/subx-cli

# Zsh
subx-cli generate-completion zsh > ~/.zfunc/_subx-cli

# Fish
subx-cli generate-completion fish > ~/.config/fish/completions/subx-cli.fish
```

### JSON output

`generate-completion` **rejects** JSON mode because its stdout is, by
design, a shell-completion script that is incompatible with the JSON
envelope contract. When invoked with `--output json` (or
`SUBX_OUTPUT=json`), it writes a top-level error envelope to stdout
with `error.code == "E_OUTPUT_MODE_UNSUPPORTED"` and
`error.category == "command_execution"`, exits with the
`SubXError::CommandExecution(_)` exit code (currently `1`), and
emits **no** completion-script bytes. See
[Machine-Readable Output](machine-readable-output.md#generate-completion-rejection).

## Workflows

### Typical Workflow

```bash
# 1. Navigate to media folder
cd ~/Downloads/TV_Show_S01/

# 2. Preview AI matching results
subx-cli match --dry-run --copy .

# 3. Execute matching and file organization
subx-cli match --copy .

# 4. Convert all subtitles to SRT
subx-cli convert --format srt .

# 5. Fix timing drift
subx-cli sync --batch .

# 6. (Optional) Translate to another language
subx-cli translate -i . --recursive --target-language zh-TW
```

### Multi-Source Workflow

```bash
# Match across multiple directories
subx-cli match -i ./Downloads/Movies -i ./Downloads/TV_Shows -i ./Backup/Subs \
    --recursive --dry-run --copy

# After reviewing dry-run output, execute
subx-cli match -i ./Downloads/Movies -i ./Downloads/TV_Shows -i ./Backup/Subs \
    --recursive --copy

# Batch convert everything to SRT
subx-cli convert -i ./Movies -i ./TV_Shows --format srt --recursive --keep-original

# Batch sync with VAD
subx-cli sync -i ./Movies -i ./TV_Shows --batch --recursive

# Check encodings
subx-cli detect-encoding -i ./Movies -i ./TV_Shows --recursive --verbose
```

## Troubleshooting

**AI matching accuracy is low.** Filenames with identifying information
(show name, season, episode) produce better results. Lower the AI
temperature for more deterministic output:
`subx-cli config set ai.temperature 0.1`

**Timeline sync produces incorrect offsets.** Verify the audio file is
accessible and in a supported container format. For quiet audio, increase
VAD sensitivity: `subx-cli config set sync.vad.sensitivity 0.8`. For noisy
audio, raise the minimum speech duration:
`subx-cli config set sync.vad.min_speech_duration_ms 200`. When automatic
detection fails, fall back to manual offset:
`subx-cli sync --offset <seconds> subtitle.srt`

**Batch processing is slow.** Increase worker count and queue size:

```bash
subx-cli config set parallel.max_workers 16
subx-cli config set parallel.task_queue_size 2000
```

**Encoding detection is wrong.** Raise the detection confidence threshold:
`subx-cli config set formats.encoding_detection_confidence 0.8`. If the
file uses a rare encoding, specify it explicitly:
`subx-cli convert --encoding big5 subtitle.srt --format srt`

**Subtitles are not copied/moved.** The `--copy` and `--move` flags only
take effect when the subtitle and video are in different directories, the AI
confidence exceeds the threshold (default 80%), and no identically named
file already exists at the target. Use `--dry-run` to preview which files
will be affected.

**`--copy` and `--move` together?** These flags are mutually exclusive. Use
`--copy` to preserve originals or `--move` to clean up after relocation.

**Cache taking too much space.** Run `subx-cli cache clear` to remove all
cached dry-run results. If new files have been added and you want fresh
matches, clear the cache before re-running `match`.

**Task execution timeouts.** Increase the timeout:
`subx-cli config set general.task_timeout_seconds 7200`