pix-cli 0.1.3__tar.gz → 0.1.4__tar.gz

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 (27) hide show
  1. {pix_cli-0.1.3 → pix_cli-0.1.4}/AGENTS.md +3 -2
  2. {pix_cli-0.1.3 → pix_cli-0.1.4}/PKG-INFO +42 -26
  3. {pix_cli-0.1.3 → pix_cli-0.1.4}/README.md +41 -25
  4. {pix_cli-0.1.3 → pix_cli-0.1.4}/README.zh-CN.md +41 -25
  5. {pix_cli-0.1.3 → pix_cli-0.1.4}/docs/architecture.md +60 -14
  6. {pix_cli-0.1.3 → pix_cli-0.1.4}/docs/decisions.md +22 -6
  7. {pix_cli-0.1.3 → pix_cli-0.1.4}/docs/development.md +4 -1
  8. {pix_cli-0.1.3 → pix_cli-0.1.4}/docs/roadmap.md +16 -6
  9. {pix_cli-0.1.3 → pix_cli-0.1.4}/pyproject.toml +1 -1
  10. {pix_cli-0.1.3 → pix_cli-0.1.4}/src/pix/__init__.py +1 -1
  11. {pix_cli-0.1.3 → pix_cli-0.1.4}/src/pix/config.py +19 -0
  12. {pix_cli-0.1.3 → pix_cli-0.1.4}/src/pix/default_config.toml +6 -0
  13. {pix_cli-0.1.3 → pix_cli-0.1.4}/src/pix/logger.py +3 -0
  14. pix_cli-0.1.4/src/pix/rclone.py +162 -0
  15. {pix_cli-0.1.3 → pix_cli-0.1.4}/src/pix/sync.py +109 -29
  16. {pix_cli-0.1.3 → pix_cli-0.1.4}/tests/test_cli.py +251 -20
  17. {pix_cli-0.1.3 → pix_cli-0.1.4}/uv.lock +1 -1
  18. pix_cli-0.1.3/src/pix/rclone.py +0 -72
  19. {pix_cli-0.1.3 → pix_cli-0.1.4}/.github/workflows/ci.yml +0 -0
  20. {pix_cli-0.1.3 → pix_cli-0.1.4}/.github/workflows/publish.yml +0 -0
  21. {pix_cli-0.1.3 → pix_cli-0.1.4}/.gitignore +0 -0
  22. {pix_cli-0.1.3 → pix_cli-0.1.4}/.python-version +0 -0
  23. {pix_cli-0.1.3 → pix_cli-0.1.4}/LICENSE +0 -0
  24. {pix_cli-0.1.3 → pix_cli-0.1.4}/src/pix/cli.py +0 -0
  25. {pix_cli-0.1.3 → pix_cli-0.1.4}/src/pix/doctor.py +0 -0
  26. {pix_cli-0.1.3 → pix_cli-0.1.4}/src/pix/py.typed +0 -0
  27. {pix_cli-0.1.3 → pix_cli-0.1.4}/src/pix/shrink.py +0 -0
@@ -32,9 +32,10 @@ uv build
32
32
  - 不通过 shell 调用 `vips` 或 `rclone`。
33
33
  - `-q` 是 quality,`--quiet` 是静默。
34
34
  - 退出码 0/1/2,日志走 stderr;`mirror` / `publish` 透传 rclone 退出码。
35
- - `mirror` / `publish` 使用 `rclone bisync`;首次运行必须显式 `--resync`,不要自动加。它们是双向镜像,不是单向备份。
35
+ - `mirror` / `publish` 使用 `rclone bisync`;调用前先 `rclone mkdir` 目标并用 `rclone lsjson --max-depth 1` 判空。目标为空时自动加 `--resync`,目标非空时必须显式 `--resync`。用户不需要手动运行 rclone。它们是双向镜像,不是单向备份。
36
36
  - `--max-delete` 默认 10(删除百分比,rclone bisync 语义);`mirror` / `publish` 源为空时默认拒绝,`--allow-empty` 加 `--resync` 才放行。
37
- - 修改排除项后必须提示用户先 `--resync`;`--exclude` 是配置在前、命令行追加并去重。
37
+ - `mirror` 默认只同步 `[mirror].include` 中的图片扩展名,`include = []` 关闭白名单;`publish` 固定只同步 `.avif`。两者用 rclone filters 文件(bisync `--filters-file`、判空 `--filter-from`)并加 `--ignore-case`。
38
+ - 修改 include/exclude 后必须提示用户先 `--resync`;升级到带过滤的版本后首次运行也需要 `--resync`。`--exclude` 是配置在前、命令行追加并去重。
38
39
  - 增量状态固定在 `OUT/.pix/state.json`,每 200 个成功文件刷新并在 `finally` 落盘;`publish` 必须排除 `.pix/**`。
39
40
  - `shrink` 必须拒绝目标冲突,`--prune` 必须拒绝空源,非 dry-run 必须持有 `OUT/.pix/lock`。
40
41
  - `publish` 源必须至少有一个 `.avif`;`base_url` 必须以 `http://` 或 `https://` 开头。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pix-cli
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: Batch AVIF shrink and image-host sync CLI
5
5
  Project-URL: Homepage, https://github.com/uzvg/pix
6
6
  Project-URL: Repository, https://github.com/uzvg/pix
@@ -43,7 +43,7 @@ Description-Content-Type: text/markdown
43
43
  | `pix shrink` | Recursively compress images to AVIF, re-encoding only changed files |
44
44
  | `pix mirror` | Bidirectionally mirror an original photo directory to a cloud drive |
45
45
  | `pix publish` | Bidirectionally mirror AVIF output to an image host; optionally emit public URLs |
46
- | `pix doctor` | Check Python, libvips, rclone and configuration |
46
+ | `pix doctor` | Report Python, libvips and rclone versions, plus the config path |
47
47
  | `pix config init` | Write a commented default configuration file |
48
48
 
49
49
  `pix` is deliberately narrow:
@@ -66,7 +66,7 @@ photos/ ── mirror (rclone bisync) ──────────────
66
66
  | `shrink` | pyvips thumbnail + AVIF encode | pix |
67
67
  | `mirror` | `rclone bisync SRC REMOTE` | pix composes arguments, rclone executes |
68
68
  | `publish` | `rclone bisync OUT REMOTE`, excluding `.pix/**` | pix + rclone |
