Skip to content

DrBaher/contract-vault-cli

Repository files navigation

contract-vault

Never miss a contract renewal, notice deadline, or obligation. A git-backed register for your signed contracts: register an executed deal, then search the portfolio and surface renewal / notice / payment deadlines as a calendar. Local-first, single-file, stdlib-only — no DB, no daemon, no SaaS.

obligations is a view over the register, not a separate tool.

  • Stdlib only. Zero runtime dependencies; fully functional with no extras.
  • Git-backed, single file. No DB, no daemon. The vault is a plain git repo.
  • Deterministic. The register, .ics calendar, and reminders work fully with the LLM off and no network.
  • Verify, not trust. Every field carries its source (deterministic | llm | manual | none) and a confidence, surfaced everywhere.

Part of the contract-ops suite — optional. contract-vault stands on its own, but it also composes with the contract-ops CLI suite: it can ingest extract-cli output (so any LLM/parsing need is delegated to that extraction step, never reimplemented here) and shares the suite's agent conventions — sitting at the manage-out end of extract → … → sign → contract-vault. It's the post-signature sibling of template-vault: template-vault stores blank templates, contract-vault stores the signed instances.


Run this

pipx run contract-vault demo     # zero-config: register two sample contracts → renewals calendar
# or, installed:  pip install contract-vault && contract-vault demo

That runs the full ingest → find → due flow on bundled fixtures (no extract-cli, no LLM, no network) and prints a renewals/notice calendar. Point it at your own signed contract with extract contract.pdf | contract-vault ingest -.

Where to go next


Install

pip install contract-vault            # zero dependencies, fully functional

Extraction itself is delegated to extract-cli. contract-vault works without it (piped JSON / .json input), but for one-shot ingestion of real documents install it with the backends you need:

pip install extract-cli               # text / markdown
pip install "extract-cli[docx]"       # + Word
pip install "extract-cli[pdf]"        # + PDF
# convenience extras that pull extract-cli in via contract-vault:
pip install "contract-vault[pdf]"

Requires Python 3.9+.


Quick start

contract-vault init ~/contracts            # create a git-backed vault
contract-vault demo                        # full ingest→find→due flow on bundled fixtures

# Ingest an executed contract (shells out to `extract` if it is on PATH):
contract-vault ingest ~/Downloads/acme-msa.pdf --vault ~/contracts

# ...or compose with extract explicitly (works even if extract is elsewhere):
extract ~/Downloads/acme-msa.pdf --json | contract-vault ingest - --vault ~/contracts

contract-vault list --vault ~/contracts
contract-vault find --auto-renew --value-gt 100000 --vault ~/contracts
contract-vault due --within 90d --format ics --vault ~/contracts > renewals.ics

The --vault flag is optional: contract-vault discovers the vault by walking up from the current directory (like git), or reads $CONTRACT_VAULT_DIR.


The vault model

A vault is a git repository laid out exactly like template-vault, one directory per deal:

~/contracts/
├── .contract-vault.json                       # vault config (kind, schema_version)
├── acme-corporation/
│   └── master-services-agreement/
│       ├── record.json                         # the structured deal record
│       └── source.pdf                          # the executed document (when vaulted)
└── initech-inc/
    └── mutual-non-disclosure-agreement/
        └── record.json

Each record.json conforms to docs/spec/contract-record.schema.json and carries: parties[], effective_date, expiration_date, term{length, auto_renew, notice_period_days, renewal_window}, governing_law, value, status, signed_on, source{path, sha256, format, vaulted}, obligations[]{type, due, description, source, confidence}, provenance{from_extract, extractor_version, ...}, and a field_meta map recording the source + confidence of every field. Re-ingesting the same document (same sha256) is idempotent.


Commands

