@drbaher/draft-cli 0.1.0

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.
@@ -0,0 +1,341 @@
1
+ # PARAM_SCHEMA — v1 contract (locked)
2
+
3
+ This doc is the source of truth for how `draft-cli` discovers placeholders,
4
+ maps them to parameters, validates inputs, and reports results. Locked
5
+ after Q1–Q4 and D1–D4 review. Reviewer: DrBaher.
6
+
7
+ ---
8
+
9
+ ## 1. Stack & posture
10
+
11
+ - **Runtime:** Node.js ≥ 18 (global `fetch`, `node:test`, `--env-file`-style behavior re-implemented inline so we don't require ≥ 20.6).
12
+ - **Distribution:** `npm install -g @drbaher/draft-cli`, single-file `draft-cli.mjs` shebang executable.
13
+ - **Runtime dependencies (v1):** exactly one — `jszip` (MIT, zero transitive) for `.docx` unzip. Everything else uses Node's stdlib. LLM tier uses global `fetch` directly; no SDK dep.
14
+ - **Local-first.** No telemetry. The only network call is the optional LLM tier and only when explicitly configured (see §3 T5).
15
+
16
+ ## 2. Inputs and outputs
17
+
18
+ ```
19
+ draft <template> [flags]
20
+ draft - [flags] # template body from stdin
21
+ draft <cat>/<name>[@ver] ... # pulls via `template-vault get`
22
+ ```
23
+
24
+ - **Input forms accepted:** `path/to/file.md`, `path/to/file.txt`, `path/to/file.docx`, stdin (`-`), or a `template-vault` ref shaped `<category>/<name>[@version]`. Vault refs shell out to `template-vault get` — no library import.
25
+ - **Output:** stdout by default, `--output PATH` for files. Output is always plain text/markdown in v1 — `.docx` is **input-only** for v1. Writing `.docx` back is deferred to v2.
26
+ - **Encoding:** UTF-8 in, UTF-8 out. No BOM written; BOM tolerated on read.
27
+
28
+ ## 3. Detection cascade (sequential-with-stop)
29
+
30
+ The cascade runs each tier in order. The first tier that returns **≥ 1 placeholder** wins and the others are skipped. The active tier is reported in `--why` and in `--json` output.
31
+
32
+ | Tier | Name | Deterministic | Default | Trigger to skip |
33
+ | ---- | -------------- | ------------- | ------- | -------------------------------- |
34
+ | T1 | Bracket | ✅ | on | `--syntax mustache` selected |
35
+ | T2 | Mustache | ✅ | opt-in | not selected via `--syntax` |
36
+ | T3 | DOCX highlight | ✅ | auto | input not `.docx` |
37
+ | T4 | Heuristic | ✅ | on | `--no-heuristic` |
38
+ | T5 | LLM | ❌ | env-gated | no LLM provider configured |
39
+
40
+ ### T1 — Bracket `[...]`
41
+
42
+ A bracketed run is treated as a placeholder when **all** of:
43
+
44
+ 1. `[...]`, no nested brackets, length 1–200.
45
+ 2. **Not** immediately followed by `(` — i.e. not a markdown link
46
+ (`[label](url)` is skipped).
47
+ 3. **Not** a checkbox marker — inner matches `[ xX]{1,3}` is skipped
48
+ (`[x]`, `[ ]`, `[X]`, etc.).
49
+ 4. **Not** a pure section reference — inner matches `\d+(\.\d+)*$` is
50
+ skipped (`[3.1]`, `[4.2.1]`).
51
+ 5. Inner contains **at least one letter** (excludes `[___]`, `[---]`).
52
+ 6. Inner is **not entirely uppercase letters** (excludes
53
+ `[CONFIDENTIALITY]`, `[ARTICLE I]`).
54
+
55
+ Examples that match: `[Party A]`, `[Effective Date]`,
56
+ `[State of California]`, `[Today’s date]`, `[1 year(s)]`,
57
+ `[Fill in state]`,
58
+ `[Evaluating whether to enter into a business relationship with the other party.]`.
59
+
60
+ Examples that don't: `[3.1]`, `[ARTICLE I]`, `[CONFIDENTIALITY]`,
61
+ `[x]`, `[ ]`, `[the docs](https://example.com)`.
62
+
63
+ The rule is intentionally permissive because real legal templates
64
+ (Common Paper, YC SAFE, Bonterms) use sentence-shaped placeholders
65
+ with full punctuation. False positives are filtered via the
66
+ `<template>.params.json` schema (§5); false negatives in this domain
67
+ are higher-cost than false positives.
68
+
69
+ **Canonical key derivation** (when no schema is present): inner →
70
+ lowercase → non-alphanumeric runs collapsed to `_` → leading/trailing
71
+ `_` stripped → prefix `_` if leading char is a digit → truncated at 60
72
+ chars. So `[Party A]` → `party_a`, `[1 year(s)]` → `_1_year_s`,
73
+ `[Today’s date]` → `today_s_date`. Templates with long sentence-shaped
74
+ placeholders should ship a schema file to give them clean keys.
75
+
76
+ Cross-references like `[See Section 4]` *do* match T1 by design. The
77
+ **schema file** (§5) is the disambiguation tool: when present, only declared
78
+ keys substitute and other bracketed runs are left untouched.
79
+
80
+ ### T2 — Mustache `{{...}}`
81
+
82
+ Opt-in via `--syntax mustache`. Matches `{{<inner>}}` where `<inner>` is
83
+ either Title Case (same rule as T1 inner) or snake_case `[a-z][a-z0-9_]{0,78}`.
84
+
85
+ Mixed-convention templates (both `[X]` and `{{X}}` present) emit a
86
+ `doctor`-style stderr warning. The selected `--syntax` family is the only
87
+ one substituted; the other is left untouched in output.
88
+
89
+ ### T3 — DOCX highlight
90
+
91
+ Triggered only when input is `.docx`. Unzip with `jszip`, read
92
+ `word/document.xml`, regex-scan for highlight runs:
93
+
94
+ ```xml
95
+ <w:r>
96
+ <w:rPr><w:highlight w:val="yellow"/></w:rPr>
97
+ <w:t>Acme Corporation</w:t>
98
+ </w:r>
99
+ ```
100
+
101
+ Highlight colors recognized as placeholders: `yellow`, `green`, `cyan`,
102
+ `magenta` (Word's "highlight as TODO" colors). Black/white/auto highlights
103
+ are ignored.
104
+
105
+ The captured text becomes the bracket-equivalent. So `Acme Corporation`
106
+ in a yellow highlight is treated identically to `[Acme Corporation]` from
107
+ T1 — same canonical-key derivation, same alias machinery.
108
+
109
+ Output for .docx input is plain markdown: the text is extracted in
110
+ document order (one paragraph per `<w:p>`), highlights are replaced, and
111
+ the result is written to stdout or `--output`. Round-trip to `.docx`
112
+ is **v2**.
113
+
114
+ ### T4 — Generic-name heuristic
115
+
116
+ A bundled dictionary (`config/heuristic.json` shipped in the wheel) lists
117
+ known generic placeholder values: `Acme Corporation`, `Acme Inc`,
118
+ `Foo Corp`, `John Doe`, `Jane Roe`, `123 Main Street`, `example@example.com`,
119
+ `555-555-1234`, `MM/DD/YYYY`, `TBD`, `[INSERT ___]`, etc. Curated, not
120
+ inferred.
121
+
122
+ Matches against the **untemplated body** (after T1–T3 ran and found
123
+ nothing). Case-sensitive whole-word matching.
124
+
125
+ **Safety gate (D3 locked):** T4 matches **never substitute silently**.
126
+ Behavior:
127
+
128
+ - In a TTY without `--yes-heuristic`: print each match, prompt `y/N`.
129
+ - In a non-TTY without `--yes-heuristic`: print a warning block and
130
+ **leave matches untouched**. Substitution requires explicit opt-in.
131
+ - With `--yes-heuristic`: substitute non-interactively (the user
132
+ has taken ownership).
133
+ - `--no-heuristic` disables T4 entirely.
134
+
135
+ Dictionary override: `--dictionary PATH` replaces (not extends) the bundled list.
136
+
137
+ ### T5 — LLM (last resort, env-gated)
138
+
139
+ Runs only when **both** are true:
140
+ - T1–T4 produced zero placeholders.
141
+ - A provider is configured via env (read from `.env` in the working
142
+ directory or process env — process env wins).
143
+
144
+ Provider env vars (any one suffices):
145
+ - `ANTHROPIC_API_KEY` → Anthropic Messages API (default model: claude-sonnet-4-6).
146
+ - `OPENAI_API_KEY` → OpenAI Responses API (default model: gpt-4o-mini).
147
+ - `DRAFT_LLM_PROVIDER` + `DRAFT_LLM_API_KEY` (+ optional `DRAFT_LLM_MODEL`) → explicit override.
148
+
149
+ If no provider env is present, the cascade **stops at T4** and the CLI
150
+ errors with a clear message: `no placeholders detected by deterministic
151
+ tiers; set ANTHROPIC_API_KEY (or equivalent) in .env to enable LLM
152
+ detection, or pass --syntax mustache if your template uses {{...}}.`
153
+
154
+ The LLM call sends template text **only**, no params file, no user data.
155
+ Prompt asks for a JSON array of placeholder spans with `start`, `end`,
156
+ `suggested_key`. Result is validated against the same canonical-key
157
+ rules; invalid entries are dropped with a warning.
158
+
159
+ `--no-llm` disables T5 even when env is configured (the cascade ends at
160
+ T4). `--llm` asserts that an LLM provider should be available and
161
+ fail-fasts with a clear error if none is configured; it does **not**
162
+ override the sequential-with-stop semantics. Running T5 on top of an
163
+ earlier-tier hit (the "find missed generics in a bracketed template"
164
+ workflow) is a v2 candidate, not v1 behavior.
165
+
166
+ ## 4. Key conventions
167
+
168
+ Three surfaces, one canonical key per parameter.
169
+
170
+ | Surface | Form | Example |
171
+ | ------------- | --------------------- | ---------------- |
172
+ | Match source | Title Case w/ spaces | `Party A Name` |
173
+ | Canonical key | snake_case | `party_a_name` |
174
+ | CLI flag | kebab-case | `--party-a-name` |
175
+ | JSON file key | snake_case | `"party_a_name"` |
176
+
177
+ Derivation when no schema is present: match text → lowercase, spaces → underscores. `Party A Name` → `party_a_name`. The original match text is preserved in output (case/spacing intact); we replace byte-for-byte.
178
+
179
+ Disallowed in placeholders (will error if found in a schema file): dots, slashes, leading digits, hyphens inside the match text. Reserved schema-file keys: `_meta`, `_aliases`, `_required`, `_defaults`.
180
+
181
+ ## 5. Schema file: `<template>.params.json`
182
+
183
+ Sibling file, opt-in. If absent, placeholders are inferred by the active
184
+ cascade tier; every inferred placeholder is treated as required; keys are
185
+ auto-derived.
186
+
187
+ If present, the schema is **authoritative**: only declared parameters
188
+ substitute. Anything else the cascade detects is left untouched and
189
+ listed in `--why` as `unmapped`.
190
+
191
+ **Schema rescue (T1/T2 only).** When a schema declares a phrase that the
192
+ heuristic detection rule would reject (e.g. all-caps `[COMPANY]`, all-
193
+ underscore `[_____________]`, snake_case `[party_a]`), the schema's
194
+ alias list is consulted during detection and rescues that phrase.
195
+ Without this, a schema-declared alias would be silently dropped before
196
+ ever reaching the resolution step. The rescue applies only to bracket
197
+ (T1) and mustache (T2) tiers; T3/T4/T5 use text-based detection that
198
+ doesn't need rescuing.
199
+
200
+ ### Short form (default in docs)
201
+
202
+ ```json
203
+ {
204
+ "party_a": ["Party A", "Disclosing Party"],
205
+ "party_b": ["Party B", "Receiving Party"],
206
+ "effective_date": ["Effective Date"]
207
+ }
208
+ ```
209
+
210
+ Each value is a list of phrase forms (bracket inner text, mustache inner
211
+ text, or highlighted text). The canonical key is **NOT** implicitly in
212
+ its own alias list (Q3 locked) — list it explicitly if needed.
213
+
214
+ ### Long form (with `_meta`)
215
+
216
+ ```json
217
+ {
218
+ "_meta": { "schema_version": 1 },
219
+ "party_a": { "aliases": ["Party A"], "required": true },
220
+ "effective_date": { "aliases": ["Effective Date"], "required": false, "default": "the date first written above" }
221
+ }
222
+ ```
223
+
224
+ Parser selects long form iff a top-level `_meta` key is present. Short
225
+ and long are not mixable within one file.
226
+
227
+ ### Orphan handling (Q4 locked)
228
+
229
+ Schema declares a key whose alias list matches no detected phrase →
230
+ **error**, exit 2. Catches drift early.
231
+
232
+ ## 6. Precedence
233
+
234
+ CLI flag > JSON `--params` file > `--interactive` prompt > schema `default` > error.
235
+
236
+ - CLI flag present (even `""`) wins.
237
+ - JSON value present wins over prompt and default.
238
+ - `--interactive` set AND still missing → prompt.
239
+ - Schema `default` present AND still missing → use the default.
240
+ - Still missing → error, exit 2.
241
+
242
+ ## 7. Validation, modes, errors
243
+
244
+ ### `draft --validate <template> --params FILE`
245
+
246
+ Same lookup, never writes output. Exits 0 if every required key resolves;
247
+ 2 otherwise. Honors all five cascade tiers and the schema if present.
248
+
249
+ ### `draft --list-placeholders <template>`
250
+
251
+ Prints detected placeholders in first-appearance order, deduplicated.
252
+ With `--json`:
253
+
254
+ ```json
255
+ {
256
+ "template": "nda/house-mutual",
257
+ "tier": "bracket",
258
+ "placeholders": [
259
+ { "key": "party_a", "first_seen_as": "Party A",
260
+ "aliases": ["Party A", "Disclosing Party"],
261
+ "required": true, "occurrences": 4, "tier": "bracket" }
262
+ ],
263
+ "warnings": []
264
+ }
265
+ ```
266
+
267
+ ### Error shapes (stderr, red on TTY, honors `NO_COLOR`/`FORCE_COLOR`)
268
+
269
+ ```
270
+ error: missing required parameter(s):
271
+ - party_a (matched: [Party A], [Disclosing Party])
272
+ supply --party-a or set "party_a" in --params
273
+ - effective_date (matched: [Effective Date])
274
+ supply --effective-date or set "effective_date" in --params
275
+ hint: run `draft --list-placeholders nda/house-mutual` to see all parameters.
276
+ ```
277
+
278
+ ```
279
+ error: mixed placeholder conventions in template (4 bracket, 2 mustache).
280
+ note: pass --syntax bracket or --syntax mustache; the other family is left untouched.
281
+ ```
282
+
283
+ ```
284
+ error: schema declares "party_c" with aliases ["Party C","Third Party"],
285
+ but no matching phrase was detected by tier 'bracket'.
286
+ hint: remove the entry from the schema, or add the phrase to the template.
287
+ ```
288
+
289
+ ```
290
+ error: no placeholders detected by deterministic tiers (bracket, mustache,
291
+ docx-highlight, heuristic).
292
+ hint: set ANTHROPIC_API_KEY in .env to enable LLM detection,
293
+ or pass --syntax mustache if your template uses {{...}}.
294
+ ```
295
+
296
+ Exit codes: `0` success, `1` template/input I/O error, `2` validation
297
+ failure, `3` template-vault subprocess failure, `4` LLM tier failure
298
+ (network, auth, malformed response).
299
+
300
+ ## 8. `--why` output
301
+
302
+ Structured stderr block (or stdout under `--json`):
303
+
304
+ ```
305
+ draft: substituted 7 placeholders in nda/house-mutual → draft.md
306
+ why:
307
+ input = nda/house-mutual (via template-vault get)
308
+ tier = bracket
309
+ schema = nda/house-mutual.params.json (short form)
310
+ placeholders = 4 distinct, 12 occurrences
311
+ resolved = 4 (3 from CLI, 1 from --params, 0 interactive, 0 default)
312
+ defaulted = 0
313
+ unresolved = 0
314
+ unmapped = 1 ([See Section 4] — not in schema)
315
+ warnings = 0
316
+ ```
317
+
318
+ ## 9. Out of scope for v1 (deferred — schema is forward-compatible)
319
+
320
+ - Computed placeholders (`[Effective Date + 2 years]`). Long-form schema reserves a future `"computed"` key.
321
+ - Typed parameters (`party`, `date`, `money`). Reserves a future `"type"` key.
322
+ - Cross-template parameter registry (`parties.json`). Additive; would layer underneath `--params` in precedence.
323
+ - `.docx` output round-trip.
324
+ - LLM-assisted suggestion *from a deal description* (current T5 only suggests from template text).
325
+
326
+ ---
327
+
328
+ ## Locked decisions (audit trail)
329
+
330
+ | ID | Question | Decision |
331
+ | --- | --------------------------------------- | -------- |
332
+ | Q1 | Cross-references like [See Section 4] | Subsumed: schema file disambiguates; T4/T5 expand coverage. |
333
+ | Q2 | Short-form vs long-form schema | Both supported; `_meta` selects long form. |
334
+ | Q3 | Canonical key implicit-alias | No — explicit list only. |
335
+ | Q4 | Orphan schema declarations | Error, exit 2. |
336
+ | D1 | Cascade semantics | Sequential-with-stop. |
337
+ | D2 | LLM default behavior | Env-gated auto-fallback at T4 boundary. |
338
+ | D3 | Heuristic safety gate | Warn-only, requires `--yes-heuristic` or interactive confirm. |
339
+ | D4 | .docx parsing | `jszip` + regex on `word/document.xml`. |
340
+
341
+ *End of contract. Code begins once approved.*
package/README.md ADDED
@@ -0,0 +1,305 @@
1
+ # draft-cli
2
+
3
+ A **deterministic placeholder-filler** for legal-document templates. Reads
4
+ bracketed (`[Party A]`) or mustache (`{{Party A}}`) markup, `.docx` yellow
5
+ highlights, or generic-name heuristics; substitutes from CLI flags, a JSON
6
+ params file, or an interactive prompt; writes a ready-to-review draft.
7
+
8
+ Single-file Node.js. One runtime dependency (`jszip`, for `.docx`). Local-first,
9
+ no telemetry, MIT-licensed. Part of the contract-operations suite (see
10
+ [cli.drbaher.com](https://cli.drbaher.com)).
11
+
12
+ ---
13
+
14
+ ## What it does
15
+
16
+ You have a template. You have a deal. You want a draft you can review and
17
+ send. The middle step — finding every `[Party A]`, `[Effective Date]`, and
18
+ `[State of California]` and replacing them with real values — is mechanical,
19
+ deterministic work that doesn't need an LLM and shouldn't need to leave your
20
+ machine.
21
+
22
+ ```
23
+ template-vault get nda/house-mutual | draft - \
24
+ --party-a "Acme Corporation" \
25
+ --party-b "Vendor Inc." \
26
+ --effective-date 2026-06-01 \
27
+ --output draft.md
28
+ ```
29
+
30
+ Or via a params file:
31
+
32
+ ```
33
+ draft nda/house-mutual --params deal-acme.json --output draft.md
34
+ ```
35
+
36
+ `draft` does **only** that step. Templates come from `template-vault-cli` or
37
+ any markdown / .docx / stdin source. Review and red-line happens in
38
+ `nda-review-cli`. Conversion to PDF goes through `docx2pdf-cli`. Signing
39
+ goes through `sign-cli`. Each tool stays small and composable.
40
+
41
+ ---
42
+
43
+ ## Install
44
+
45
+ ```sh
46
+ npm install -g @drbaher/draft-cli
47
+ ```
48
+
49
+ Or run without installing:
50
+
51
+ ```sh
52
+ npx @drbaher/draft-cli@latest --demo
53
+ ```
54
+
55
+ Requires Node.js ≥ 18. Tested on Ubuntu and macOS, Node 18 / 20 / 22.
56
+
57
+ ### Shell completion
58
+
59
+ ```sh
60
+ # bash
61
+ draft --completion bash >> ~/.bashrc
62
+
63
+ # zsh
64
+ draft --completion zsh > ~/.zsh/completions/_draft
65
+ # ensure ~/.zsh/completions is in fpath, then: autoload -U compinit && compinit
66
+ ```
67
+
68
+ Completes flags, the `--syntax bracket|mustache` value, `--completion bash|zsh`,
69
+ and file paths for `--params`, `--output`, and `--dictionary`.
70
+
71
+ ---
72
+
73
+ ## 30-second first run
74
+
75
+ No file authoring required. The bundled demo runs end-to-end:
76
+
77
+ ```sh
78
+ npx @drbaher/draft-cli@latest --demo
79
+ ```
80
+
81
+ ```
82
+ demo: substituting [Party A], [Party B], [Effective Date]
83
+ # Mutual Non-Disclosure Agreement (demo)
84
+
85
+ This Agreement is entered into on 2026-06-01 between Acme Corporation
86
+ and Vendor Inc. (collectively, the "Parties").
87
+
88
+ 1. Confidentiality. Acme Corporation and Vendor Inc. agree to keep confidential
89
+ any information disclosed under this Agreement.
90
+
91
+ 2. Term. This Agreement remains in effect for two years from the
92
+ 2026-06-01.
93
+ ```
94
+
95
+ What just happened: `draft-cli` detected three bracketed placeholders
96
+ (`[Party A]`, `[Party B]`, `[Effective Date]`), mapped them to the
97
+ canonical keys `party_a`, `party_b`, `effective_date`, and substituted
98
+ in pre-canned demo values. Real runs use your own template and your own
99
+ values.
100
+
101
+ ---
102
+
103
+ ## End-to-end transcript
104
+
105
+ ```sh
106
+ $ cat > nda.md <<'EOF'
107
+ This Agreement is between [Party A] and [Party B], effective [Effective Date].
108
+ [Party A] and [Party B] agree to keep confidential information confidential.
109
+ EOF
110
+
111
+ $ draft --list-placeholders nda.md
112
+ party_a (Party A) ×2 [tier=bracket]
113
+ party_b (Party B) ×2 [tier=bracket]
114
+ effective_date (Effective Date) ×1 [tier=bracket]
115
+
116
+ $ draft nda.md --party-a "Acme" --party-b "Vendor Inc." --effective-date 2026-06-01
117
+ This Agreement is between Acme and Vendor Inc., effective 2026-06-01.
118
+ Acme and Vendor Inc. agree to keep confidential information confidential.
119
+
120
+ $ cat > deal.json <<'EOF'
121
+ {"party_a": "Acme", "party_b": "Vendor Inc.", "effective_date": "2026-06-01"}
122
+ EOF
123
+
124
+ $ draft nda.md --params deal.json --output draft.md --why
125
+ draft: substituted 3 of 3 placeholders → draft.md
126
+ why:
127
+ input = nda.md
128
+ tier = bracket
129
+ schema = (none, inferred)
130
+ placeholders = 3 distinct, 5 occurrences
131
+ resolved = 3 (0 from CLI, 3 from --params, 0 interactive, 0 default)
132
+ defaulted = 0
133
+ unresolved = 0
134
+ unmapped = 0
135
+ warnings = 0
136
+
137
+ $ draft --validate nda.md --params deal.json && echo "ok"
138
+ ok: 3 parameter(s) resolved
139
+ ok
140
+ ```
141
+
142
+ ---
143
+
144
+ ## Detection cascade
145
+
146
+ `draft-cli` finds placeholders by trying five strategies in order. The
147
+ **first non-empty tier wins** and the others are skipped.
148
+
149
+ | Tier | Strategy | When |
150
+ | ---- | -------------------- | ----------------------------------------- |
151
+ | 1 | `[Title Case]` | Default. Matches Common Paper / YC SAFE / Bonterms convention. |
152
+ | 2 | `{{Title Case}}` | Opt-in with `--syntax mustache`. |
153
+ | 3 | `.docx` highlights | Auto on `.docx` input. Yellow / green / cyan / magenta runs. |
154
+ | 4 | Heuristic dictionary | Bundled list of generic names (`Acme Corporation`, `John Doe`, `example@example.com`, etc.). Warn-only by default. |
155
+ | 5 | LLM | Last resort. Runs only when `.env` or process env configures `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, or `DRAFT_LLM_*`. |
156
+
157
+ The cascade is **deterministic through tier 4**. Tier 5 is the only
158
+ non-deterministic step and runs only when you've explicitly configured a
159
+ provider key. Pass `--no-llm` to disable it even when configured. Pass
160
+ `--no-heuristic` to skip tier 4.
161
+
162
+ See [PARAM_SCHEMA.md](PARAM_SCHEMA.md) for the full contract.
163
+
164
+ ---
165
+
166
+ ## Schema file (optional)
167
+
168
+ A sibling `<template>.params.json` lets you declare canonical keys, alias
169
+ phrases, defaults, and whether each parameter is required.
170
+
171
+ **Short form:**
172
+
173
+ ```json
174
+ {
175
+ "party_a": ["Party A", "Disclosing Party"],
176
+ "party_b": ["Party B", "Receiving Party"],
177
+ "effective_date": ["Effective Date"]
178
+ }
179
+ ```
180
+
181
+ **Long form** (gates on `_meta`):
182
+
183
+ ```json
184
+ {
185
+ "_meta": { "schema_version": 1 },
186
+ "party_a": { "aliases": ["Party A"], "required": true },
187
+ "effective_date": { "aliases": ["Effective Date"], "required": false, "default": "the date first written above" }
188
+ }
189
+ ```
190
+
191
+ With a schema, `draft-cli` substitutes **only** declared parameters and
192
+ leaves other bracketed text untouched. Without one, every detected
193
+ bracketed phrase is treated as a required parameter.
194
+
195
+ ---
196
+
197
+ ## Command reference
198
+
199
+ ```
200
+ draft <template> fill placeholders and emit the result
201
+ draft <category>/<name> pull via `template-vault get`
202
+ draft - template body on stdin
203
+ draft --demo bundled demo, no file needed
204
+ draft --list-placeholders <t> enumerate placeholders and exit
205
+ draft --validate <t> --params completeness check, no output
206
+
207
+ OPTIONS
208
+ --params FILE JSON file of param values (snake_case keys)
209
+ -o, --output PATH write to PATH (default: stdout)
210
+ --syntax bracket|mustache
211
+ -i, --interactive prompt for missing required parameters
212
+ --why structured explanation to stderr
213
+ --json machine-readable result on stdout
214
+ -q, --silent suppress all stderr (warnings, --why, notes)
215
+ --no-heuristic disable tier 4
216
+ --yes-heuristic substitute tier-4 matches without confirmation
217
+ --no-llm disable tier 5 even when env is configured
218
+ --llm assert that env is configured (fail-fast if not)
219
+ --check-llm one-token roundtrip to verify provider config
220
+ --diff show substitution table without writing output
221
+ --dictionary PATH override the bundled heuristic dictionary
222
+ --<param-name> VALUE set a parameter directly (kebab → snake_case)
223
+ -h, --help show full help
224
+ -V, --version show version
225
+ ```
226
+
227
+ Exit codes: `0` ok · `1` i/o · `2` validation · `3` template-vault failure
228
+ · `4` llm failure.
229
+
230
+ ---
231
+
232
+ ## LLM tier (env-gated, opt-in)
233
+
234
+ When tiers 1–4 all find nothing, `draft-cli` falls back to a language model
235
+ **only if** a provider key is in the environment. Read order: `.env` in
236
+ the working directory, then `process.env` (process wins).
237
+
238
+ ```sh
239
+ echo 'ANTHROPIC_API_KEY=sk-ant-…' >> .env
240
+ draft some-freeform-draft.md # tier 5 auto-runs when 1-4 empty
241
+ ```
242
+
243
+ Supported providers: Anthropic (`ANTHROPIC_API_KEY`), OpenAI
244
+ (`OPENAI_API_KEY`), or explicit (`DRAFT_LLM_PROVIDER` + `DRAFT_LLM_API_KEY`
245
+ + optional `DRAFT_LLM_MODEL`). The LLM receives template text only — no
246
+ params file, no `.env` contents, no other data. Pass `--no-llm` to disable
247
+ even when configured.
248
+
249
+ ---
250
+
251
+ ## Composability
252
+
253
+ `draft-cli` reads from stdin, writes to stdout by default, and exits with
254
+ distinct codes for each failure class. It composes with `template-vault-cli`
255
+ on the read side and `nda-review-cli` / `docx2pdf-cli` / `sign-cli` on
256
+ the write side:
257
+
258
+ ```sh
259
+ template-vault get nda/house-mutual \
260
+ | draft - --params deal-acme.json \
261
+ | nda-review review - --playbook house \
262
+ | docx2pdf - draft.pdf
263
+ ```
264
+
265
+ The `--why` and `--json` flags make every step inspectable by agents and
266
+ shell pipelines.
267
+
268
+ ---
269
+
270
+ ## Part of the contract-operations suite
271
+
272
+ `draft-cli` is one of a small set of single-purpose CLIs for contract
273
+ operations. See [cli.drbaher.com](https://cli.drbaher.com) for the suite
274
+ landing page.
275
+
276
+ - **[nda-review-cli](https://github.com/DrBaher/nda-review-cli)** —
277
+ draft, review, and negotiate NDAs against your own house playbook.
278
+ Deterministic by default; opt-in LLM augmentation.
279
+ - **[docx2pdf-cli](https://github.com/DrBaher/docx2pdf-cli)** —
280
+ honest DOCX → PDF conversion with batch processing, parallel runs,
281
+ font validation.
282
+ - **[sign-cli](https://github.com/DrBaher/sign-cli)** —
283
+ fully-offline PAdES e-signature with hash-chained audit events,
284
+ RFC 3161 timestamps.
285
+
286
+ `template-vault-cli` (a Git-backed, clause-aware package manager for
287
+ legal-document templates) is the natural upstream of `draft-cli` and
288
+ will join the suite when it ships.
289
+
290
+ ---
291
+
292
+ ## Documentation
293
+
294
+ - [GETTING_STARTED.md](GETTING_STARTED.md) — 10-minute walk-through of every flow.
295
+ - [AGENTS.md](AGENTS.md) — JSON shapes, exit codes, library use; everything an LLM agent driving `draft-cli` needs.
296
+ - [PARAM_SCHEMA.md](PARAM_SCHEMA.md) — locked v1 contract: cascade, schema file, precedence.
297
+ - [ARCHITECTURE.md](ARCHITECTURE.md) — single-file rationale, substitution model, .docx parsing.
298
+ - [FAQ.md](FAQ.md) — design questions and trade-offs.
299
+ - [SECURITY.md](SECURITY.md) — threat model and how to report a vulnerability.
300
+ - [CHANGELOG.md](CHANGELOG.md) — release notes and the v2 "Deferred" list.
301
+ - [CONTRIBUTING.md](CONTRIBUTING.md) — scope rules and release flow.
302
+
303
+ ## License
304
+
305
+ MIT. See [LICENSE](LICENSE).