69
- | `doctor` / `config init` | Dependency checks / config scaffold | pix |
69
+ | `doctor` / `config init` | Runtime report / config scaffold | pix |
70
70
 
71
71
  **Sync is rclone.** `mirror` and `publish` only compose `rclone bisync` arguments; rclone handles authentication, transfers and conflict handling. Configure remotes with `rclone config` before using them. `pix` never reads or writes `rclone.conf`.
72
72
 
@@ -76,14 +76,23 @@ photos/ ── mirror (rclone bisync) ──────────────
76
76
 
77
77
  - Python 3.11+
78
78
  - [uv](https://docs.astral.sh/uv/)
79
- - rclone >= 1.66 for `mirror` / `publish`
79
+ - rclone >= 1.66 for `mirror` / `publish` (and for `pix doctor` to report a clean environment)
80
80
  - At least one remote already configured in rclone
81
81
 
82
82
  `pyvips[binary]` bundles libvips, so no separate vips installation is required.
83
83
 
84
84
  ## Install
85
85
 
86
- Not yet published on PyPI; install from source:
86
+ Install from PyPI:
87
+
88
+ ```bash
89
+ uv tool install pix-cli
90
+ pix --version
91
+ ```
92
+
93
+ Upgrade with `uv tool upgrade pix-cli`; remove with `uv tool uninstall pix-cli`. Linux installs `~/.local/bin/pix`; Windows installs `%USERPROFILE%\.local\bin\pix.exe`.
94
+
95
+ To work on pix itself, install the checkout in editable mode:
87
96
 
88
97
  ```bash
89
98
  git clone https://github.com/uzvg/pix.git
@@ -92,7 +101,7 @@ uv tool install --editable .
92
101
  pix --version
93
102
  ```
94
103
 
95
- Linux installs `~/.local/bin/pix`; Windows installs `%USERPROFILE%\.local\bin\pix.exe`. An editable install picks up source changes immediately. Reinstall with `uv tool install --force --editable .`, or remove it with `uv tool uninstall pix-cli`.
104
+ An editable install picks up source changes immediately. Reinstall with `uv tool install --force --editable .`.
96
105
 
97
106
  ## Configure rclone
98
107
 
@@ -118,7 +127,7 @@ If the file is missing, built-in defaults are used. Generate a commented file wi
118
127
  pix config init
119
128
  ```
120
129
 
121
- Existing files are not overwritten; pass `--force` to replace one. Use `--config PATH` before the subcommand to point at another file.
130
+ Existing files are not overwritten; pass `--force` to replace one. Use `--config PATH` before the subcommand to point at another file; an explicit path must exist, while a missing default path is fine.
122
131
 
123
132
  Common keys:
124
133
 
@@ -129,15 +138,18 @@ Common keys:
129
138
  | | `effort` | `5` | libvips effort, 0–9 |
130
139
  | | `min_size` | `0` | Skip sources smaller than this; accepts `500K`, `1M`, `2G` |
131
140
  | | `include` | `jpg`, `jpeg`, `png`, `webp` | Extensions to scan, without dots |
132
- | `[mirror]` | `exclude` | `[]` | rclone exclude globs; command-line values are appended |
141
+ | `[mirror]` | `include` | common image extensions | Extensions to mirror, without dots; `[]` disables the whitelist |
142
+ | | `exclude` | `[]` | rclone exclude globs; command-line values are appended |
133
143
  | | `max_delete` | `10` | Delete limit as a percentage; `-1` disables, `0` forbids |
134
144
  | `[publish]` | `base_url` | `""` | Public URL prefix; must start with `http://` or `https://` |
135
145
  | | `urls` | `false` | Emit a JSON URL manifest |
136
- | | `exclude` | `[]` | Same as mirror |
146
+ | | `exclude` | `[]` | Same as mirror; `.pix/**` is always excluded first |
137
147
  | | `max_delete` | `10` | Same as mirror |
138
148
  | `[rclone]` | `binary` | `rclone` | rclone executable name or path |
139
149
 
140
- Precedence is **command line > pix config > built-in defaults**. `--exclude` is appended to config excludes rather than replacing them. `-q` means quality; silence is `--quiet` (no short form).
150
+ Precedence is **command line > pix config > built-in defaults**. `--exclude` is appended to config excludes rather than replacing them. `-q` means quality; `--quiet` is errors-only; `-v` / `--verbose` adds per-file detail and rclone progress. `--no-urls` overrides `[publish].urls = true` for one run.
151
+
152
+ The default `[mirror].include` is `jpg`, `jpeg`, `png`, `webp`, `gif`, `bmp`, `tif`, `tiff`, `heic`, `heif`, `avif`, `jxl`, `svg`, `ico`, `cr2`, `cr3`, `nef`, `arw`, `dng`, `orf`, `raf`, `rw2`. Set it to `[]` to mirror every file.
141
153
 
142
154
  ## Quick start
143
155
 
@@ -154,16 +166,16 @@ Precedence is **command line > pix config > built-in defaults**. `--exclude` is
154
166
  pix shrink -s ~/Pictures -d ~/Pictures-avif -w 1920 -q 85
155
167
  ```
156
168
 
157
- The first run encodes everything; later runs skip unchanged files. State lives in `OUT/.pix/state.json`. Changing `width`, `quality` or `effort` triggers a full re-encode. `--dry-run` only prints the plan.
169
+ The first run encodes everything; later runs skip unchanged files. State lives in `OUT/.pix/state.json`. `-w` is a maximum long edge, so smaller images are not upscaled; relative directories are preserved under OUT. Change detection uses source size/mtime and output size, not content hashes. Changing `width`, `quality` or `effort` triggers a full re-encode. `--dry-run` only prints the plan. `--prune` removes only outputs recorded in state; extra files in OUT are left alone.
158
170
 
159
171
  3. Mirror originals:
160
172
 
161
173
  ```bash
162
174
  pix mirror -s ~/Pictures -d onedrive:photos --dry-run
163
- pix mirror -s ~/Pictures -d onedrive:photos --resync
175
+ pix mirror -s ~/Pictures -d onedrive:photos
164
176
  ```
165
177
 
166
- `--resync` is required on the first run. Always preview with `--dry-run` first.
178
+ The destination is created automatically. Only files matching `[mirror].include` are mirrored; other source files are ignored, and non-matching files already on the destination are left untouched. If no source file matches the filter, the source counts as empty and is refused unless you pass `--allow-empty --resync`. An empty destination initializes its own baseline, so no `--resync` is needed on the first run. Use `--resync` to rebuild the baseline when the destination already has matching content, or after changing `include`/`exclude`; `--dry-run` may create an empty destination but does not transfer or delete. After upgrading from a version without include filtering, the first run also needs `--resync`; preview with `--dry-run --resync` first (the preview does not write the baseline).
167
179
 
168
180
  4. Publish the AVIF output:
169
181
 
@@ -172,17 +184,18 @@ Precedence is **command line > pix config > built-in defaults**. `--exclude` is
172
184
  --base-url https://img.example.com --urls > urls.json
173
185
  ```
174
186
 
175
- `publish` requires at least one `.avif` in the source and always excludes `.pix/**`. The URL manifest is printed to stdout as JSON only after a successful, non-dry-run sync.
187
+ `publish` requires at least one `.avif` in the source unless you pass `--allow-empty --resync`; it syncs only `.avif` files and always excludes `.pix/**`. The URL manifest is printed to stdout as JSON only after a successful, non-dry-run sync. It lists every `.avif` under OUT, not just files changed in this run, and is not filtered by `[publish].exclude` or `--exclude`; excluded files can still appear in it. If you rely on excludes to hide files, do not publish the manifest until this behavior is fixed.
176
188
 
177
189
  5. Re-run later:
178
190
 
179
191
  ```bash
180
192
  pix shrink -s ~/Pictures -d ~/Pictures-avif
181
193
  pix mirror -s ~/Pictures -d onedrive:photos
182
- pix publish -s ~/Pictures-avif -d r2:images --urls
194
+ pix publish -s ~/Pictures-avif -d r2:images \
195
+ --base-url https://img.example.com --urls
183
196
  ```
184
197
 
185
- Only changes are encoded and transferred.
198
+ Only changes are encoded and transferred. `--base-url` is not persisted between runs; set `[publish].base_url` in config to avoid repeating it.
186
199
 
187
200
  ## Command cheat sheet
188
201
 
@@ -190,9 +203,10 @@ Precedence is **command line > pix config > built-in defaults**. `--exclude` is
190
203
  |---|---|
191
204
  | Preview compression | `pix shrink -s SRC -d OUT --dry-run` |
192
205
  | Re-encode everything | `pix shrink -s SRC -d OUT --force` |
193
- | Remove outputs whose source is gone | `pix shrink -s SRC -d OUT --prune` |
206
+ | Remove tracked outputs whose source is gone | `pix shrink -s SRC -d OUT --prune` |
194
207
  | Preview a sync | `pix mirror -s SRC -d REMOTE --dry-run` |
195
- | First run, or after changing excludes | `pix mirror -s SRC -d REMOTE --resync` |
208
+ | First sync (new or empty destination) | `pix mirror -s SRC -d REMOTE` |
209
+ | Rebuild the baseline (non-empty destination, or after changing include/exclude) | `pix mirror -s SRC -d REMOTE --resync` |
196
210
  | Emit a URL manifest | `pix publish -s OUT -d REMOTE --base-url https://img.example.com --urls` |
197
211
  | Diagnose the environment | `pix doctor` |
198
212
  | Generate a config file | `pix config init` |
@@ -202,19 +216,21 @@ Precedence is **command line > pix config > built-in defaults**. `--exclude` is
202
216
  | Situation | What happens | Protection |
203
217
  |---|---|---|
204
218
  | Deletions on either side | Propagated in both directions; accidental deletion is irreversible | Preview with `--dry-run`; default `--max-delete 10` (percentage) |
205
- | First sync | rclone has no baseline and errors | Add `--resync` explicitly |
206
- | Excludes changed | The old baseline may treat excluded files as deletions | Run with `--resync` again after changing excludes |
207
- | Empty source | `mirror` / `publish` refuse; `shrink --prune` also refuses | Use `--allow-empty` (plus `--resync` for sync) only when intentional |
219
+ | First sync (destination empty) | `pix` creates the destination and initializes the baseline | Preview with `--dry-run`; it may create an empty destination but does not transfer or delete. No manual rclone needed |
220
+ | Destination has matching content but no baseline | `pix` does not auto-resync; bisync fails instead of overwriting | Confirm which side is authoritative, then add `--resync` |
221
+ | Include/exclude filters changed | When filters are active, rclone detects the changed filters file and exits 7 | Run with `--resync` after changing `include` or `exclude`; `--dry-run --resync` previews but does not write the baseline |
222
+ | Non-image files in the source | `mirror` ignores them by default; `publish` ignores anything that is not `.avif`. If no file matches, the source counts as empty and is refused | Add extensions to `[mirror].include` if they should be mirrored; use `--allow-empty --resync` only for an intentional empty sync |
223
+ | Empty source | `mirror` / `publish` refuse. `shrink --prune` refuses only while state still has entries; empty state is a no-op | Use `--allow-empty` (plus `--resync` for sync) only when intentional |
208
224
  | `a.jpg` and `a.jpeg` both exist | Both map to the same output | Detected before encoding; exits 2 |
209
225
  | Two concurrent shrinks | They would write the same state and outputs | `OUT/.pix/lock` blocks the second process |
210
- | State lost or corrupt | Warning, then a full rebuild | Only causes re-encoding; outputs stay valid |
226
+ | State missing or corrupt | Full rebuild; corrupt state logs a warning | Only causes re-encoding; outputs stay valid |
211
227
 
212
228
  ## Output and exit codes
213
229
 
214
230
  - Logs and progress go to stderr.
215
- - stdout carries `doctor` reports, `--dry-run` plans and `--urls` JSON.
231
+ - stdout carries `doctor` reports, `shrink --dry-run` plans, `config init`'s `wrote <path>`, and `--urls` JSON. `mirror` / `publish --dry-run` output comes from rclone.
216
232
  - Exit codes: `0` success, `1` runtime failure, `2` argument or configuration error.
217
- - `mirror` / `publish` return rclone's exit code unchanged.
233
+ - `mirror` / `publish` return rclone's bisync exit code unchanged; destination preflight failures return 1.
218
234
 
219
235
  URL manifest example:
220
236
 
@@ -230,9 +246,9 @@ URL manifest example:
230
246
  |---|---|
231
247
  | `pyvips/libvips unavailable` | Reinstall to bring libvips: `uv tool install --force --editable /path/to/pix`; or install libvips manually (Linux: `apt install libvips42`, Windows: libvips release zip on `PATH`) |
232
248
  | rclone is older than 1.66 | Upgrade rclone |
233
- | bisync reports a missing baseline | Add `--resync` on the first run |
249
+ | bisync reports a missing baseline or changed filters file | The destination has matching content, or the include/exclude rules changed (including the first run after upgrading). Confirm local should win, then add `--resync`; an empty destination initializes automatically. `--dry-run --resync` previews but does not write the baseline. |
234
250
  | `OUT/.pix/lock` already exists | Check for another `shrink`, then remove a stale lock |
235
- | Configuration fails to parse | Run `pix doctor` to locate the file; fix the TOML or remove the file to fall back to defaults |
251
+ | Configuration fails to parse | The error names the file and location; fix the TOML or remove the file to fall back to defaults. `pix doctor` cannot run until the config parses |
236
252
  | `publish` says the source has no `.avif` | Run `pix shrink` first |
237
253
  | URL-related error | `base_url` must start with `http://` or `https://`, and `--urls` requires it |
238
254
 
@@ -15,7 +15,7 @@
15
15
  | `pix shrink` | Recursively compress images to AVIF, re-encoding only changed files |
16
16
  | `pix mirror` | Bidirectionally mirror an original photo directory to a cloud drive |
17
17
  | `pix publish` | Bidirectionally mirror AVIF output to an image host; optionally emit public URLs |
18
- | `pix doctor` | Check Python, libvips, rclone and configuration |
18
+ | `pix doctor` | Report Python, libvips and rclone versions, plus the config path |
19
19
  | `pix config init` | Write a commented default configuration file |
20
20
 
21
21
  `pix` is deliberately narrow:
@@ -38,7 +38,7 @@ photos/ ── mirror (rclone bisync) ──────────────
38
38
  | `shrink` | pyvips thumbnail + AVIF encode | pix |
39
39
  | `mirror` | `rclone bisync SRC REMOTE` | pix composes arguments, rclone executes |
40
40
  | `publish` | `rclone bisync OUT REMOTE`, excluding `.pix/**` | pix + rclone |
41
- | `doctor` / `config init` | Dependency checks / config scaffold | pix |
41
+ | `doctor` / `config init` | Runtime report / config scaffold | pix |
42
42
 
43
43
  **Sync is rclone.** `mirror` and `publish` only compose `rclone bisync` arguments; rclone handles authentication, transfers and conflict handling. Configure remotes with `rclone config` before using them. `pix` never reads or writes `rclone.conf`.
44
44
 
@@ -48,14 +48,23 @@ photos/ ── mirror (rclone bisync) ──────────────
48
48
 
49
49
  - Python 3.11+
50
50
  - [uv](https://docs.astral.sh/uv/)
51
- - rclone >= 1.66 for `mirror` / `publish`
51
+ - rclone >= 1.66 for `mirror` / `publish` (and for `pix doctor` to report a clean environment)
52
52
  - At least one remote already configured in rclone
53
53
 
54
54
  `pyvips[binary]` bundles libvips, so no separate vips installation is required.
55
55
 
56
56
  ## Install
57
57
 
58
- Not yet published on PyPI; install from source:
58
+ Install from PyPI:
59
+
60
+ ```bash
61
+ uv tool install pix-cli
62
+ pix --version
63
+ ```
64
+
65
+ Upgrade with `uv tool upgrade pix-cli`; remove with `uv tool uninstall pix-cli`. Linux installs `~/.local/bin/pix`; Windows installs `%USERPROFILE%\.local\bin\pix.exe`.
66
+
67
+ To work on pix itself, install the checkout in editable mode:
59
68
 
60
69
  ```bash
61
70
  git clone https://github.com/uzvg/pix.git
@@ -64,7 +73,7 @@ uv tool install --editable .
64
73
  pix --version
65
74
  ```
66
75
 
67
- Linux installs `~/.local/bin/pix`; Windows installs `%USERPROFILE%\.local\bin\pix.exe`. An editable install picks up source changes immediately. Reinstall with `uv tool install --force --editable .`, or remove it with `uv tool uninstall pix-cli`.
76
+ An editable install picks up source changes immediately. Reinstall with `uv tool install --force --editable .`.
68
77
 
69
78
  ## Configure rclone
70
79
 
@@ -90,7 +99,7 @@ If the file is missing, built-in defaults are used. Generate a commented file wi
90
99
  pix config init
91
100
  ```
92
101
 
93
- Existing files are not overwritten; pass `--force` to replace one. Use `--config PATH` before the subcommand to point at another file.
102
+ Existing files are not overwritten; pass `--force` to replace one. Use `--config PATH` before the subcommand to point at another file; an explicit path must exist, while a missing default path is fine.
94
103
 
95
104
  Common keys:
96
105
 
@@ -101,15 +110,18 @@ Common keys:
101
110
  | | `effort` | `5` | libvips effort, 0–9 |
102
111
  | | `min_size` | `0` | Skip sources smaller than this; accepts `500K`, `1M`, `2G` |
103
112
  | | `include` | `jpg`, `jpeg`, `png`, `webp` | Extensions to scan, without dots |
104
- | `[mirror]` | `exclude` | `[]` | rclone exclude globs; command-line values are appended |
113
+ | `[mirror]` | `include` | common image extensions | Extensions to mirror, without dots; `[]` disables the whitelist |
114
+ | | `exclude` | `[]` | rclone exclude globs; command-line values are appended |
105
115
  | | `max_delete` | `10` | Delete limit as a percentage; `-1` disables, `0` forbids |
106
116
  | `[publish]` | `base_url` | `""` | Public URL prefix; must start with `http://` or `https://` |
107
117
  | | `urls` | `false` | Emit a JSON URL manifest |
108
- | | `exclude` | `[]` | Same as mirror |
118
+ | | `exclude` | `[]` | Same as mirror; `.pix/**` is always excluded first |
109
119
  | | `max_delete` | `10` | Same as mirror |
110
120
  | `[rclone]` | `binary` | `rclone` | rclone executable name or path |
111
121
 
112
- Precedence is **command line > pix config > built-in defaults**. `--exclude` is appended to config excludes rather than replacing them. `-q` means quality; silence is `--quiet` (no short form).
122
+ Precedence is **command line > pix config > built-in defaults**. `--exclude` is appended to config excludes rather than replacing them. `-q` means quality; `--quiet` is errors-only; `-v` / `--verbose` adds per-file detail and rclone progress. `--no-urls` overrides `[publish].urls = true` for one run.
123
+
124
+ The default `[mirror].include` is `jpg`, `jpeg`, `png`, `webp`, `gif`, `bmp`, `tif`, `tiff`, `heic`, `heif`, `avif`, `jxl`, `svg`, `ico`, `cr2`, `cr3`, `nef`, `arw`, `dng`, `orf`, `raf`, `rw2`. Set it to `[]` to mirror every file.
113
125
 
114
126
  ## Quick start
115
127
 
@@ -126,16 +138,16 @@ Precedence is **command line > pix config > built-in defaults**. `--exclude` is
126
138
  pix shrink -s ~/Pictures -d ~/Pictures-avif -w 1920 -q 85
127
139
  ```
128
140
 
129
- The first run encodes everything; later runs skip unchanged files. State lives in `OUT/.pix/state.json`. Changing `width`, `quality` or `effort` triggers a full re-encode. `--dry-run` only prints the plan.
141
+ The first run encodes everything; later runs skip unchanged files. State lives in `OUT/.pix/state.json`. `-w` is a maximum long edge, so smaller images are not upscaled; relative directories are preserved under OUT. Change detection uses source size/mtime and output size, not content hashes. Changing `width`, `quality` or `effort` triggers a full re-encode. `--dry-run` only prints the plan. `--prune` removes only outputs recorded in state; extra files in OUT are left alone.
130
142
 
131
143
  3. Mirror originals:
132
144
 
133
145
  ```bash
134
146
  pix mirror -s ~/Pictures -d onedrive:photos --dry-run
135
- pix mirror -s ~/Pictures -d onedrive:photos --resync
147
+ pix mirror -s ~/Pictures -d onedrive:photos
136
148
  ```
137
149
 
138
- `--resync` is required on the first run. Always preview with `--dry-run` first.
150
+ The destination is created automatically. Only files matching `[mirror].include` are mirrored; other source files are ignored, and non-matching files already on the destination are left untouched. If no source file matches the filter, the source counts as empty and is refused unless you pass `--allow-empty --resync`. An empty destination initializes its own baseline, so no `--resync` is needed on the first run. Use `--resync` to rebuild the baseline when the destination already has matching content, or after changing `include`/`exclude`; `--dry-run` may create an empty destination but does not transfer or delete. After upgrading from a version without include filtering, the first run also needs `--resync`; preview with `--dry-run --resync` first (the preview does not write the baseline).
139
151
 
140
152
  4. Publish the AVIF output:
141
153
 
@@ -144,17 +156,18 @@ Precedence is **command line > pix config > built-in defaults**. `--exclude` is
144
156
  --base-url https://img.example.com --urls > urls.json
145
157
  ```
146
158
 
147
- `publish` requires at least one `.avif` in the source and always excludes `.pix/**`. The URL manifest is printed to stdout as JSON only after a successful, non-dry-run sync.
159
+ `publish` requires at least one `.avif` in the source unless you pass `--allow-empty --resync`; it syncs only `.avif` files and always excludes `.pix/**`. The URL manifest is printed to stdout as JSON only after a successful, non-dry-run sync. It lists every `.avif` under OUT, not just files changed in this run, and is not filtered by `[publish].exclude` or `--exclude`; excluded files can still appear in it. If you rely on excludes to hide files, do not publish the manifest until this behavior is fixed.
148
160
 
149
161
  5. Re-run later:
150
162
 
151
163
  ```bash
152
164
  pix shrink -s ~/Pictures -d ~/Pictures-avif
153
165
  pix mirror -s ~/Pictures -d onedrive:photos
154
- pix publish -s ~/Pictures-avif -d r2:images --urls
166
+ pix publish -s ~/Pictures-avif -d r2:images \
167
+ --base-url https://img.example.com --urls
155
168
  ```
156
169
 
157
- Only changes are encoded and transferred.
170
+ Only changes are encoded and transferred. `--base-url` is not persisted between runs; set `[publish].base_url` in config to avoid repeating it.
158
171
 
159
172
  ## Command cheat sheet
160
173
 
@@ -162,9 +175,10 @@ Precedence is **command line > pix config > built-in defaults**. `--exclude` is
162
175
  |---|---|
163
176
  | Preview compression | `pix shrink -s SRC -d OUT --dry-run` |
164
177
  | Re-encode everything | `pix shrink -s SRC -d OUT --force` |
165
- | Remove outputs whose source is gone | `pix shrink -s SRC -d OUT --prune` |
178
+ | Remove tracked outputs whose source is gone | `pix shrink -s SRC -d OUT --prune` |
166
179
  | Preview a sync | `pix mirror -s SRC -d REMOTE --dry-run` |
167
- | First run, or after changing excludes | `pix mirror -s SRC -d REMOTE --resync` |
180
+ | First sync (new or empty destination) | `pix mirror -s SRC -d REMOTE` |
181
+ | Rebuild the baseline (non-empty destination, or after changing include/exclude) | `pix mirror -s SRC -d REMOTE --resync` |
168
182
  | Emit a URL manifest | `pix publish -s OUT -d REMOTE --base-url https://img.example.com --urls` |
169
183
  | Diagnose the environment | `pix doctor` |
170
184
  | Generate a config file | `pix config init` |
@@ -174,19 +188,21 @@ Precedence is **command line > pix config > built-in defaults**. `--exclude` is
174
188
  | Situation | What happens | Protection |
175
189
  |---|---|---|
176
190
  | Deletions on either side | Propagated in both directions; accidental deletion is irreversible | Preview with `--dry-run`; default `--max-delete 10` (percentage) |
177
- | First sync | rclone has no baseline and errors | Add `--resync` explicitly |
178
- | Excludes changed | The old baseline may treat excluded files as deletions | Run with `--resync` again after changing excludes |
179
- | Empty source | `mirror` / `publish` refuse; `shrink --prune` also refuses | Use `--allow-empty` (plus `--resync` for sync) only when intentional |
191
+ | First sync (destination empty) | `pix` creates the destination and initializes the baseline | Preview with `--dry-run`; it may create an empty destination but does not transfer or delete. No manual rclone needed |
192
+ | Destination has matching content but no baseline | `pix` does not auto-resync; bisync fails instead of overwriting | Confirm which side is authoritative, then add `--resync` |
193
+ | Include/exclude filters changed | When filters are active, rclone detects the changed filters file and exits 7 | Run with `--resync` after changing `include` or `exclude`; `--dry-run --resync` previews but does not write the baseline |
194
+ | Non-image files in the source | `mirror` ignores them by default; `publish` ignores anything that is not `.avif`. If no file matches, the source counts as empty and is refused | Add extensions to `[mirror].include` if they should be mirrored; use `--allow-empty --resync` only for an intentional empty sync |
195
+ | Empty source | `mirror` / `publish` refuse. `shrink --prune` refuses only while state still has entries; empty state is a no-op | Use `--allow-empty` (plus `--resync` for sync) only when intentional |
180
196
  | `a.jpg` and `a.jpeg` both exist | Both map to the same output | Detected before encoding; exits 2 |
181
197
  | Two concurrent shrinks | They would write the same state and outputs | `OUT/.pix/lock` blocks the second process |
182
- | State lost or corrupt | Warning, then a full rebuild | Only causes re-encoding; outputs stay valid |
198
+ | State missing or corrupt | Full rebuild; corrupt state logs a warning | Only causes re-encoding; outputs stay valid |
183
199
 
184
200
  ## Output and exit codes
185
201
 
186
202
  - Logs and progress go to stderr.
187
- - stdout carries `doctor` reports, `--dry-run` plans and `--urls` JSON.
203
+ - stdout carries `doctor` reports, `shrink --dry-run` plans, `config init`'s `wrote <path>`, and `--urls` JSON. `mirror` / `publish --dry-run` output comes from rclone.
188
204
  - Exit codes: `0` success, `1` runtime failure, `2` argument or configuration error.
189
- - `mirror` / `publish` return rclone's exit code unchanged.
205
+ - `mirror` / `publish` return rclone's bisync exit code unchanged; destination preflight failures return 1.
190
206
 
191
207
  URL manifest example:
192
208
 
@@ -202,9 +218,9 @@ URL manifest example:
202
218
  |---|---|
203
219
  | `pyvips/libvips unavailable` | Reinstall to bring libvips: `uv tool install --force --editable /path/to/pix`; or install libvips manually (Linux: `apt install libvips42`, Windows: libvips release zip on `PATH`) |
204
220
  | rclone is older than 1.66 | Upgrade rclone |
205
- | bisync reports a missing baseline | Add `--resync` on the first run |
221
+ | bisync reports a missing baseline or changed filters file | The destination has matching content, or the include/exclude rules changed (including the first run after upgrading). Confirm local should win, then add `--resync`; an empty destination initializes automatically. `--dry-run --resync` previews but does not write the baseline. |
206
222
  | `OUT/.pix/lock` already exists | Check for another `shrink`, then remove a stale lock |
207
- | Configuration fails to parse | Run `pix doctor` to locate the file; fix the TOML or remove the file to fall back to defaults |
223
+ | Configuration fails to parse | The error names the file and location; fix the TOML or remove the file to fall back to defaults. `pix doctor` cannot run until the config parses |
208
224
  | `publish` says the source has no `.avif` | Run `pix shrink` first |
209
225
  | URL-related error | `base_url` must start with `http://` or `https://`, and `--urls` requires it |
210
226
 
@@ -15,7 +15,7 @@
15
15
  | `pix shrink` | 递归压缩图片为 AVIF,只重新编码变化的文件 |
16
16
  | `pix mirror` | 把原图目录与云盘做双向镜像 |
17
17
  | `pix publish` | 把 AVIF 输出目录与图床做双向镜像,可选输出公开 URL |
18
- | `pix doctor` | 检查 Python、libvips、rclone 与配置 |
18
+ | `pix doctor` | 报告 Python、libvips、rclone 版本与配置文件路径 |
19
19
  | `pix config init` | 生成带注释的默认配置文件 |
20
20
 
21
21
  `pix` 刻意保持边界清晰:
@@ -38,7 +38,7 @@ photos/ ── mirror (rclone bisync) ──────────────
38
38
  | `shrink` | pyvips 缩放并编码 AVIF | pix |
39
39
  | `mirror` | `rclone bisync SRC REMOTE` | pix 拼参数,rclone 执行 |
40
40
  | `publish` | `rclone bisync OUT REMOTE`,排除 `.pix/**` | pix + rclone |
41
- | `doctor` / `config init` | 依赖检查 / 配置脚手架 | pix |
41
+ | `doctor` / `config init` | 运行时报告 / 配置脚手架 | pix |
42
42
 
43
43
  **同步由 rclone 执行。** `mirror` / `publish` 只负责拼装 `rclone bisync` 参数;认证、传输和冲突处理都由 rclone 完成。使用前请先用 `rclone config` 配好 remote。`pix` 不读取也不写入 `rclone.conf`。
44
44
 
@@ -48,14 +48,23 @@ photos/ ── mirror (rclone bisync) ──────────────
48
48
 
49
49
  - Python 3.11+
50
50
  - [uv](https://docs.astral.sh/uv/)
51
- - `mirror` / `publish` 需要 rclone >= 1.66
51
+ - `mirror` / `publish` 需要 rclone >= 1.66;`pix doctor` 也要有 rclone 才会报告环境正常
52
52
  - 已在 rclone 中配置好至少一个 remote
53
53
 
54
54
  `pyvips[binary]` 自带 libvips,不需要单独安装 vips。
55
55
 
56
56
  ## 安装
57
57
 
58
- 尚未发布到 PyPI,从源码安装:
58
+ 从 PyPI 安装:
59
+
60
+ ```bash
61
+ uv tool install pix-cli
62
+ pix --version
63
+ ```
64
+
65
+ 升级用 `uv tool upgrade pix-cli`,卸载用 `uv tool uninstall pix-cli`。Linux 安装到 `~/.local/bin/pix`,Windows 为 `%USERPROFILE%\.local\bin\pix.exe`。
66
+
67
+ 如果要开发 pix 本身,用 editable 模式安装源码:
59
68
 
60
69
  ```bash
61
70
  git clone https://github.com/uzvg/pix.git
@@ -64,7 +73,7 @@ uv tool install --editable .
64
73
  pix --version
65
74
  ```
66
75
 
67
- Linux 安装到 `~/.local/bin/pix`,Windows 为 `%USERPROFILE%\.local\bin\pix.exe`。editable 安装修改源码后立即生效。重装用 `uv tool install --force --editable .`,卸载用 `uv tool uninstall pix-cli`。
76
+ editable 安装修改源码后立即生效。重装用 `uv tool install --force --editable .`。
68
77
 
69
78
  ## 配置 rclone
70
79
 
@@ -90,7 +99,7 @@ rclone 把凭据保存在 `~/.config/rclone/rclone.conf`(Windows:`%APPDATA%\
90
99
  pix config init
91
100
  ```
92
101
 
93
- 已有文件不会被覆盖;需要替换时加 `--force`。用 `--config PATH`(写在子命令之前)指向其他文件。
102
+ 已有文件不会被覆盖;需要替换时加 `--force`。用 `--config PATH`(写在子命令之前)指向其他文件;显式路径必须存在,默认路径缺失则会使用内置默认值。
94
103
 
95
104
  常用配置项:
96
105
 
@@ -101,15 +110,18 @@ pix config init
101
110
  | | `effort` | `5` | libvips effort,0–9 |
102
111
  | | `min_size` | `0` | 小于该值的源文件跳过;支持 `500K`、`1M`、`2G` |
103
112
  | | `include` | `jpg`、`jpeg`、`png`、`webp` | 扫描扩展名,不带点 |
104
- | `[mirror]` | `exclude` | `[]` | rclone 排除 glob;命令行项会追加 |
113
+ | `[mirror]` | `include` | 常见图片扩展名 | 需要镜像的扩展名,不带点;`[]` 关闭白名单 |
114
+ | | `exclude` | `[]` | rclone 排除 glob;命令行项会追加 |
105
115
  | | `max_delete` | `10` | 删除百分比上限;`-1` 不限制,`0` 禁止删除 |
106
116
  | `[publish]` | `base_url` | `""` | 公开 URL 前缀,必须以 `http://` 或 `https://` 开头 |
107
117
  | | `urls` | `false` | 是否输出 JSON URL 清单 |
108
- | | `exclude` | `[]` | 同 `mirror` |
118
+ | | `exclude` | `[]` | 同 `mirror`;`.pix/**` 总是先被排除 |
109
119
  | | `max_delete` | `10` | 同 `mirror` |
110
120
  | `[rclone]` | `binary` | `rclone` | rclone 可执行文件名或路径 |
111
121
 
112
- 优先级为**命令行 > pix 配置 > 内置默认**。`--exclude` 是追加而非覆盖配置项。`-q` 表示 quality,静默模式用 `--quiet`(无短选项)。
122
+ 优先级为**命令行 > pix 配置 > 内置默认**。`--exclude` 是追加而非覆盖配置项。`-q` 表示 quality;`--quiet` 只输出错误;`-v` / `--verbose` 输出每文件详情和 rclone 进度。`--no-urls` 可单次覆盖 `[publish].urls = true`。
123
+
124
+ `[mirror].include` 默认为 `jpg`、`jpeg`、`png`、`webp`、`gif`、`bmp`、`tif`、`tiff`、`heic`、`heif`、`avif`、`jxl`、`svg`、`ico`、`cr2`、`cr3`、`nef`、`arw`、`dng`、`orf`、`raf`、`rw2`。设为 `[]` 可镜像所有文件。
113
125
 
114
126
  ## 快速上手
115
127
 
@@ -126,16 +138,16 @@ pix config init
126
138
  pix shrink -s ~/Pictures -d ~/Pictures-avif -w 1920 -q 85
127
139
  ```
128
140
 
129
- 首次全量编码,之后跳过未变化的文件。状态保存在 `OUT/.pix/state.json`。修改 `width`、`quality` 或 `effort` 会触发全量重编码。`--dry-run` 只打印计划。
141
+ 首次全量编码,之后跳过未变化的文件。状态保存在 `OUT/.pix/state.json`。`-w` 是长边上限,小图不会被放大;输出会保留相对目录结构。变更检测基于源文件 size/mtime 和输出 size,不做内容哈希。修改 `width`、`quality` 或 `effort` 会触发全量重编码。`--dry-run` 只打印计划。`--prune` 只删除 state 中记录的输出,OUT 里的额外文件不会被清理。
130
142
 
131
143
  3. 镜像原图:
132
144
 
133
145
  ```bash
134
146
  pix mirror -s ~/Pictures -d onedrive:photos --dry-run
135
- pix mirror -s ~/Pictures -d onedrive:photos --resync
147
+ pix mirror -s ~/Pictures -d onedrive:photos
136
148
  ```
137
149
 
138
- 首次运行必须显式加 `--resync`。建议先跑 `--dry-run` 预览。
150
+ 目标目录会自动创建。只有匹配 `[mirror].include` 的文件会被镜像;源目录里的其他文件会被忽略,目标上不匹配的文件也不会被删除。如果没有任何源文件匹配过滤规则,源会被视为空并拒绝同步,除非显式传入 `--allow-empty --resync`。目标为空时会自动初始化基线,首次运行不需要 `--resync`;目标已有匹配内容需要重建基线,或修改 `include` / `exclude` 后,才需要显式加 `--resync`。`--dry-run` 可能创建一个空目标目录,但不传输、不删除文件。从没有 include 过滤的旧版本升级后,首次运行也需要 `--resync`;建议先跑 `--dry-run --resync` 预览(预览不会写入基线)。
139
151
 
140
152
  4. 发布 AVIF 输出:
141
153
 
@@ -144,17 +156,18 @@ pix config init
144
156
  --base-url https://img.example.com --urls > urls.json
145
157
  ```
146
158
 
147
- `publish` 要求源目录至少有一个 `.avif`,并固定排除 `.pix/**`。URL 清单只在同步成功且不是 `--dry-run` 时输出到 stdout。
159
+ `publish` 要求源目录至少有一个 `.avif`,除非显式传入 `--allow-empty --resync`;只同步 `.avif` 文件,并固定排除 `.pix/**`。URL 清单只在同步成功且不是 `--dry-run` 时输出到 stdout。清单列出 OUT 下所有 `.avif`,不只是本次变化的文件,也不按 `[publish].exclude` 或 `--exclude` 过滤;被排除的文件仍可能出现在清单里。如果依赖 exclude 隐藏文件,目前不要发布清单,直到该行为修复。
148
160
 
149
161
  5. 之后重复运行:
150
162
 
151
163
  ```bash
152
164
  pix shrink -s ~/Pictures -d ~/Pictures-avif
153
165
  pix mirror -s ~/Pictures -d onedrive:photos
154
- pix publish -s ~/Pictures-avif -d r2:images --urls
166
+ pix publish -s ~/Pictures-avif -d r2:images \
167
+ --base-url https://img.example.com --urls
155
168
  ```
156
169
 
157
- 只处理变化的部分。
170
+ 只处理变化的部分。`--base-url` 不会在两次运行之间保留;不想重复传就写进配置的 `[publish].base_url`。
158
171
 
159
172
  ## 常用命令
160
173
 
@@ -162,9 +175,10 @@ pix config init
162
175
  |---|---|
163
176
  | 预览压缩 | `pix shrink -s SRC -d OUT --dry-run` |
164
177
  | 强制全部重压 | `pix shrink -s SRC -d OUT --force` |
165
- | 删除源已消失的输出 | `pix shrink -s SRC -d OUT --prune` |
178
+ | 删除 state 中记录、且源已消失的输出 | `pix shrink -s SRC -d OUT --prune` |
166
179
  | 预览同步 | `pix mirror -s SRC -d REMOTE --dry-run` |
167
- | 首次运行或修改排除项后 | `pix mirror -s SRC -d REMOTE --resync` |
180
+ | 首次同步(目标不存在或为空) | `pix mirror -s SRC -d REMOTE` |
181
+ | 重建基线(目标非空,或修改 include/exclude 后) | `pix mirror -s SRC -d REMOTE --resync` |
168
182
  | 输出 URL 清单 | `pix publish -s OUT -d REMOTE --base-url https://img.example.com --urls` |
169
183
  | 诊断环境 | `pix doctor` |
170
184
  | 生成配置文件 | `pix config init` |
@@ -174,19 +188,21 @@ pix config init
174
188
  | 情况 | 会发生什么 | 如何防护 |
175
189
  |---|---|---|
176
190
  | 任意一侧删除文件 | 删除双向传播,误删不可逆 | 先 `--dry-run`;默认 `--max-delete 10`(百分比) |
177
- | 首次同步 | rclone 没有基线,直接报错 | 显式加 `--resync` |
178
- | 修改排除项 | 旧基线可能把被排除的文件当成删除传播 | 修改后必须重新 `--resync` |
179
- | 源目录为空 | `mirror` / `publish` 默认拒绝;`shrink --prune` 同样拒绝 | 仅在确认要清空时用 `--allow-empty`(同步还需 `--resync`) |
191
+ | 首次同步(目标为空) | `pix` 自动创建目标并初始化基线 | 先 `--dry-run` 预览;它可能创建空目标目录,但不传输、不删除。不需要手动运行 rclone |
192
+ | 目标已有匹配内容但没有基线 | `pix` 不自动 resync,bisync 失败而不是覆盖 | 确认以哪边为准后显式加 `--resync` |
193
+ | 修改 include/exclude | 启用过滤时,rclone 会检测到 filters 文件变化并以 7 退出 | 修改后必须重新 `--resync`;`--dry-run --resync` 只预览,不写基线 |
194
+ | 源目录含非图片文件 | `mirror` 默认忽略;`publish` 只同步 `.avif`。如果没有任何文件匹配,源会被视为空并拒绝 | 需要镜像时把扩展名加入 `[mirror].include`;只有确认要空同步时才用 `--allow-empty --resync` |
195
+ | 源目录为空 | `mirror` / `publish` 默认拒绝;`shrink --prune` 仅在 state 仍有记录时拒绝,state 为空时是 no-op | 仅在确认要清空时用 `--allow-empty`(同步还需 `--resync`) |
180
196
  | `a.jpg` 与 `a.jpeg` 同时存在 | 映射到同一输出 | 编码前检测并以 2 退出 |
181
197
  | 同时运行两个 shrink | 会写同一份状态和输出 | `OUT/.pix/lock` 阻止第二个进程 |
182
- | 状态文件丢失或损坏 | 告警后全量重建 | 只会重新编码,不会产生错误结果 |
198
+ | 状态文件缺失或损坏 | 全量重建;损坏时会告警 | 只会重新编码,不会产生错误结果 |
183
199
 
184
200
  ## 输出与退出码
185
201
 
186
202
  - 日志和进度输出到 stderr。
187
- - stdout 用于 `doctor` 报告、`--dry-run` 计划和 `--urls` JSON。
203
+ - stdout 用于 `doctor` 报告、`shrink --dry-run` 计划、`config init` 的 `wrote <path>`,以及 `--urls` JSON;`mirror` / `publish --dry-run` 的输出由 rclone 产生。
188
204
  - 退出码:`0` 成功,`1` 运行失败,`2` 参数或配置错误。
189
- - `mirror` / `publish` 原样返回 rclone 的退出码。
205
+ - `mirror` / `publish` 原样返回 rclone bisync 的退出码;目标预检失败返回 1。
190
206
 
191
207
  URL 清单示例:
192
208
 
@@ -202,9 +218,9 @@ URL 清单示例:
202
218
  |---|---|
203
219
  | 提示 `pyvips/libvips unavailable` | 重装以带入 libvips:`uv tool install --force --editable /path/to/pix`;或手动安装 libvips(Linux:`apt install libvips42`;Windows:下载 libvips release 压缩包并把 `bin` 加入 PATH) |
204
220
  | rclone 版本低于 1.66 | 升级 rclone |
205
- | bisync 报缺少基线 | 首次运行加 `--resync` |
221
+ | bisync 报缺少基线或 filters 文件变化 | 目标已有匹配内容,或 include/exclude 规则变化(含升级后首次运行)。确认本地为准后加 `--resync`;目标是空的会自动初始化。`--dry-run --resync` 只预览,不写基线 |
206
222
  | `OUT/.pix/lock` 已存在 | 确认没有其他 `shrink` 在运行,再删除陈旧锁 |
207
- | 配置解析失败 | 运行 `pix doctor` 定位文件;修复 TOML,或删除配置改用默认值 |
223
+ | 配置解析失败 | 错误信息会指出文件和位置;修复 TOML,或删除配置改用默认值。配置解析失败时 `pix doctor` 也会在加载配置阶段退出 2 |
208
224
  | `publish` 提示源目录没有 `.avif` | 先运行 `pix shrink` |
209
225
  | URL 相关报错 | `base_url` 必须以 `http://` 或 `https://` 开头,且 `--urls` 依赖它 |
210
226