bindery-cli 0.27.0__tar.gz → 0.29.0__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 (74) hide show
  1. bindery_cli-0.29.0/.gitignore +21 -0
  2. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/CLAUDE.md +14 -11
  3. bindery_cli-0.27.0/README.md → bindery_cli-0.29.0/PKG-INFO +81 -12
  4. bindery_cli-0.27.0/PKG-INFO → bindery_cli-0.29.0/README.md +59 -28
  5. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/logo.svg +1 -1
  6. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/patchnotes.md +153 -11
  7. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/pyproject.toml +12 -5
  8. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/roadmap.md +187 -7
  9. bindery_cli-0.29.0/scripts/FastSweep.java +102 -0
  10. bindery_cli-0.29.0/scripts/fast_sweep.py +169 -0
  11. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/spec.md +64 -9
  12. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/src/bindery/__init__.py +2 -2
  13. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/src/bindery/audit.py +186 -4
  14. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/src/bindery/cli.py +454 -7
  15. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/src/bindery/epub.py +415 -2
  16. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/src/bindery/transforms.py +52 -8
  17. bindery_cli-0.29.0/testing_facility/top500candidates/REPORT.md +20 -0
  18. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/tests/test_audit.py +200 -0
  19. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/tests/test_cli.py +315 -0
  20. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/tests/test_epub.py +290 -0
  21. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/tests/test_transforms.py +46 -0
  22. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/uv.lock +15 -7
  23. bindery_cli-0.27.0/.gitignore +0 -12
  24. bindery_cli-0.27.0/scripts/FastSweep.java +0 -40
  25. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/.github/workflows/ci.yml +0 -0
  26. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/.github/workflows/publish.yml +0 -0
  27. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/AGENTS.md +0 -0
  28. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/LICENSE +0 -0
  29. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/docs/screenshots/library-sweep.png +0 -0
  30. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/run_tests.sh +0 -0
  31. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/scripts/find_block_in_inline.py +0 -0
  32. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/scripts/find_css_illegal_tags.py +0 -0
  33. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/scripts/find_duplicate_playorder.py +0 -0
  34. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/scripts/find_empty_body.py +0 -0
  35. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/scripts/find_illegal_tags.py +0 -0
  36. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/scripts/find_invalid_ids.py +0 -0
  37. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/scripts/find_invalid_st_tag.py +0 -0
  38. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/scripts/find_invalid_value_attr.py +0 -0
  39. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/scripts/find_missing_images.py +0 -0
  40. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/scripts/find_missing_title.py +0 -0
  41. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/src/bindery/__main__.py +0 -0
  42. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/src/bindery/library.py +0 -0
  43. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/src/bindery/pagination.py +0 -0
  44. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/src/bindery/reserialize.py +0 -0
  45. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/src/bindery/validate.py +0 -0
  46. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/src/bindery/watermark.py +0 -0
  47. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/test_facility/agda.epub +0 -0
  48. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/test_facility/agda_fixed.epub +0 -0
  49. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/test_facility/last_man_out.epub +0 -0
  50. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/test_facility/last_man_out_fixed.epub +0 -0
  51. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/test_facility/leaves_of_grass.epub +0 -0
  52. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/test_facility/leaves_of_grass_fixed.epub +0 -0
  53. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/test_facility/leaves_of_grass_st_fixed.epub +0 -0
  54. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/test_facility/leaves_of_grass_tags_fixed.epub +0 -0
  55. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/test_facility/muse_of_nightmares.epub +0 -0
  56. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/test_facility/muse_of_nightmares_fixed.epub +0 -0
  57. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/test_facility/poor_mans_fight.epub +0 -0
  58. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/test_facility/poor_mans_fight_fixed.epub +0 -0
  59. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/test_facility/test_block_in_inline.py +0 -0
  60. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/test_facility/test_duplicate_playorder.py +0 -0
  61. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/test_facility/test_empty_body.py +0 -0
  62. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/test_facility/test_illegal_tags.py +0 -0
  63. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/test_facility/test_invalid_ids.py +0 -0
  64. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/test_facility/test_invalid_value.py +0 -0
  65. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/test_facility/test_missing_title.py +0 -0
  66. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/test_facility/unworthy_gods.epub +0 -0
  67. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/test_facility/unworthy_gods_fixed.epub +0 -0
  68. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/testing_facility/books_to_fix/.gitkeep +0 -0
  69. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/tests/test_library.py +0 -0
  70. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/tests/test_pagination.py +0 -0
  71. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/tests/test_reserialize.py +0 -0
  72. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/tests/test_validate.py +0 -0
  73. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/tests/test_version.py +0 -0
  74. {bindery_cli-0.27.0 → bindery_cli-0.29.0}/tests/test_watermark.py +0 -0
