Metadata-Version: 2.4
Name: amagi-binder
Version: 0.1.0
Summary: Turn a folder of exhibits and a short YAML or CSV index into one court-ready PDF: Bates stamps, exhibit slip sheets, a linked table of contents, bookmarks, and a CSV Bates log.
Author-email: "AmagiLabs (Amagisolutionsgroup, Inc.)" <dev@amagisolutionsgroup.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/amagilabs/amagi-binder
Project-URL: Issues, https://github.com/amagilabs/amagi-binder/issues
Keywords: bates,bates-numbering,exhibits,legal,litigation,pdf,discovery,cli,claude-code
Classifier: Environment :: Console
Classifier: Intended Audience :: Legal Industry
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Office/Business
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pikepdf>=8.0
Requires-Dist: reportlab<5,>=4.0; python_version < "3.10"
Requires-Dist: reportlab>=4.0; python_version >= "3.10"
Requires-Dist: pillow>=9.0
Requires-Dist: pyyaml>=6.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pypdf>=4.0; extra == "dev"
Dynamic: license-file

# 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.

![demo](assets/demo.gif)

![Contents page, exhibit slip sheet, and a stamped landscape page](assets/binder_pages.png)

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](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:

```yaml
# 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](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](examples/riverside/index.csv)).

### 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](examples/binder.pdf) and
[examples/binder_bates_log.csv](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 `RBK000004` and 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](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 `--box` or `--position` if 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](https://github.com/amagilabs/amagi-litigation-deadlines): computes
  litigation response deadlines from a trigger event and date, with a citation for every rule.
- [amagi-contract-deadlines](https://github.com/amagilabs/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](https://github.com/amagilabs/amagi-legal-review-skills): Claude Code
  skills for contract and lease review.
- More at [github.com/amagilabs](https://github.com/amagilabs).

## Development

```
pip install -e ".[dev]"
pytest
python examples/make_examples.py   # rebuild the example exhibits
```

## License

MIT. See [LICENSE](LICENSE). amagi-binder uses [pikepdf](https://pikepdf.readthedocs.io)
(MPL-2.0), [ReportLab](https://www.reportlab.com/opensource/) (BSD),
[Pillow](https://python-pillow.org) (MIT-CMU) and [PyYAML](https://pyyaml.org) (MIT). The example
case, its parties and its documents are made up.
