{"id":51594387,"url":"https://github.com/drbaher/extract-cli","last_synced_at":"2026-07-11T17:02:18.962Z","repository":{"id":359554594,"uuid":"1245901497","full_name":"DrBaher/extract-cli","owner":"DrBaher","description":"Ingest any contract (.md/.txt/.html/.docx/.pdf) and emit structured JSON — parties, clauses, dates, governing law — with a confidence and source on every field. Stdlib Python. Part of the eight-CLI contract-ops suite — cli.drbaher.com","archived":false,"fork":false,"pushed_at":"2026-05-31T17:13:28.000Z","size":878,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-31T19:11:17.705Z","etag":null,"topics":["agent-first","clause","cli","contract","contract-ops","extraction","json","legal-tech","nda","python"],"latest_commit_sha":null,"homepage":null,"language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DrBaher.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-21T17:08:53.000Z","updated_at":"2026-05-31T17:13:30.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/DrBaher/extract-cli","commit_stats":null,"previous_names":["drbaher/extract-cli"],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/DrBaher/extract-cli","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DrBaher%2Fextract-cli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DrBaher%2Fextract-cli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DrBaher%2Fextract-cli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DrBaher%2Fextract-cli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DrBaher","download_url":"https://codeload.github.com/DrBaher/extract-cli/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DrBaher%2Fextract-cli/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35368768,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-11T02:00:05.354Z","response_time":104,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agent-first","clause","cli","contract","contract-ops","extraction","json","legal-tech","nda","python"],"created_at":"2026-07-11T17:02:17.392Z","updated_at":"2026-07-11T17:02:18.946Z","avatar_url":"https://github.com/DrBaher.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# extract-cli\n\n\u003e **Ingest any contract — yours or a counterparty's foreign paper — and get\n\u003e structured JSON.** Hand `extract-cli` a `.md` / `.txt` / `.html` / `.docx` /\n\u003e `.pdf` and it returns the parties, dates, term, governing law, a normalized\n\u003e **clause map**, defined terms, and a headline value — each with a `confidence`\n\u003e and a `source`, so you **verify, don't trust**. Stdlib-only, single-file, local-first.\n\u003e\n\u003e Works standalone — and also composes with the [contract-ops CLI suite](https://cli.drbaher.com/)\n\u003e as its **open-loop front door**: it turns foreign paper into the suite's canonical,\n\u003e structured vocabulary that [**nda-review-cli**](https://github.com/DrBaher/nda-review-cli),\n\u003e [**compare-cli**](https://github.com/DrBaher/compare-cli), and\n\u003e [**contract-vault**](https://github.com/DrBaher/contract-vault-cli) can consume. Its\n\u003e output is a **cross-CLI data contract** — see [`docs/INTEROP.md`](docs/INTEROP.md) and\n\u003e [`docs/spec/extract-output.schema.json`](docs/spec/extract-output.schema.json).\n\n```\ningest (extract) → review → diff → convert → sign\n   ^you are here\n```\n\n## Run this\n\n```bash\npipx run extract-cli demo        # zero-config: extract a bundled NDA → structured JSON\n# or, installed:  pip install extract-cli \u0026\u0026 extract demo\n```\n\nThat prints the full output contract — parties, dates, term, governing law, and\na clause map normalized onto the suite's canonical vocabulary — for a bundled\nfixture, with no setup and no network. Point it at your own file with\n`extract path/to/contract.docx`.\n\n## Where to go next\n\n- **New here?** Keep reading — [What it does](#what-it-does) and\n  [The two extraction tiers](#the-two-extraction-tiers).\n- **Driving it from an agent?** See [`AGENTS.md`](AGENTS.md) and call\n  `extract --catalog json` at startup to discover commands/flags. The output\n  shape is locked by [`docs/spec/extract-output.schema.json`](docs/spec/extract-output.schema.json).\n- **Wiring it into the pipeline?** See [`docs/INTEROP.md`](docs/INTEROP.md) — the\n  contract is the output schema + the shared clause vocabulary.\n- **Contributing / building a sibling CLI?** [`CONTRIBUTING.md`](CONTRIBUTING.md)\n  and [ARCHITECTURE.md](ARCHITECTURE.md).\n\n## What it does\n\nGive it a contract in **`.md` / `.txt` / `.html`** (native), **`.docx`**, or\n**`.pdf`**, and it returns structured JSON: the parties, dates, term, governing law, a\n**clause map** normalized onto the suite's canonical clause vocabulary, a\ndefined-term inventory, and a headline value. Every field carries a\n`confidence` and a `source` so downstream tools **verify, don't trust**.\n\nIt is **stdlib-only**, single-file, terminal-first, and composable. No DB, no\ndaemon, no network in the default path.\n\n## Install\n\n```bash\npip install extract-cli                 # core: .md/.txt/.html + best-effort .docx/.pdf\npip install \"extract-cli[docx]\"         # higher-fidelity .docx (python-docx)\npip install \"extract-cli[pdf]\"          # higher-fidelity .pdf (pypdf)\npip install \"extract-cli[docx,pdf]\"     # both\n```\n\nThe core has **zero runtime dependencies** and is fully functional on\n`.md`/`.txt`/`.html` with no extras (HTML is also auto-detected when it hides\ninside a `.txt`, e.g. SEC EDGAR filings). `.docx` and `.pdf` work out of the box via stdlib readers; the\n`[docx]`/`[pdf]` extras improve fidelity on complex documents (see\n[ARCHITECTURE.md](ARCHITECTURE.md)).\n\n## The two extraction tiers\n\n`extract-cli` is explicit about *how* it knows each field — encoded in every\nfield's `source` and in `_meta.tiers_used`.\n\n| Tier | When | Fields | Network? |\n|---|---|---|---|\n| **deterministic** | always on (default) | parties, dates, defined terms, **clause map**, governing law, best-effort term/notice/value | none |\n| **llm** | opt-in via `--llm` only | renewal mechanics, obligation phrasing, ambiguous governing law | yes (your provider) |\n\nThe deterministic core is **fully useful without the LLM**. The LLM tier is\nopt-in, never in a hot path, and gated behind an explicit flag and a config\nfile — if no config is present, `--llm` degrades gracefully with a warning and\nyou still get the full deterministic output.\n\n**Clause-map fallback.** Some documents (e.g. `.docx` that auto-number clauses\nvia Word's numbering with no heading style) carry no signal the deterministic\ncascade can see, so its clause map comes back empty. When `--llm` is set *and*\nno clauses were detected, the LLM is asked for the section headings; the result\nis normalized through the same canonical vocabulary and emitted with\n`tier: \"llm\"`, `source: \"llm\"`, and a modest confidence (verify, not trust).\nWhen the deterministic cascade already found clauses, the LLM is not consulted\nfor them.\n\n## Commands\n\n```bash\nextract \u003cpath\u003e            # parse a document → structured JSON on stdout (default)\nextract --catalog json    # machine-readable catalog of commands/flags (agents call at startup)\nextract schema            # print the output JSON Schema (the cross-CLI contract)\nextract fields            # list extractable fields and their tier\nextract demo              # run on a bundled fixture and show the narrative\nextract completion bash   # emit a shell-completion script (bash|zsh)\n```\n\n### Flags\n\n| Flag | Meaning |\n|---|---|\n| `--catalog json` | Print the machine-readable command/flag catalog and exit (the suite discovery contract; agents call this at startup) |\n| `--llm` | Opt-in LLM enrichment of fuzzy fields (off by default) |\n| `--fields a,b,c` | Emit only a subset of top-level fields (e.g. `parties,clauses`) |\n| `--format json\\|table` | Output format (default `json`) |\n| `--no-confidence` | Omit confidence/source markers (reduced convenience view) |\n| `--json` | Force JSON to stdout (the default) |\n| `--why` | Rationale block on **stderr** |\n| `-q`, `--silent`, `--quiet` | Suppress non-error diagnostics |\n| `--no-color` | Disable ANSI color (also honors `NO_COLOR` / `FORCE_COLOR`) |\n| `-V`, `--version` | Print `extract-cli X.Y.Z` |\n\nStreams follow the suite convention: **stdout** is the machine payload (JSON),\n**stderr** is for humans (`--why`, warnings, errors). Exit codes: `0` success,\n`1` low-signal document (e.g. a scanned/empty PDF), `2` bad usage.\n\n## Output shape (abridged)\n\n```jsonc\n{\n  \"document\":   { \"title\": \"...\", \"format\": \"markdown\", \"sha256\": \"…\", \"source_path\": \"nda.md\" },\n  \"parties\":    [ { \"name\": \"Acme Robotics, Inc.\", \"role\": \"Disclosing Party\", \"confidence\": 0.9, \"source\": \"deterministic\" } ],\n  \"dates\":      { \"effective\": { \"value\": \"2024-03-01\", \"confidence\": 0.85, \"source\": \"deterministic\" }, \"expiration\": { \"value\": null, \"confidence\": 0.0, \"source\": \"none\" } },\n  \"term\":       { \"length\": { \"value\": \"3 years\", ... }, \"auto_renew\": { \"value\": true, ... }, \"notice_period_days\": { \"value\": 60, ... } },\n  \"governing_law\": { \"value\": \"State of Delaware\", \"confidence\": 0.85, \"source\": \"deterministic\" },\n  \"jurisdiction\": { \"value\": \"US-DE\", \"confidence\": 0.8, \"source\": \"deterministic\" },\n  \"clauses\":    [ { \"canonical_title\": \"Confidentiality\", \"detected_title\": \"## Confidentiality Obligations\", \"tier\": \"h2\", \"span\": {\"start\": 0, \"end\": 120}, \"confidence\": 0.95, \"source\": \"deterministic\", \"mapped\": true } ],\n  \"defined_terms\": [ { \"term\": \"Confidential Information\", \"confidence\": 0.6, \"source\": \"deterministic\" } ],\n  \"value\":      { \"value\": \"$50,000\", \"confidence\": 0.6, \"source\": \"deterministic\" },\n  \"amounts\":    [ { \"value\": \"$50,000\", \"confidence\": 0.6, \"source\": \"deterministic\" } ],\n  \"signatories\": [ { \"name\": \"Jane Doe\", \"title\": \"CEO\", \"confidence\": 0.55, \"source\": \"deterministic\" } ],\n  \"_meta\":      { \"extractor_version\": \"0.1.11\", \"tiers_used\": [\"deterministic\"], \"llm_used\": false }\n}\n```\n\n## The clause map (the differentiator)\n\nA counterparty's \"SECTION 7. NON-DISCLOSURE\" and your template's\n\"## Confidentiality\" are the same clause. `extract-cli` extends\ntemplate-vault-cli's **clause-detection cascade** — `## H2` headings →\nbold-numbered `**1. …**` → plain numbered (`1. Term`, `Section 3. …`, two-line\n`ARTICLE N`) → ALL-CAPS lines (and an opt-in `--llm` fallback) — plus a built-in\n**canonical alias vocabulary** to normalize foreign clause titles onto the\nnames the rest of the suite already speaks. Clauses it can't map are kept with\n`mapped: false` (and a `*` in the table view) so nothing is silently dropped.\n\n```bash\nextract counterparty.pdf | jq '.clauses[] | {canonical_title, detected_title, mapped}'\n```\n\n## Composability — piping into the rest of the suite\n\n`extract-cli` is built to be the first stage of a Unix pipe. The glue is its\n**stdout JSON + standard tools** (`jq`, `comm`) and the **shared clause\nvocabulary** — `extract`'s `canonical_title` values are the same names\n`template-vault-cli` detects and `nda-review-cli` keys policy on, so a foreign\ndocument's clauses line up with the suite's with no bespoke adapter. Every\nexample below is runnable today (verified against the real sibling CLIs).\n\n```bash\n# 1) Inspect any contract's structure (.md/.txt/.html/.docx/.pdf, one tool).\nextract counterparty.docx | jq '{parties: [.parties[].name],\n  governing_law: .governing_law.value, clauses: [.clauses[].canonical_title]}'\n\n# 2) Clause-coverage gap vs your canonical template in template-vault-cli.\n#    extract normalizes the counterparty's *foreign* headings onto the same\n#    clause vocabulary template-vault detects, so a plain `comm` diffs them.\ntemplate-vault info nda/mutual-standard --json | jq -r '.clauses[].title' | sort \u003e ours.txt\nextract counterparty_nda.docx | jq -r '.clauses[].canonical_title' | sort -u \u003e theirs.txt\ncomm -23 ours.txt theirs.txt    # clauses in OUR standard that THEY are missing\ncomm -13 ours.txt theirs.txt    # clauses THEY added that we don't have\n\n# 3) Intake: extract for structure, nda-review-cli for a policy verdict on the\n#    same foreign doc; merge both views with jq.\nextract counterparty_nda.docx \u003e extract.json\nnda-review review --file counterparty_nda.docx --playbook output/nda_playbook.json \\\n  --out-json review.json\njq -n --slurpfile e extract.json --slurpfile r review.json \\\n  '{parties: [$e[0].parties[].name], governing_law: $e[0].governing_law.value,\n    clauses: ($e[0].clauses | length), decision: $r[0].decision, risk: $r[0].risk_score}'\n\n# 4) Triage a folder of inbound contracts: governing law + parties per file.\nfor f in inbox/*; do\n  extract \"$f\" --fields parties,governing_law --no-confidence \\\n    | jq -c --arg f \"$f\" '{file: $f, gov: .governing_law, parties: [.parties[].name]}'\ndone\n\n# 5) Gate a workflow on extraction confidence (non-zero exit if any clause is shaky).\nextract draft.docx | jq -e '.clauses | all(.confidence \u003e 0.7)' \u0026\u0026 echo \"ok to review\"\n```\n\n\u003e The integration contract is the **output schema** and the **canonical clause\n\u003e vocabulary**, not per-tool flags. See [`docs/INTEROP.md`](docs/INTEROP.md) for\n\u003e the shared conventions and the schema's versioning commitment.\n\n## LLM configuration (opt-in)\n\n`--llm` reads a shared suite config from a fixed user config location:\n\n- `~/.config/contract-ops/llm.json`  (or `$XDG_CONFIG_HOME/contract-ops/llm.json`)\n\nCopy [`config/llm.json.example`](config/llm.json.example) to that path.\nConfigure it once and every suite tool that adopts the same lookup gets LLM\nfeatures for free. The config is never read from the current working directory,\nso running the CLI in an untrusted directory cannot inject an endpoint/api_key.\nWithout it, `--llm` just warns and returns the deterministic output.\n\n## Accuracy\n\nLine coverage tells you the code runs; it doesn't tell you the extraction is\n*correct*. `make eval` scores the deterministic tier against a small corpus of\n**real, executed contracts** (SEC EDGAR filings) with hand-verified ground truth\n([`tests/eval/`](tests/eval/)), reporting precision/recall per field:\n\n| Field | Score |\n|---|---|\n| parties | P 1.00 · R 0.92 · F1 0.96 |\n| effective date | accuracy 1.00 |\n| governing law | accuracy 1.00 |\n| jurisdiction (normalized) | accuracy 1.00 |\n| clauses (recall on verified sections) | 0.86 |\n\nClause recall improved sharply once the HTML reader learned to treat\nemphasis (heading tags, \u003cb\u003e/\u003cu\u003e, CSS font-weight/underline) as section\nheadings; the residual misses are compound/combined heading titles. A test (`tests/test_eval.py`) gates these so\naccuracy can't silently regress.\n\n## Development\n\n```bash\nmake install      # editable install with the [dev] extra\nmake test         # full suite\nmake coverage     # suite + coverage report (installs extras; fails under 100%)\nmake typecheck    # mypy --strict\nmake eval         # accuracy benchmark vs the labeled corpus\nmake build        # wheel + sdist\nmake smoke        # build, install the wheel in a clean venv, run it\nmake spec-check   # assert docs/spec schema == `extract schema`\nmake release VERSION=X.Y.Z\n```\n\nSee [ARCHITECTURE.md](ARCHITECTURE.md) and [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdrbaher%2Fextract-cli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdrbaher%2Fextract-cli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdrbaher%2Fextract-cli/lists"}