@@ -0,0 +1,21 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ build/
5
+ dist/
6
+ .venv/
7
+ .ruff_cache/
8
+ .pytest_cache/
9
+
10
+ # Ignore actual books used for testing to prevent IP leaks
11
+ testing_facility/books_to_fix/*
12
+ !testing_facility/books_to_fix/.gitkeep
13
+ testing_facility/top500candidates/*.epub
14
+
15
+ # Compiled Java scratch (FastSweep harness)
16
+ *.class
17
+
18
+ # Local virtualenvs (each carries its own auto-generated inner .gitignore,
19
+ # which is the only reason they were invisible until now)
20
+ venv/
21
+ .venv_ci/
@@ -1,4 +1,4 @@
1
- # CLAUDE.md (Bindery)
1
+ # CLAUDE.md (bindery-cli)
2
2
 
3
3
  Per-project guidance. Overrides the global file where they conflict.
4
4
 
@@ -14,13 +14,13 @@ Born from the 2026 library audit (see the user memory `calibre-library-epubcheck
14
14
 
15
15
  - **Minimal Dependencies.** Runtime deps are exactly: `tqdm` (progress/output),
16
16
  `vir-tui` (shared TUI rendering) and `cquarry` (read-only Calibre `metadata.db` access,
17
- adopted in v0.16.0). `vir-tui` is PINNED to an exact commit in pyproject.toml
18
- (bump deliberately); `cquarry` deliberately tracks `@main` since v0.19.0 so CI always
19
- exercises the current library. `html5lib` remains the one approved
17
+ adopted in v0.16.0), pinned as PyPI ranges in pyproject.toml (`vir-tui>=2.2.0`,
18
+ `cquarry>=1.9.0`); `uv.lock` records the resolved versions. `html5lib` remains the one approved
20
19
  heavy-parsing exception (used only for the `--reserialize` fix, imported lazily so every
21
20
  other mode runs without it). Tests use the standard `unittest` framework. epubcheck is an
22
- external CLI dependency, and `calibredb` is required for the `--install-to-calibre`
23
- feature. Before adding any further Python package, stop and ask.
21
+ external CLI dependency expected on PATH; `--install-to-calibre` needs no external
22
+ binary, it re-registers the format through cquarry's write module (the calibredb
23
+ subprocess was retired in v0.24.0). Before adding any further Python package, stop and ask.
24
24
  - **Semantics-preserving transforms by default, everything else fenced behind a flag.**
25
25
  The always-on core is exactly five well-formedness fixes (prolog junk, duplicate
26
26
  `xmlns`, bare `&`, named entities, void self-closing) plus the NCX pipeline; every core
@@ -28,7 +28,8 @@ Born from the 2026 library audit (see the user memory `calibre-library-epubcheck
28
28
  visible content. Two kinds of exceptions exist, both opt-in and off unless requested:
29
29
  * **Structural repairs** (`--fix-empty-body`, `--fix-missing-title`, `--fix-id-colons`,
30
30
  `--fix-page-map`, `--strip-epub3-attrs`, `--downgrade-epub3-tags`,
31
- `--unwrap-block-in-inline`, `--strip-invalid-value`, `--unwrap-illegal-tags`;
31
+ `--unwrap-block-in-inline`, `--strip-invalid-value`, `--unwrap-illegal-tags`,
32
+ `--prune-missing-resources`, `--strip-broken-anchors`, `--encode-url-spaces`;
32
33
  transforms.py, threaded through epub.py): they alter markup structure or fabricate
33
34
  minimal content. v0.14–v0.16 ran these unconditionally, which broke this rule;
34
35
  v0.17.0 restored it. `--unwrap-illegal-tags` additionally protects any illegal-tag
@@ -50,8 +51,10 @@ Born from the 2026 library audit (see the user memory `calibre-library-epubcheck
50
51
  visible (they clear errors), so a run with no measurable improvement is a noop and
51
52
  nothing is applied. Changing either bar means re-running the library dry run.
52
53
  - **Library writes are sacred.** Replacement must stay atomic (temp in same dir, then
53
- `os.replace`), touch only the `.epub`, preserve mode, and be dry-run by default. Calibre integrations should seamlessly swap formats using `calibredb add_format -replace`.
54
- Never write to the library without `-apply`. Test every change on `/tmp` copies first.
54
+ `os.replace`), touch only the `.epub`, preserve mode, and be dry-run by default.
55
+ Calibre format installation goes through cquarry's write module (`--install-to-calibre`;
56
+ the calibredb subprocess was retired in v0.24.0). Never write to the library without
57
+ `--apply`. Test every change on `/tmp` copies first.
55
58
 
56
59
  ## Layout
57
60
 
@@ -59,11 +62,11 @@ Born from the 2026 library audit (see the user memory `calibre-library-epubcheck
59
62
  - `src/bindery/pagination.py`: the opt-in lossy page-number strip (runhead detection, page-layer decision, block-centric removal/merge, safety nets).
60
63
  - `src/bindery/watermark.py`: the opt-in lossy watermark strip (anchored and anchorless signature removal).
61
64
  - `src/bindery/reserialize.py`: structural repair via `html5lib`.
62
- - `src/bindery/audit.py`: read-only body-text audits (`content`, `pagenumbers`, `emptytext`, `ocr`, and `monolithic` since v0.21.0, `--max-doc-chars N`) behind the `audit` subcommand (v0.15.0). Since v0.18.0 library mode resolves EPUB paths through `cquarry.get_format_path()` and can apply a tag to flagged books via `cquarry.write.WritableCalibreDB` (only with explicit `--tag`; the only sanctioned write path). Since v0.19.0 `audit --id BOOK_ID` audits a single library book through cquarry's single-entity `get_book()` fetch (no library-wide cache; supports `--tag`; incompatible with the directory argument; comma lists since v0.23.0). Since v0.22.0 every archive entry is fully read (CRC + decompression) before analysis — a damaged entry reports its own CORRUPT verdict instead of feeding emptytext — and manifest/NCX references to absent files classify as `convention` (bloated ToC, consecutive span) or `fragment` (broken span). Hazard: the scan loop reuses `tag` as its per-book display column, so the `--tag` argument is captured as `audit_tag` at function entry — do not collapse them again.
65
+ - `src/bindery/audit.py`: read-only body-text audits (`content`, `pagenumbers`, `emptytext`, `ocr`, and `monolithic` since v0.21.0, `--max-doc-chars N`) behind the `audit` subcommand (v0.15.0). Since v0.18.0 library mode resolves EPUB paths through `cquarry.get_format_path()` and can apply a tag to flagged books via `cquarry.write.WritableCalibreDB` (only with explicit `--tag`; the only sanctioned write path). Since v0.19.0 `audit --id BOOK_ID` audits a single library book through cquarry's single-entity `get_book()` fetch (no library-wide cache; supports `--tag`; incompatible with the directory argument; comma lists since v0.23.0). Since v0.22.0 every archive entry is fully read (CRC + decompression) before analysis — a damaged entry reports its own CORRUPT verdict instead of feeding emptytext — and manifest/NCX references to absent files classify as `convention` (bloated ToC, consecutive span) or `fragment` (broken span). Hazard: the scan loop reuses `tag` as its per-book display column, so the `--tag` argument is captured as `audit_tag` at function entry — do not collapse them again. Since v0.29.0 `audit --json FILE` writes per-file analyzer verdicts in the `library --json` shape (directory, library, and single-book modes; with `--id`, exactly one id; emptytext omitted from a record when the archive verdict owns the body-text story).
63
66
  - `src/bindery/epub.py`: archive rewrite, NCX uid sync, RepairReport, mismatch detection, and the opt-in structural-repair plumbing (including the CSS precondition scan).
64
67
  - `src/bindery/validate.py`: epubcheck wrapper, the `gate` (improvement) and `no_worse` (no-regression, for the lossy strips) acceptance bars.
65
68
  - `src/bindery/library.py`: Calibre walk, atomic replace, backups, and native format installation. Since v0.19.0 `CalibreIdResolver` resolves the book id from `metadata.db` through cquarry (one lazy path→id map per run; since v0.20.0 the map comes from `CalibreDB.format_path_index()`, re-normalized with `resolve().lower()` for the resolver's symlinked-directory and case-insensitive matching) — the `(id)` directory-name regex is only the no-catalog fallback. Since v0.24.0 `install_format()` places the repaired file atomically and updates the `data` row through `cquarry.write.WritableCalibreDB` (remove+add in one `batch()`; `add_format` refuses duplicates); the external `calibredb` CLI is gone. Never reintroduce directory-name guessing as the primary source: renamed/mismatched directories would replace the wrong book.
66
- - `src/bindery/cli.py`: `repair` and `library` subcommands, including `--all` and `--install-to-calibre`.
69
+ - `src/bindery/cli.py`: `repair` and `library` subcommands, including `--all` and `--install-to-calibre`; since v0.29.0 also the `run` verbs — `phase1` (pre-import vetting over loose files: the audit battery composed with one gated repair sweep; read-only until `--apply-lossy`, which IS the lossy-strip consent) and `phase3` (the scoped post-import apply step: `library --id --sweep --only all --apply --all --install-to-calibre` plus a pre/post summary; mechanically refuses unscoped sweeps with exit 2). Both take `--non-interactive` and surface open questions as `decisions_needed` in their JSON, never prompts. They drive the shipped `run_library` through the real parser, so no flag can drift between the verb and the subcommand it wraps.
67
70
  - `tests/`: transforms, end-to-end repair, atomic replace, pagination, watermarks, the audit analyzers (content/pagenumbers/emptytext/ocr/monolithic, archive integrity, spine classification), the library sweep and `CalibreIdResolver`, validate, reserialize, CLI wiring, and the version pin.
68
71
 
69
72
  ## Conventions
@@ -1,16 +1,38 @@
1
+ Metadata-Version: 2.5
2
+ Name: bindery-cli
3
+ Version: 0.29.0
4
+ Summary: Audit EPUB body text and repair broken EPUBs with deterministic, epubcheck-gated fixes; atomic in-place replacement in a Calibre library.
5
+ Author: vrnvctss
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Keywords: calibre,ebook,epub,epubcheck,html-repair,repair,watermarks
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: End Users/Desktop
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Classifier: Topic :: Utilities
15
+ Requires-Python: >=3.14
16
+ Requires-Dist: cquarry>=1.9.0
17
+ Requires-Dist: tqdm
18
+ Requires-Dist: vir-tui>=2.2.0
19
+ Provides-Extra: reserialize
20
+ Requires-Dist: html5lib>=1.1; extra == 'reserialize'
21
+ Description-Content-Type: text/markdown
22
+
1
23
  <div align="center">
2
- <img src="logo.svg" width="96" height="96" alt="Bindery logo"/>
3
- <h1>Bindery</h1>
24
+ <img src="logo.svg" width="96" height="96" alt="bindery-cli logo"/>
25
+ <h1>bindery-cli</h1>
4
26
  <p>Audit EPUB body text and repair broken EPUBs with safe, deterministic fixes, validated by epubcheck, with optional in-place replacement in a Calibre library.</p>
5
27
  </div>
6
28
 
7
29
  <p align="center">
8
- <img src="docs/screenshots/library-sweep.png" alt="Bindery's dry-run library sweep: per-book epubcheck results and a summary table ending in 'no files written'">
30
+ <img src="docs/screenshots/library-sweep.png" alt="bindery-cli's dry-run library sweep: per-book epubcheck results and a summary table ending in 'no files written'">
9
31
  </p>
10
32
 
11
33
  ## What it fixes
12
34
 
13
- Bindery makes accidentally broken markup well-formed again. It does not rewrite or reflow content. The **default pass** applies only a small set of deterministic, semantics-preserving well-formedness fixes that real-world EPUBs (especially Calibre conversions) trip over — nothing else changes without an explicit flag:
35
+ bindery-cli makes accidentally broken markup well-formed again. It does not rewrite or reflow content. The **default pass** applies only a small set of deterministic, semantics-preserving well-formedness fixes that real-world EPUBs (especially Calibre conversions) trip over — nothing else changes without an explicit flag:
14
36
 
15
37
  - **Unclosed void elements** (`<link>`, `<br>`, `<img>`, ...) get self-closed.
16
38
  - **Undeclared named entities** (`&nbsp;`, `&deg;`, `&eacute;`, ...) become numeric character references that every XML parser understands.
@@ -38,6 +60,9 @@ Everything below is off until its flag is passed (or all at once via `--all`). T
38
60
  - **`--strip-epub3-attrs`**: scrubs the EPUB3-only attributes epubcheck rejects on an EPUB2 package — `page-progression-direction`, `epub:type`, `aria-label` (a fixed, documented set; rendering is unchanged, and lookalikes like `type` or the wider aria family survive).
39
61
  - **`--downgrade-epub3-tags`**: downgrades EPUB3/HTML5 semantic elements to their EPUB2 equivalents — `figure`/`section` to `div`, `figcaption` to `p` — keeping existing classes and appending the semantic name (`class="figure"`) as the styling hook. Tag names a stylesheet styles as an element selector are protected book-wide, so styled formatting is never destroyed.
40
62
  - **`--unwrap-illegal-tags`**: strips completely invalid or deprecated HTML tags that break EPUB3 validation (`<st>`, `<sentence>`, `<o>`, `<w>`, `<pagebreak>`) while retaining their inner text. Any of those names styled as an *element selector* by an EPUB stylesheet (`.css` entries and inline `<style>` blocks alike; class/id selectors like `.st`/`#w` don't count) is protected for the whole book, guaranteeing format preservation.
63
+ - **`--prune-missing-resources`**: removes references to files the archive does not contain (RSC-007/PKG-010): dead `<link>` elements, anchors' `href` to absent files (anchor text preserved), absent `<img>` sources (replaced by their alt text when they carry one), and orphaned non-spine OPF manifest items. Spine documents are never pruned: a missing spine document is reported as a damaged fragment, not silently dropped.
64
+ - **`--strip-broken-anchors`**: strips href attributes that cannot resolve, keeping the anchor text byte-for-byte: a `#fragment` the target document does not define (RSC-020/RSC-012; NCX navTargets fall back to the document target, so chapter navigation survives) and unresolvable URI schemes (`kindle:`, `file:`).
65
+ - **`--encode-url-spaces`**: percent-encodes raw spaces in `src`/`href` attribute values across the package (OPF manifest, NCX, content documents). A URL with a literal space is not a valid URL (RSC-020) and unresolvable on strict readers; the encoded form denotes the same file.
41
66
 
42
67
 
43
68
  Three opt-in fixes are **lossy** and stand apart from the semantics-preserving rest:
@@ -61,7 +86,7 @@ The lossy modes (`--strip-pagination`, `--strip-broken-tags`, and `--strip-water
61
86
 
62
87
  ## Install
63
88
 
64
- Python 3.14+, plus epubcheck on `PATH` for the gate. Dependencies: `tqdm` for progress output, plus the pinned VirInvictus libraries `vir-tui` (TUI rendering) and `cquarry` (read-only Calibre database access); `html5lib` remains an optional extra, needed only for `--reserialize`.
89
+ Python 3.14+, plus epubcheck on `PATH` for the gate. Dependencies: `tqdm` for progress output, plus the VirInvictus libraries `vir-tui` (TUI rendering) and `cquarry` (read-only Calibre database access); `html5lib` remains an optional extra, needed only for `--reserialize`.
65
90
 
66
91
  ```sh
67
92
  uv tool install bindery-cli # core tools
@@ -70,14 +95,16 @@ uv tool install "bindery-cli[reserialize]" # incl. --reserialize
70
95
 
71
96
  ## Usage
72
97
 
73
- Three verbs: `bindery repair` fixes a single EPUB epubcheck-gated, `bindery audit`
74
- reports content flaws without touching anything, and `bindery library` sweeps a Calibre
75
- library tree (dry run by default; `--apply` replaces accepted books atomically in place).
98
+ Four verbs: `bindery repair` fixes a single EPUB epubcheck-gated, `bindery audit`
99
+ reports content flaws without touching anything, `bindery library` sweeps a Calibre
100
+ library tree (dry run by default; `--apply` replaces accepted books atomically in place),
101
+ and `bindery run` composes the acquisition pathway's EPUB slices (phase-1 pre-import
102
+ vetting, phase-3 scoped post-import repair) for a calling agent.
76
103
  The sections below take each in turn.
77
104
 
78
105
  ## Auditing
79
106
 
80
- Bindery includes a comprehensive auditing tool to inspect EPUB body text for non-schema flaws that epubcheck cannot catch. It extracts and analyzes the visible text to detect content issues, producing console reports that can be used to filter your library or feed into `bindery repair`.
107
+ bindery-cli includes a comprehensive auditing tool to inspect EPUB body text for non-schema flaws that epubcheck cannot catch. It extracts and analyzes the visible text to detect content issues, producing console reports that can be used to filter your library or feed into `bindery repair`.
81
108
 
82
109
  Scan a Calibre library for specific issues:
83
110
 
@@ -112,6 +139,12 @@ cd ~/docs/Calibre\ Library && bindery audit all --id 1234,1235
112
139
  ```
113
140
  `--id` composes with `--tag`: the book is tagged only if the audit flags it.
114
141
 
142
+ **Machine-readable reports:** `--json FILE` writes the run's verdicts in the same shape `library --json` uses: one record per book with a `status` (`clean`/`problem`/`error`) and per-analyzer verdicts (`problem`, `status`, `details`), plus the always-on archive/spine verdicts (OK when they were silent). This is the contract downstream consumers of the pre-import vetting slice read.
143
+ ```sh
144
+ bindery audit all ~/Downloads/epubs --json vetting-report.json
145
+ ```
146
+ With `--id`, `--json` accepts exactly one book id (each single-book run writes the file wholesale).
147
+
115
148
  **Spine-integrity reporting:** Both `audit` and `library` reports now classify manifest/NCX references that point to absent files. A `convention` verdict means the ToC is bloated but the present documents form a consecutive chapter span (e.g., the Wandering Inn official-build pattern, safe). A `fragment` verdict means the span itself is broken.
116
149
 
117
150
  **Archive integrity:** every audit fully reads each archive entry (CRC + decompression), so a damaged download is reported CORRUPT — with the first broken entry named — instead of being mislabeled EMPTY by `emptytext`. `library --sweep` splits its `unreadable` bucket into `not_a_zip` / `truncated` / `encrypted` / `corrupt_entry`, so the right disease is visible without leaving the sweep.
@@ -162,7 +195,7 @@ bindery library ~/docs/Calibre\ Library --only all --apply --all --install-to-ca
162
195
  - `--only {fatals,ncx,all}` restricts the candidate set. `ncx` targets NCX-001 mismatches (detected without epubcheck); `fatals` needs `--audit`.
163
196
  - `--id <ids>` limits the sweep to a comma-separated list of Calibre book IDs, skipping the full library walk.
164
197
  - `--audit CSV` (the `fatals,errors,warnings,path` format produced by an epubcheck sweep) skips clean books so a run is fast. Paths are resolved on both sides, and a CSV that matches nothing triggers a loud warning instead of silently selecting zero books.
165
- - `--sweep` replaces the CSV step entirely: it runs a live epubcheck sweep for candidate selection and reuses each result as that book's before-measurement, so no book is checked twice. Bindery launches a transparent, persistent Java daemon (`EpubcheckDaemon`) to evaluate candidates in the background, eliminating the JVM startup penalty. This reduces validation time from ~5 seconds per book down to ~0.05 seconds, allowing `--sweep` to scan a 5,000-book library in just 10 minutes. Combine with `--only fatals` for a self-contained, lightning-fast "find and fix the broken books" run.
198
+ - `--sweep` replaces the CSV step entirely: it runs a live epubcheck sweep for candidate selection and reuses each result as that book's before-measurement, so no book is checked twice. bindery-cli first tries a persistent Java daemon (`EpubcheckDaemon`) to eliminate the per-book JVM startup; in practice the daemon cannot boot on most installs (its classpath carries only epubcheck's launcher jar, not the `lib/` dependencies epubcheck itself puts on the classpath), and the run silently falls back to the standard per-book epubcheck subprocess at the usual seconds per book. Budget accordingly. Fixing the classpath is a recorded decision (it would also change the counts the gate measures), not a shipped behavior. Combine with `--only fatals` for a self-contained "find and fix the broken books" run.
166
199
  - `--json FILE` writes a machine-readable report of the whole run (per-book status, before/after counts, applied flag, summary totals). `--manual-list FILE` writes the paths of every book that was not auto-repaired, one per line, ready for manual follow-up.
167
200
  - `--apply` is required to write; the default is a dry run. `--backup DIR` mirrors originals before replacing; `--backup-inplace` writes `.epub.bak` beside each file.
168
201
  - `--install-to-calibre` resolves the Calibre book id from `metadata.db` via cquarry (one read-only path→id map per run), so a hand-renamed `Author/Title (id)/` directory can never send the repaired file to the wrong book. The directory-name regex is only a no-catalog fallback; with neither, the file is saved atomically in place.
@@ -173,11 +206,47 @@ bindery library ~/docs/Calibre\ Library --only all --apply --all --install-to-ca
173
206
  - Exit codes: 0 for a clean sweep, 1 for a usage error, 2 when any book was rejected, unreadable, or failed epubcheck (for scripts and cron).
174
207
  - `repair` refuses to overwrite an existing output file unless `--force` is given.
175
208
 
209
+ ## Run slices
210
+
211
+ `bindery run` wraps the acquisition pathway's EPUB steps as verbs. They add no
212
+ repair classes of their own: they compose the shipped audit and library runners
213
+ in the documented order, and both speak `--json` for a calling agent.
214
+
215
+ **`run phase1 DIR`** is the pre-import vetting slice for a directory of loose
216
+ files: the audit battery (corruption sweep, content battery, monolithic) in one
217
+ decompression pass per book, then the gated repair sweep (epubcheck, watermark
218
+ detection, repairability) over the same directory. The verb is read-only until
219
+ `--apply-lossy` is passed, and that flag IS the recorded lossy-strip consent;
220
+ `--backup DIR` mirrors originals first (keep backups outside the vetted
221
+ directory). Consent questions never block the verb: a read-only run that finds
222
+ repairable books records an `apply_lossy` entry in the JSON's
223
+ `decisions_needed` instead of prompting, and `--non-interactive` declares that
224
+ contract for callers. Exit codes per the `library` contract: 0 clean, 1 usage,
225
+ 2 when any book is flagged, rejected, unreadable, or failed epubcheck (a
226
+ consent question alone is not trouble).
227
+
228
+ ```sh
229
+ bindery run phase1 ~/Downloads --json phase1-report.json # read-only
230
+ bindery run phase1 ~/Downloads --apply-lossy --backup /tmp/phase1-backups
231
+ ```
232
+
233
+ **`run phase3 --ids IDS`** (from the library directory) is the post-import
234
+ scoped repair sweep: exactly `library --id IDS --sweep --only all --apply --all
235
+ --install-to-calibre` plus a pre/post epubcheck summary. It mechanically
236
+ refuses an unscoped library-wide sweep with exit 2 (a full-library sweep is a
237
+ dedicated hours-long task, never a verb call), and books left partial or
238
+ unreadable surface as `decisions_needed` in the JSON.
239
+
240
+ ```sh
241
+ cd ~/docs/Calibre\ Library && bindery run phase3 --ids 5071,5072 --json phase3-report.json
242
+ ```
243
+
176
244
  ## Companion scripts
177
245
 
178
- `scripts/` holds standalone, read-only utilities that are useful for EPUB maintenance but fall outside Bindery's repair contract (fixing what they find would be a content change, which Bindery makes only via the opt-in `--strip-pagination`):
246
+ `scripts/` holds standalone, read-only utilities that are useful for EPUB maintenance but fall outside bindery-cli's repair contract (fixing what they find would be a content change, which bindery-cli makes only via the opt-in `--strip-pagination`):
179
247
 
180
248
  - `find_missing_images.py`: scans a library tree and reports every book whose `<img>` tags point at files that do not exist inside the archive (a common defect in converted EPUBs). Reads the archives in place; nothing is unpacked or written. The library path is set at the bottom of the script.
249
+ - `fast_sweep.py`: compiles and drives `FastSweep.java`, a parallel JVM epubcheck sweep that pays JVM startup once instead of per book (a 7,000-book dry-run sweep drops from hours to minutes). `--mode=audit` emits the `fatals,errors,warnings,path` CSV that `bindery library --audit` reads; `--mode=extract` emits per-book error codes for aggregation (`--summary` prints the per-code report). The epubcheck jar is located from the `epubcheck` launcher or `EPUBCHECK_JAR`; the harness compiles once (`--release 25`) and is cached on mtime.
181
250
 
182
251
  ## Development
183
252
 
@@ -193,7 +262,7 @@ MIT. See [LICENSE](LICENSE).
193
262
 
194
263
  ## Support
195
264
 
196
- If Bindery's useful to you and you'd like to chip in:
265
+ If bindery-cli's useful to you and you'd like to chip in:
197
266
 
198
267
  - liberapay · [liberapay.com/bdkl](https://liberapay.com/bdkl/)
199
268
  - bitcoin
@@ -1,32 +1,16 @@
1
- Metadata-Version: 2.5
2
- Name: bindery-cli
3
- Version: 0.27.0
4
- Summary: High-speed EPUB repair surgeon with a persistent Java validation daemon, safe HTML unwrapping, and native Calibre integration.
5
- Author: vrnvctss
6
- License: MIT
7
- License-File: LICENSE
8
- Keywords: calibre,ebook,epub,epubcheck,html-repair,repair,watermarks
9
- Requires-Python: >=3.14
10
- Requires-Dist: cquarry>=1.9.0
11
- Requires-Dist: tqdm
12
- Requires-Dist: vir-tui>=2.2.0
13
- Provides-Extra: reserialize
14
- Requires-Dist: html5lib>=1.1; extra == 'reserialize'
15
- Description-Content-Type: text/markdown
16
-
17
1
  <div align="center">
18
- <img src="logo.svg" width="96" height="96" alt="Bindery logo"/>
19
- <h1>Bindery</h1>
2
+ <img src="logo.svg" width="96" height="96" alt="bindery-cli logo"/>
3
+ <h1>bindery-cli</h1>
20
4
  <p>Audit EPUB body text and repair broken EPUBs with safe, deterministic fixes, validated by epubcheck, with optional in-place replacement in a Calibre library.</p>
21
5
  </div>
22
6
 
23
7
  <p align="center">
24
- <img src="docs/screenshots/library-sweep.png" alt="Bindery's dry-run library sweep: per-book epubcheck results and a summary table ending in 'no files written'">
8
+ <img src="docs/screenshots/library-sweep.png" alt="bindery-cli's dry-run library sweep: per-book epubcheck results and a summary table ending in 'no files written'">
25
9
  </p>
26
10
 
27
11
  ## What it fixes
28
12
 
29
- Bindery makes accidentally broken markup well-formed again. It does not rewrite or reflow content. The **default pass** applies only a small set of deterministic, semantics-preserving well-formedness fixes that real-world EPUBs (especially Calibre conversions) trip over — nothing else changes without an explicit flag:
13
+ bindery-cli makes accidentally broken markup well-formed again. It does not rewrite or reflow content. The **default pass** applies only a small set of deterministic, semantics-preserving well-formedness fixes that real-world EPUBs (especially Calibre conversions) trip over — nothing else changes without an explicit flag:
30
14
 
31
15
  - **Unclosed void elements** (`<link>`, `<br>`, `<img>`, ...) get self-closed.
32
16
  - **Undeclared named entities** (`&nbsp;`, `&deg;`, `&eacute;`, ...) become numeric character references that every XML parser understands.
@@ -54,6 +38,9 @@ Everything below is off until its flag is passed (or all at once via `--all`). T
54
38
  - **`--strip-epub3-attrs`**: scrubs the EPUB3-only attributes epubcheck rejects on an EPUB2 package — `page-progression-direction`, `epub:type`, `aria-label` (a fixed, documented set; rendering is unchanged, and lookalikes like `type` or the wider aria family survive).
55
39
  - **`--downgrade-epub3-tags`**: downgrades EPUB3/HTML5 semantic elements to their EPUB2 equivalents — `figure`/`section` to `div`, `figcaption` to `p` — keeping existing classes and appending the semantic name (`class="figure"`) as the styling hook. Tag names a stylesheet styles as an element selector are protected book-wide, so styled formatting is never destroyed.
56
40
  - **`--unwrap-illegal-tags`**: strips completely invalid or deprecated HTML tags that break EPUB3 validation (`<st>`, `<sentence>`, `<o>`, `<w>`, `<pagebreak>`) while retaining their inner text. Any of those names styled as an *element selector* by an EPUB stylesheet (`.css` entries and inline `<style>` blocks alike; class/id selectors like `.st`/`#w` don't count) is protected for the whole book, guaranteeing format preservation.
41
+ - **`--prune-missing-resources`**: removes references to files the archive does not contain (RSC-007/PKG-010): dead `<link>` elements, anchors' `href` to absent files (anchor text preserved), absent `<img>` sources (replaced by their alt text when they carry one), and orphaned non-spine OPF manifest items. Spine documents are never pruned: a missing spine document is reported as a damaged fragment, not silently dropped.
42
+ - **`--strip-broken-anchors`**: strips href attributes that cannot resolve, keeping the anchor text byte-for-byte: a `#fragment` the target document does not define (RSC-020/RSC-012; NCX navTargets fall back to the document target, so chapter navigation survives) and unresolvable URI schemes (`kindle:`, `file:`).
43
+ - **`--encode-url-spaces`**: percent-encodes raw spaces in `src`/`href` attribute values across the package (OPF manifest, NCX, content documents). A URL with a literal space is not a valid URL (RSC-020) and unresolvable on strict readers; the encoded form denotes the same file.
57
44
 
58
45
 
59
46
  Three opt-in fixes are **lossy** and stand apart from the semantics-preserving rest:
@@ -77,7 +64,7 @@ The lossy modes (`--strip-pagination`, `--strip-broken-tags`, and `--strip-water
77
64
 
78
65
  ## Install
79
66
 
80
- Python 3.14+, plus epubcheck on `PATH` for the gate. Dependencies: `tqdm` for progress output, plus the pinned VirInvictus libraries `vir-tui` (TUI rendering) and `cquarry` (read-only Calibre database access); `html5lib` remains an optional extra, needed only for `--reserialize`.
67
+ Python 3.14+, plus epubcheck on `PATH` for the gate. Dependencies: `tqdm` for progress output, plus the VirInvictus libraries `vir-tui` (TUI rendering) and `cquarry` (read-only Calibre database access); `html5lib` remains an optional extra, needed only for `--reserialize`.
81
68
 
82
69
  ```sh
83
70
  uv tool install bindery-cli # core tools
@@ -86,14 +73,16 @@ uv tool install "bindery-cli[reserialize]" # incl. --reserialize
86
73
 
87
74
  ## Usage
88
75
 
89
- Three verbs: `bindery repair` fixes a single EPUB epubcheck-gated, `bindery audit`
90
- reports content flaws without touching anything, and `bindery library` sweeps a Calibre
91
- library tree (dry run by default; `--apply` replaces accepted books atomically in place).
76
+ Four verbs: `bindery repair` fixes a single EPUB epubcheck-gated, `bindery audit`
77
+ reports content flaws without touching anything, `bindery library` sweeps a Calibre
78
+ library tree (dry run by default; `--apply` replaces accepted books atomically in place),
79
+ and `bindery run` composes the acquisition pathway's EPUB slices (phase-1 pre-import
80
+ vetting, phase-3 scoped post-import repair) for a calling agent.
92
81
  The sections below take each in turn.
93
82
 
94
83
  ## Auditing
95
84
 
96
- Bindery includes a comprehensive auditing tool to inspect EPUB body text for non-schema flaws that epubcheck cannot catch. It extracts and analyzes the visible text to detect content issues, producing console reports that can be used to filter your library or feed into `bindery repair`.
85
+ bindery-cli includes a comprehensive auditing tool to inspect EPUB body text for non-schema flaws that epubcheck cannot catch. It extracts and analyzes the visible text to detect content issues, producing console reports that can be used to filter your library or feed into `bindery repair`.
97
86
 
98
87
  Scan a Calibre library for specific issues:
99
88
 
@@ -128,6 +117,12 @@ cd ~/docs/Calibre\ Library && bindery audit all --id 1234,1235
128
117
  ```
129
118
  `--id` composes with `--tag`: the book is tagged only if the audit flags it.
130
119
 
120
+ **Machine-readable reports:** `--json FILE` writes the run's verdicts in the same shape `library --json` uses: one record per book with a `status` (`clean`/`problem`/`error`) and per-analyzer verdicts (`problem`, `status`, `details`), plus the always-on archive/spine verdicts (OK when they were silent). This is the contract downstream consumers of the pre-import vetting slice read.
121
+ ```sh
122
+ bindery audit all ~/Downloads/epubs --json vetting-report.json
123
+ ```
124
+ With `--id`, `--json` accepts exactly one book id (each single-book run writes the file wholesale).
125
+
131
126
  **Spine-integrity reporting:** Both `audit` and `library` reports now classify manifest/NCX references that point to absent files. A `convention` verdict means the ToC is bloated but the present documents form a consecutive chapter span (e.g., the Wandering Inn official-build pattern, safe). A `fragment` verdict means the span itself is broken.
132
127
 
133
128
  **Archive integrity:** every audit fully reads each archive entry (CRC + decompression), so a damaged download is reported CORRUPT — with the first broken entry named — instead of being mislabeled EMPTY by `emptytext`. `library --sweep` splits its `unreadable` bucket into `not_a_zip` / `truncated` / `encrypted` / `corrupt_entry`, so the right disease is visible without leaving the sweep.
@@ -178,7 +173,7 @@ bindery library ~/docs/Calibre\ Library --only all --apply --all --install-to-ca
178
173
  - `--only {fatals,ncx,all}` restricts the candidate set. `ncx` targets NCX-001 mismatches (detected without epubcheck); `fatals` needs `--audit`.
179
174
  - `--id <ids>` limits the sweep to a comma-separated list of Calibre book IDs, skipping the full library walk.
180
175
  - `--audit CSV` (the `fatals,errors,warnings,path` format produced by an epubcheck sweep) skips clean books so a run is fast. Paths are resolved on both sides, and a CSV that matches nothing triggers a loud warning instead of silently selecting zero books.
181
- - `--sweep` replaces the CSV step entirely: it runs a live epubcheck sweep for candidate selection and reuses each result as that book's before-measurement, so no book is checked twice. Bindery launches a transparent, persistent Java daemon (`EpubcheckDaemon`) to evaluate candidates in the background, eliminating the JVM startup penalty. This reduces validation time from ~5 seconds per book down to ~0.05 seconds, allowing `--sweep` to scan a 5,000-book library in just 10 minutes. Combine with `--only fatals` for a self-contained, lightning-fast "find and fix the broken books" run.
176
+ - `--sweep` replaces the CSV step entirely: it runs a live epubcheck sweep for candidate selection and reuses each result as that book's before-measurement, so no book is checked twice. bindery-cli first tries a persistent Java daemon (`EpubcheckDaemon`) to eliminate the per-book JVM startup; in practice the daemon cannot boot on most installs (its classpath carries only epubcheck's launcher jar, not the `lib/` dependencies epubcheck itself puts on the classpath), and the run silently falls back to the standard per-book epubcheck subprocess at the usual seconds per book. Budget accordingly. Fixing the classpath is a recorded decision (it would also change the counts the gate measures), not a shipped behavior. Combine with `--only fatals` for a self-contained "find and fix the broken books" run.
182
177
  - `--json FILE` writes a machine-readable report of the whole run (per-book status, before/after counts, applied flag, summary totals). `--manual-list FILE` writes the paths of every book that was not auto-repaired, one per line, ready for manual follow-up.
183
178
  - `--apply` is required to write; the default is a dry run. `--backup DIR` mirrors originals before replacing; `--backup-inplace` writes `.epub.bak` beside each file.
184
179
  - `--install-to-calibre` resolves the Calibre book id from `metadata.db` via cquarry (one read-only path→id map per run), so a hand-renamed `Author/Title (id)/` directory can never send the repaired file to the wrong book. The directory-name regex is only a no-catalog fallback; with neither, the file is saved atomically in place.
@@ -189,11 +184,47 @@ bindery library ~/docs/Calibre\ Library --only all --apply --all --install-to-ca
189
184
  - Exit codes: 0 for a clean sweep, 1 for a usage error, 2 when any book was rejected, unreadable, or failed epubcheck (for scripts and cron).
190
185
  - `repair` refuses to overwrite an existing output file unless `--force` is given.
191
186
 
187
+ ## Run slices
188
+
189
+ `bindery run` wraps the acquisition pathway's EPUB steps as verbs. They add no
190
+ repair classes of their own: they compose the shipped audit and library runners
191
+ in the documented order, and both speak `--json` for a calling agent.
192
+
193
+ **`run phase1 DIR`** is the pre-import vetting slice for a directory of loose
194
+ files: the audit battery (corruption sweep, content battery, monolithic) in one
195
+ decompression pass per book, then the gated repair sweep (epubcheck, watermark
196
+ detection, repairability) over the same directory. The verb is read-only until
197
+ `--apply-lossy` is passed, and that flag IS the recorded lossy-strip consent;
198
+ `--backup DIR` mirrors originals first (keep backups outside the vetted
199
+ directory). Consent questions never block the verb: a read-only run that finds
200
+ repairable books records an `apply_lossy` entry in the JSON's
201
+ `decisions_needed` instead of prompting, and `--non-interactive` declares that
202
+ contract for callers. Exit codes per the `library` contract: 0 clean, 1 usage,
203
+ 2 when any book is flagged, rejected, unreadable, or failed epubcheck (a
204
+ consent question alone is not trouble).
205
+
206
+ ```sh
207
+ bindery run phase1 ~/Downloads --json phase1-report.json # read-only
208
+ bindery run phase1 ~/Downloads --apply-lossy --backup /tmp/phase1-backups
209
+ ```
210
+
211
+ **`run phase3 --ids IDS`** (from the library directory) is the post-import
212
+ scoped repair sweep: exactly `library --id IDS --sweep --only all --apply --all
213
+ --install-to-calibre` plus a pre/post epubcheck summary. It mechanically
214
+ refuses an unscoped library-wide sweep with exit 2 (a full-library sweep is a
215
+ dedicated hours-long task, never a verb call), and books left partial or
216
+ unreadable surface as `decisions_needed` in the JSON.
217
+
218
+ ```sh
219
+ cd ~/docs/Calibre\ Library && bindery run phase3 --ids 5071,5072 --json phase3-report.json
220
+ ```
221
+
192
222
  ## Companion scripts
193
223
 
194
- `scripts/` holds standalone, read-only utilities that are useful for EPUB maintenance but fall outside Bindery's repair contract (fixing what they find would be a content change, which Bindery makes only via the opt-in `--strip-pagination`):
224
+ `scripts/` holds standalone, read-only utilities that are useful for EPUB maintenance but fall outside bindery-cli's repair contract (fixing what they find would be a content change, which bindery-cli makes only via the opt-in `--strip-pagination`):
195
225
 
196
226
  - `find_missing_images.py`: scans a library tree and reports every book whose `<img>` tags point at files that do not exist inside the archive (a common defect in converted EPUBs). Reads the archives in place; nothing is unpacked or written. The library path is set at the bottom of the script.
227
+ - `fast_sweep.py`: compiles and drives `FastSweep.java`, a parallel JVM epubcheck sweep that pays JVM startup once instead of per book (a 7,000-book dry-run sweep drops from hours to minutes). `--mode=audit` emits the `fatals,errors,warnings,path` CSV that `bindery library --audit` reads; `--mode=extract` emits per-book error codes for aggregation (`--summary` prints the per-code report). The epubcheck jar is located from the `epubcheck` launcher or `EPUBCHECK_JAR`; the harness compiles once (`--release 25`) and is cached on mtime.
197
228
 
198
229
  ## Development
199
230
 
@@ -209,7 +240,7 @@ MIT. See [LICENSE](LICENSE).
209
240
 
210
241
  ## Support
211
242
 
212
- If Bindery's useful to you and you'd like to chip in:
243
+ If bindery-cli's useful to you and you'd like to chip in:
213
244
 
214
245
  - liberapay · [liberapay.com/bdkl](https://liberapay.com/bdkl/)
215
246
  - bitcoin
@@ -1,4 +1,4 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="Bindery logo">
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="bindery-cli logo">
2
2
  <rect width="128" height="128" rx="24" fill="#181616"/>
3
3
  <!-- book spine / cover being rebound -->
4
4
  <rect x="30" y="26" width="68" height="76" rx="5" fill="none" stroke="#c5c9c5" stroke-width="5"/>