amagi-binder
Turn a folder of exhibits into one court-ready PDF. You list the exhibits in a short YAML or CSV file. amagi-binder Bates-stamps every page and adds a slip sheet before each exhibit. It builds a table of contents with clickable links, bookmarks every exhibit, and writes a CSV log of which Bates numbers went where. Built for solo attorneys, small firms and people representing themselves.
You don't need Acrobat Pro or a litigation suite for this. Everything runs on your machine. Files are never uploaded, and you don't need an account or an API key.
Install
pipx install amagi-binder
Needs Python 3.9 or newer. It takes PDF, JPG, PNG and TIFF files.
Usage
The examples folder holds a made-up breach-of-warranty case with six exhibits:
a contract, a purchase order, an email thread, a scanned landscape service log, a photo and a
demand letter. The check and build commands below run from that folder.
1. Start an index from a folder
Here the same six files sit in a folder called my_exhibits (this run was on Windows, so the
paths print with backslashes):
$ amagi-binder init my_exhibits/ --title "Plaintiff's Trial Exhibits"
wrote my_exhibits\index.yaml with 6 exhibits (sorted by file name). Edit it, then run:
amagi-binder build my_exhibits\index.yaml -o binder.pdf
Files are sorted the way a person would sort them (2_ before 10_), and each title is a guess
from the file name:
# amagi-binder index. Edit titles, dates and order, then run:
# amagi-binder build <this file> -o binder.pdf
title: "Plaintiff's Trial Exhibits"
caption: "" # e.g. "Doe v. Roe, Case No. 1:26-cv-00001"
bates:
prefix: "" # e.g. "DOE" or "DOE_"
start: 1
digits: 6
labels: numbers # or: letters
exhibits:
- file: "01_supply_agreement.pdf"
title: "Supply agreement"
date: ""
- file: "02_purchase_order.pdf"
title: "Purchase order"
date: ""
...
Fix the titles, add dates, and put the exhibits in the order you want. The index order is the
binder order. An exhibit can also take a fixed label (7A), a description for its slip
sheet, a pages range ("1-3,5"), or several files that belong together, like an email and
its attachment. See examples/riverside/index.yaml for a filled-in
index.
Prefer a spreadsheet? Use a CSV with the columns file,label,title,date,description,pages
instead (example).
2. Check the plan
check reads every file and shows what each exhibit will get. It writes nothing.
$ amagi-binder check riverside/index.yaml
Exhibit Pages Bates range Binder pages Title
1 3 RBK000001 - RBK000003 2-5 Equipment Supply and Service Agreement
2 1 RBK000004 6-7 Purchase Order RB-2025-014
3 1 RBK000005 8-9 Emails between D. Whitfield and P. Osterm...
4 1 RBK000006 10-11 Northgate service log for the DX-40
5 1 RBK000007 12-13 Photo of the oven display reading 547 F a...
6 1 RBK000008 14-15 Letter demanding return and refund under...
6 exhibits, 15 binder pages, 1 contents page. Next Bates number: RBK000009
OK
It stops with exit code 1 on a missing file, a password-protected or broken PDF, a bad page
range, or two exhibits with the same label. It warns about files in the folder that the index
leaves out. --json prints the same plan for scripts.
3. Build the binder
$ amagi-binder build riverside/index.yaml -o binder.pdf
Exhibit Pages Bates range Binder pages Title
...
wrote binder.pdf (6 exhibits, 15 binder pages, 1 contents page. Next Bates number: RBK000009)
wrote binder_bates_log.csv
The sample output is in the repo: examples/binder.pdf and examples/binder_bates_log.csv.
What's in the PDF:
- A table of contents listing each exhibit's label, title, date, page count, Bates range and binder page. Click a row to jump to that exhibit. Long lists run onto more pages.
- A slip sheet before each exhibit, with the label in large type, the title, date, description, Bates range and case caption.
- A Bates number on every exhibit page. By default it sits bottom-right, 0.3 inch from the edge, as the page appears on screen. Rotated scans and cropped pages are stamped in the right spot.
- Bookmarks for every exhibit, with a sub-bookmark for each file when an exhibit has several.
- Page labels, so the viewer's page box shows
RBK000004and you can type a Bates number to jump to it.
The log has one row per exhibit, with a SHA-256 hash of each source file so you can show later which version of a document you produced:
exhibit,title,date,files,pages,bates_begin,bates_end,binder_page_begin,binder_page_end,sha256
1,Equipment Supply and Service Agreement,"January 15, 2025",01_supply_agreement.pdf,3,RBK000001,RBK000003,2,5,11202a2a1919931129ebf405d27c836632364512c43c153dcb00ed10ceb555d0
2,Purchase Order RB-2025-014,"January 20, 2025",02_purchase_order.pdf,1,RBK000004,RBK000004,6,7,6f1df52cfad2f2fd28bb037cd13e5dfc283510f6144624fc678f9283d6e83d0c
Options
Flags override the index file:
$ amagi-binder build riverside/index.yaml -o binder.pdf --prefix DEF_ --start 1201 --legend CONFIDENTIAL --stamp-slips -q
wrote binder.pdf (6 exhibits, 15 binder pages, 1 contents page. Next Bates number: DEF_001215)
wrote binder_bates_log.csv
| Flag | What it does |
|---|---|
--prefix, --start, --digits |
Bates format: DEF_001201 |
--legend TEXT |
A second stamp at bottom-left, such as CONFIDENTIAL |
--exhibit-stamp |
Adds Exhibit 3, page 2 of 5 at bottom-center |
--position, --font-size, --box |
Stamp placement, size, and a white box behind it |
--stamp-slips |
Slip sheets get a Bates number too |
--no-bates, --no-slips, --no-toc |
Leave parts out |
--labels letters, --label-start N |
Exhibits A, B, C (after Z comes AA), or start at 12 |
--exhibit-word Tab |
Slip sheets say TAB instead of EXHIBIT |
--paper letter|legal|a4 |
Size of slip sheets, contents pages and photo pages |
--exhibits DIR |
Folder the file names are relative to, if the index lives elsewhere |
Run the same index twice and you get a byte-identical PDF.
Exit codes: 0 ok, 1 the index has problems, 2 usage error.
Use it from Claude
The repo ships a SKILL.md. Copy it to ~/.claude/skills/amagi-binder/SKILL.md,
point Claude at a folder, and say "make an exhibit binder, Bates prefix DOE." Claude writes the
index, runs check, asks you about anything it flagged, then builds the binder. The skill tells
Claude never to invent titles or dates.
Limits
- It numbers and assembles. It does not redact, OCR, or decide what to produce or withhold.
- Check your court's local rules on exhibit format. The defaults (stamp bottom-right, letter paper, slip sheets) are common, but they aren't universal.
- Stamps are drawn on top of the page. If a document's content runs to the edge, look at that
page and use
--boxor--positionif a stamp covers text. - Password-protected PDFs must be unlocked first (for example
qpdf --decrypt --password=PASSWORD in.pdf out.pdf). - Stamps use the built-in Helvetica font, which covers Western European characters. Other
characters in titles or legends print as
?.
If this saved you time
A star helps other people find it. If a PDF stamps in the wrong place, open an issue and attach the page if you can share it.
More AmagiLabs tools
- amagi-litigation-deadlines: computes litigation response deadlines from a trigger event and date, with a citation for every rule.
- amagi-contract-deadlines: pulls every renewal, notice and expiry date out of a folder of contracts into a calendar and a CSV.
- amagi-legal-review-skills: Claude Code skills for contract and lease review.
- More at github.com/amagilabs.
Development
pip install -e ".[dev]"
pytest
python examples/make_examples.py # rebuild the example exhibits
License
MIT. See LICENSE. amagi-binder uses pikepdf (MPL-2.0), ReportLab (BSD), Pillow (MIT-CMU) and PyYAML (MIT). The example case, its parties and its documents are made up.
Metadata
Release files for amagi-binder 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| amagi_binder-0.1.0.tar.gz | 93.6 kB | Without provenance | Details for amagi_binder-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| amagi_binder-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for amagi_binder-0.1.0-py3-none-any.whl |
Total release size: 119.2 kB
Release files / amagi_binder-0.1.0.tar.gz
| Download URL | amagi_binder-0.1.0.tar.gz |
|---|---|
| Size | 93.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8e9dff15ccd2419a6cd58a5da37fe04e02e032175cd7dd546c78690fc9ae435d
|
|
BLAKE2b-256 checksum How to use checksums |
133e214cbae1dfea19f1b8ae5e5e7aec86d60eab109c96aee2b0c5d7d6fb3879
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|
Release files / amagi_binder-0.1.0-py3-none-any.whl
| Download URL | amagi_binder-0.1.0-py3-none-any.whl |
|---|---|
| Size | 25.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
526718f360b96905bc473a0b1c7b92c25ae86e9d776eda12199e7c13b6d3ae64
|
|
BLAKE2b-256 checksum How to use checksums |
31f1daa35dfdb928d0cfdbae28520e9636abd6ef897bfe88167e15b4a6df56c8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|
Security for amagi-binder 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.