Command What it does
init [path] Create / initialize an executed-contract vault (a git repo). --encrypt enables git-crypt encryption at rest.
ingest <file> Run extract <file> --json (if on PATH) and store + commit the record.
ingest - Read piped extract JSON from stdin (extract f --json | contract-vault ingest -).
list List stored deals.
get <id> / show <id> Print one record (by path, leaf name, or unique prefix).
find / search Query by --counterparty, --governing-law, --currency CCC, --expiring-before DATE, --value-gt N, --auto-renew, or full-text. Pair --currency with --value-gt for currency-aware thresholds.
due / obligations Project upcoming actions. --within 30d|60d|90d, --format ics|json|table, --status open|done|waived|all (default open), --type, --owner. Emits valid RFC 5545 .ics.
obligation <deal> <id> Track an obligation: --status open|done|waived, --owner NAME, --recurrence weekly|monthly|quarterly|semiannual|annual, --reminders 30,7. Completed obligations drop off due; recurring ones expand into occurrences.
stats Portfolio stats: count, total value, expiring soon, by counterparty / governing law.
export Export the register as csv / md / json (--expiring-before, --needs-review). For spreadsheets & reports.
verify Integrity check: source sha256 matches + git tree clean.
review Deterministic worklist of fields that are unidentified / LLM-derived / low-confidence (--threshold; --strict exits 1 for CI). Never calls an LLM.
accept <deal> <field> Mark a reviewed field as human-verified (source=manual), optionally --value to correct it; recomputes the calendar for date/term changes. Bulk via accept --from FILE.
risk / at-risk Renewal exposure: missed notice deadlines (CRITICAL if auto-renewing), imminent notices, and expirations (--within, --strict).
remind The reminder digest: obligations whose reminder window is open right now (honors per-obligation --reminders). For cron/agents (--strict, --format).
config reminders Set corpus-wide default reminder lead-times per type (--type … --set 60,30,7), --show/--clear. Applies to every contract; overridable per obligation.
history <deal> The deal's git history (ingest + each accept).
demo Run the full flow on bundled fixtures (no extract-cli, no LLM).

Global I/O conventions (shared across the suite)

  • --catalog json — print the machine-readable command/flag catalog and exit (the suite discovery contract; agents call this at startup, before any subcommand).
  • --json — machine-readable JSON on stdout (opt-in; default output is human).
  • --why — structured explanation on stderr ([why] <header> + indented lines).
  • -q / --quiet / --silent — suppress non-error output.
  • --no-color — disable ANSI color (also honors NO_COLOR and FORCE_COLOR).
  • -V / --version, -h / --help.
  • Exit codes: 0 ok, 1 failure / findings (e.g. verify mismatch), 2 bad usage.

Composability

# One-shot: extract a PDF and register it.
extract deal.pdf --json | contract-vault ingest -

# Export the next quarter of renewals/notices to any calendar app.
contract-vault due --within 90d --format ics > renewals.ics

# Hand the portfolio to finance/legal as a spreadsheet or a report.
contract-vault export > portfolio.csv
contract-vault export --format md --expiring-before 2027-01-01 > renewals-report.md

# Pipe the machine-readable register into jq.
contract-vault find --expiring-before 2026-01-01 --json | jq '.deals[].id'

# A reminder manifest is just the JSON projection (days_until + suggested lead_days).
contract-vault due --within 365d --format json > reminders.json

--format json (or --json) for due/obligations doubles as a reminder manifest: each row carries days_until and a suggested lead_days. The .ics output is the same data rendered as RFC 5545, with a VALARM per event.


Notifications & reminders

contract-vault has no daemon and sends no notifications itself — it computes deadlines and lead-times deterministically; delivery is your environment's job. There are three ways to actually get notified:

  1. Calendar (zero code). contract-vault due --within 365d --format ics > contracts.ics and subscribe to it. Each event carries VALARMs (TRIGGER:-P30D, …) from the per-obligation --reminders, so your calendar app fires the alerts natively.
  2. Cron / CI gate. Run on a schedule and act on the exit code or piped JSON — e.g. contract-vault remind --strict || mail-me (exit 1 when something is due), or contract-vault risk --within 30d --strict.
  3. An agent (Claude, etc.). The agent polls the CLI and notifies through its own channel. contract-vault is built for this (--catalog json, --json, exit codes, AGENTS.md).

The remind command is the turnkey digest — "what should I be notified about now":

contract-vault remind                 # obligations whose reminder window is open today
contract-vault remind --strict --json # exit 1 + JSON if anything is due (cron/agent loop)

It returns each obligation only while 0 ≤ days_until ≤ its longest reminder lead, honoring obligation <deal> <id> --reminders 30,7. A daily remind --json is exactly the set a notifier should send.

Set lead-times once for the whole corpus (instead of per obligation) with a vault policy:

contract-vault config reminders --type expiration --set 60,30,7   # applies to every contract
contract-vault config reminders --type obligation  --set 14,7

Resolution per obligation: per-obligation --reminders → vault default for its type → vault default catch-all → built-in default.

To run these on a schedule (cron/launchd, a Claude Code remote routine, or CI) and get notified — you pick the cadence and channel — see docs/SCHEDULING.md and the example wrapper scripts/contract-vault-remind.sh.


Shell completion

contract-vault ships a hidden __complete subcommand (matching the template-vault sibling). Register it for bash with:

_contract_vault() {
  COMPREPLY=( $(contract-vault __complete "${COMP_WORDS[@]:1}") )
}
complete -F _contract_vault contract-vault

It completes subcommands, deal ids (for get/show/verify), and query flags/counterparties/governing-laws (for find/search). (extract-cli ships an extract completion <shell> subcommand instead; both styles are valid across the suite — contract-vault uses the hidden-subcommand style.)


LLM (opt-in, delegated) and "verify, not trust"

contract-vault never calls an LLM itself. Identification of clauses and fields — including the LLM fallback for anything the deterministic tiers miss — is the job of extract-cli. ingest --llm simply forwards --llm to the extract step, which runs that LLM tier; shared config is looked up at ~/.config/contract-ops/llm.json first, then ./config/llm.json (see config/llm.json.example). The register / due / .ics paths stay fully deterministic and work with the LLM off.

Every field contract-vault stores carries its source (deterministic | llm | manual | none) and a confidence. review surfaces the ones worth a human look — without calling a model:

contract-vault review                       # unidentified / llm-derived / low-confidence fields
contract-vault review --strict               # exit 1 if anything needs review (CI gate)
contract-vault find --needs-review --json    # which deals need attention
extract deal.pdf --json --llm | contract-vault ingest -   # improve them at extraction time

# After a human checks a contract, record the verdict (-> source=manual, drops out of review):
contract-vault accept acme-corp/msa governing_law --value Delaware
contract-vault accept acme-corp/msa expiration_date --value 2027-01-31   # recomputes the calendar
contract-vault accept acme-corp/msa value            # accept the current value as verified

Security

It stores executed contracts, so security matters — see SECURITY.md for the full model. In brief:

  • No network, no telemetry, no in-tool LLM, zero runtime deps. JSON-only parsing (no eval/pickle/yaml); subprocess calls are shell-free; paths are slug-contained.
  • Owner-only permissions (POSIX): the vault is 0700, records/sources 0600.
  • Untrusted input can't pull arbitrary files in: piped/.json source_path is metadata only; only ingest <doc> vaults a file you chose.
  • The working copy is plaintext — protect it with full-disk encryption, a private git remote, and optionally contract-vault init --encrypt (git-crypt encrypts what's committed/pushed; back up the key).
  • Integrity/audit: verify (SHA-256 + clean git tree) and history <deal>.

Interop

The cross-CLI contracts live under docs/spec/ as JSON Schema 2020-12 and are registered in docs/INTEROP.md:


Development

make install      # editable install with dev extras
make test         # full test suite
make typecheck    # mypy --strict
make coverage     # tests under coverage + report
make build        # wheel + sdist
make smoke        # build, install the wheel in a clean venv, run it
make spec-check   # validate fixtures + outputs against docs/spec schemas (offline)

See ARCHITECTURE.md and CONTRIBUTING.md.

License

MIT © DrBaher

About

Git-backed register for signed contracts: surface renewals, notice deadlines, and obligations as a portfolio and an .ics calendar. Stdlib Python. Part of the eight-CLI contract-ops suite — cli.drbaher.com

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages