@drbaher/draft-cli 0.6.0 → 0.8.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.
- package/CHANGELOG.md +98 -0
- package/PARAM_SCHEMA.md +129 -0
- package/draft-cli.mjs +395 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,104 @@ All notable changes to this project will be documented in this file. The
|
|
|
4
4
|
format is loosely based on [Keep a Changelog](https://keepachangelog.com/),
|
|
5
5
|
and the project adheres to semantic versioning once it leaves 0.x.
|
|
6
6
|
|
|
7
|
+
## 0.8.0 — 2026-05-17
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **LLM inference from a deal description** (last v2 item). New
|
|
12
|
+
`--from-deal PATH` flag reads a free-form deal description and
|
|
13
|
+
asks the configured T5 LLM provider to extract values for the
|
|
14
|
+
schema's declared placeholders:
|
|
15
|
+
```sh
|
|
16
|
+
draft nda.md --from-deal deal-notes.txt --output draft.md
|
|
17
|
+
```
|
|
18
|
+
Where `deal-notes.txt` is unstructured prose:
|
|
19
|
+
```
|
|
20
|
+
Mutual NDA between Acme Corporation (DE) and Globex (UK), effective
|
|
21
|
+
June 1, 2026, for a 2-year term.
|
|
22
|
+
```
|
|
23
|
+
The LLM is asked to fill `party_a`, `party_a_state`, `party_b`,
|
|
24
|
+
`effective_date`, etc. — only the keys already detected as
|
|
25
|
+
placeholders are extracted.
|
|
26
|
+
- **Value-resolution precedence updated:**
|
|
27
|
+
`CLI flag > --params JSON > --from-deal (LLM) > --interactive > schema default > error`.
|
|
28
|
+
CLI / --params always win, so users can fix or override anything
|
|
29
|
+
the LLM got wrong.
|
|
30
|
+
- **New public API:** `inferFromDeal(dealText, placeholders, providerCfg, { fetcher })`.
|
|
31
|
+
|
|
32
|
+
### Decisions locked (V2_BRIEFS_REMAINING Q4.1–Q4.3)
|
|
33
|
+
|
|
34
|
+
- **Q4.1 Provider:** same T5 provider config — `ANTHROPIC_API_KEY`,
|
|
35
|
+
`OPENAI_API_KEY`, or explicit `DRAFT_LLM_*`. No separate inference
|
|
36
|
+
provider; one network surface, one set of env vars.
|
|
37
|
+
- **Q4.2 Extra keys:** keys the LLM emits that aren't in the
|
|
38
|
+
detected placeholders are **warned** to stderr (not dropped
|
|
39
|
+
silently). The LLM gets a fresh list of allowed keys in the
|
|
40
|
+
prompt so this is rare in practice.
|
|
41
|
+
- **Q4.3 Auto-LLM:** `--from-deal` does **not** require an
|
|
42
|
+
explicit `--llm` flag — the inference is implicit. `--no-llm`
|
|
43
|
+
still disables it (the user can opt out of the network call).
|
|
44
|
+
|
|
45
|
+
### Notes
|
|
46
|
+
|
|
47
|
+
- `--from-deal` errors are fatal (`EXIT.LLM` for provider /
|
|
48
|
+
network / parse failures). Users with bad provider configs see
|
|
49
|
+
the issue immediately rather than silently running with no
|
|
50
|
+
inferred values.
|
|
51
|
+
- Bundle mode (v0.7.0) does not yet thread `--from-deal` through
|
|
52
|
+
per-template inference. Deferred to a follow-up; the shared
|
|
53
|
+
parameter resolution makes the single-doc API already useful
|
|
54
|
+
for bundles via `--params`.
|
|
55
|
+
|
|
56
|
+
## 0.7.0 — 2026-05-17
|
|
57
|
+
|
|
58
|
+
### Added
|
|
59
|
+
|
|
60
|
+
- **Multi-document bundles.** `draft --bundle <bundle.json>` reads a
|
|
61
|
+
bundle definition and fills multiple templates with the same set of
|
|
62
|
+
parameter values in one invocation:
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"_meta": { "schema_version": 1 },
|
|
66
|
+
"outputs": [
|
|
67
|
+
{ "template": "msa/v3.md", "output": "out/msa.md" },
|
|
68
|
+
{ "template": "order-form/v3.md", "output": "out/order-form.md" }
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
Each template runs through detection independently. Placeholders
|
|
73
|
+
across templates are unioned by key (so a key declared in any
|
|
74
|
+
template's schema applies to all — Q3.3 locked). Resolution,
|
|
75
|
+
typed-parameter normalization, and computed values all run once on
|
|
76
|
+
the union. Each output is then substituted using its own
|
|
77
|
+
template/tier and written to its own path. `parties.json` refs
|
|
78
|
+
(v0.6.0) resolve inside bundle entries too.
|
|
79
|
+
- **Schema-union semantics.** A key declared/detected in any bundle
|
|
80
|
+
template applies to every template in the bundle. First-occurrence
|
|
81
|
+
metadata wins; resolved values flow to all templates that reference
|
|
82
|
+
the same key.
|
|
83
|
+
- **`.docx` bundle entries** round-trip through `substituteDocxXml`
|
|
84
|
+
when the entry's `output` path has the `.docx` extension. Same
|
|
85
|
+
runs/styles preservation as single-doc `.docx` mode.
|
|
86
|
+
- **New public API:** `loadBundle(path)`, `cmdBundle(opts, bundle,
|
|
87
|
+
paramsObj, envObj, io)`.
|
|
88
|
+
|
|
89
|
+
### Decisions locked (V2_BRIEFS_REMAINING Q3.1–Q3.3)
|
|
90
|
+
|
|
91
|
+
- **Q3.1 Bundle file format:** JSON object with `outputs` array of
|
|
92
|
+
`{template, output}` pairs. Each entry has its own output path,
|
|
93
|
+
enabling per-doc overrides without inventing a custom DSL.
|
|
94
|
+
- **Q3.2 Partial-failure policy:** abort-all. Any pre-write error
|
|
95
|
+
(no detection in an entry, missing required param across the
|
|
96
|
+
union, type / computed / ref failure, positional mismatch, schema
|
|
97
|
+
orphan) exits 4 before any file is written. Write failures
|
|
98
|
+
mid-bundle exit 1; earlier successful writes are not rolled back
|
|
99
|
+
(best-effort atomicity at the filesystem boundary).
|
|
100
|
+
- **Q3.3 Schema union semantics:** keys declared in any template's
|
|
101
|
+
schema (or detected as canonical-key matches without a schema)
|
|
102
|
+
apply across the bundle. Same value resolves into every template
|
|
103
|
+
that references the key.
|
|
104
|
+
|
|
7
105
|
## 0.6.0 — 2026-05-16
|
|
8
106
|
|
|
9
107
|
### Added
|
package/PARAM_SCHEMA.md
CHANGED
|
@@ -451,6 +451,135 @@ substitution always uses string output.
|
|
|
451
451
|
Programmatic API: `loadParties(path)`, `resolveRef(value, parties)`,
|
|
452
452
|
`resolveRefs(resolved, sources, parties)`.
|
|
453
453
|
|
|
454
|
+
### Multi-document bundles (v0.7.0, opt-in)
|
|
455
|
+
|
|
456
|
+
`draft --bundle <bundle.json>` reads a bundle definition and fills
|
|
457
|
+
multiple templates with one shared set of parameter values:
|
|
458
|
+
|
|
459
|
+
```json
|
|
460
|
+
{
|
|
461
|
+
"_meta": { "schema_version": 1 },
|
|
462
|
+
"outputs": [
|
|
463
|
+
{ "template": "msa/v3.md", "output": "out/msa.md" },
|
|
464
|
+
{ "template": "order-form/v3.md", "output": "out/order-form.md" },
|
|
465
|
+
{ "template": "dpa/v2.docx", "output": "out/dpa.docx" }
|
|
466
|
+
]
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
```sh
|
|
471
|
+
draft --bundle deal.bundle.json --params deal.json --parties parties.json
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
**Q3.1 locked:** the bundle file is a JSON object with an `outputs`
|
|
475
|
+
array of `{template, output}` pairs. Each entry has its own
|
|
476
|
+
`template` (filesystem path or `template-vault get` ref) and own
|
|
477
|
+
`output` path. No alternative shorter DSL — JSON is unambiguous and
|
|
478
|
+
extensible.
|
|
479
|
+
|
|
480
|
+
**Q3.2 locked:** abort-all. Any pre-write error (no detection in an
|
|
481
|
+
entry, missing required param across the union, type validation
|
|
482
|
+
failure, computed-value failure, ref-resolution failure, positional
|
|
483
|
+
mismatch, schema orphan) returns exit 4 **before any file is
|
|
484
|
+
written**. The bundle either writes all `outputs` or writes none.
|
|
485
|
+
Filesystem write errors mid-bundle exit 1; earlier successful
|
|
486
|
+
writes are not rolled back (best-effort atomicity at the filesystem
|
|
487
|
+
boundary).
|
|
488
|
+
|
|
489
|
+
**Q3.3 locked:** schema union. A key declared in any template's
|
|
490
|
+
schema, or detected as a canonical-key match without a schema,
|
|
491
|
+
applies across the entire bundle. The same resolved value flows to
|
|
492
|
+
every template that references the key. First-occurrence metadata
|
|
493
|
+
wins (`type`, `format`, `currency`, `computed`, `positions`, etc.);
|
|
494
|
+
templates with richer aliases for the same key contribute their
|
|
495
|
+
aliases for detection in their own body but don't redefine the key.
|
|
496
|
+
|
|
497
|
+
**Per-template detection independence:** each bundle entry runs the
|
|
498
|
+
full T1–T5 cascade against its own body. Different entries can land
|
|
499
|
+
on different tiers (e.g. MSA on T1 brackets, DPA on T3 highlights).
|
|
500
|
+
Positional addressing on T1/T2 still works per entry.
|
|
501
|
+
|
|
502
|
+
**`.docx` entries** with a `.docx` output path round-trip via
|
|
503
|
+
`substituteDocxXml` + `writeDocxBuffer`, preserving runs/styles.
|
|
504
|
+
Mixing text and `.docx` entries in the same bundle works.
|
|
505
|
+
|
|
506
|
+
**`parties.json` refs** (v0.6.0) resolve inside bundles too — load
|
|
507
|
+
the same parties file once via `--parties PATH` (or the CWD
|
|
508
|
+
default), and ref strings in any bundle template's schema default
|
|
509
|
+
or in shared `--params` expand against it.
|
|
510
|
+
|
|
511
|
+
**`--json`** for bundles emits a structured result listing each
|
|
512
|
+
entry's template, output path, and tier, plus the union of resolved
|
|
513
|
+
keys and their sources.
|
|
514
|
+
|
|
515
|
+
Programmatic API: `loadBundle(path)` parses + validates; `cmdBundle`
|
|
516
|
+
runs the orchestration with the same IO contract as `cmdDraft`.
|
|
517
|
+
|
|
518
|
+
### LLM inference from a deal description (v0.8.0, opt-in)
|
|
519
|
+
|
|
520
|
+
`--from-deal PATH` reads a free-form deal description and asks the
|
|
521
|
+
configured T5 LLM provider to extract values for the schema's
|
|
522
|
+
declared placeholders. The inverse of T5 detection — instead of
|
|
523
|
+
inferring *where* placeholders are in a template, infer *what
|
|
524
|
+
values* they should take from the deal prose:
|
|
525
|
+
|
|
526
|
+
```sh
|
|
527
|
+
draft nda.md --from-deal deal-notes.txt --output draft.md
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
```
|
|
531
|
+
# deal-notes.txt
|
|
532
|
+
Mutual NDA between Acme Corporation (DE) and Globex (UK),
|
|
533
|
+
effective June 1, 2026, for a 2-year term.
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
Then `[Party A]` → `Acme Corporation`, `[Effective Date]` →
|
|
537
|
+
`June 1, 2026`, etc., without any `--party-a` / `--effective-date`
|
|
538
|
+
flags.
|
|
539
|
+
|
|
540
|
+
**Value-resolution precedence** with `--from-deal`:
|
|
541
|
+
|
|
542
|
+
```
|
|
543
|
+
CLI flag > --params JSON > --from-deal (LLM) > --interactive > schema default > error
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
CLI / --params always win, so users can fix or override anything the
|
|
547
|
+
LLM got wrong without re-running inference.
|
|
548
|
+
|
|
549
|
+
**Q4.1 locked:** same T5 provider config (`ANTHROPIC_API_KEY`,
|
|
550
|
+
`OPENAI_API_KEY`, or explicit `DRAFT_LLM_*`). One network surface,
|
|
551
|
+
one set of env vars.
|
|
552
|
+
|
|
553
|
+
**Q4.2 locked:** extra keys (LLM emits keys not in the detected
|
|
554
|
+
placeholder list) are **warned** to stderr, not silently dropped.
|
|
555
|
+
The LLM gets the allowed-key list in the prompt so this is rare in
|
|
556
|
+
practice.
|
|
557
|
+
|
|
558
|
+
**Q4.3 locked:** `--from-deal` does **not** require explicit
|
|
559
|
+
`--llm` — the inference is implicit when the flag is present.
|
|
560
|
+
`--no-llm` still disables the inference call (the user can opt
|
|
561
|
+
out of the network).
|
|
562
|
+
|
|
563
|
+
**Provider missing:** if no LLM provider is configured (no
|
|
564
|
+
`ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `DRAFT_LLM_*` in env),
|
|
565
|
+
`--from-deal` errors immediately with `EXIT.LLM` (exit 5) and a
|
|
566
|
+
clear message. Same for network / HTTP errors / non-JSON LLM
|
|
567
|
+
responses.
|
|
568
|
+
|
|
569
|
+
**Resolution interaction with typed parameters:** inferred values
|
|
570
|
+
go through the same typed-normalization step as user-supplied
|
|
571
|
+
values. So an LLM that returns `"June 1, 2026"` for a `type: date`
|
|
572
|
+
parameter with `format: yyyy-MM-d` gets normalized to `2026-06-1`
|
|
573
|
+
before substitution.
|
|
574
|
+
|
|
575
|
+
**Bundle mode (v0.7.0) interaction:** bundles do not currently
|
|
576
|
+
thread `--from-deal` through per-template inference. The shared
|
|
577
|
+
parameter resolution already accepts `--params` JSON, which is the
|
|
578
|
+
simpler structured-data path for bundle workflows. Deferred to a
|
|
579
|
+
future release.
|
|
580
|
+
|
|
581
|
+
Programmatic API: `inferFromDeal(dealText, placeholders, providerCfg, { fetcher })`.
|
|
582
|
+
|
|
454
583
|
### Orphan handling (Q4 locked)
|
|
455
584
|
|
|
456
585
|
Schema declares a key whose alias list matches no detected phrase →
|
package/draft-cli.mjs
CHANGED
|
@@ -70,7 +70,7 @@ import { fileURLToPath } from "node:url";
|
|
|
70
70
|
*/
|
|
71
71
|
|
|
72
72
|
/** @type {string} */
|
|
73
|
-
export const VERSION = "0.
|
|
73
|
+
export const VERSION = "0.8.0";
|
|
74
74
|
|
|
75
75
|
// ─── EXIT CODES ─────────────────────────────────────────────────────────────
|
|
76
76
|
/**
|
|
@@ -278,6 +278,8 @@ export function parseArgs(argv) {
|
|
|
278
278
|
if (a === "--diff") { opts.diff = true; continue; }
|
|
279
279
|
if (a === "--params") { opts.params = argv[++i]; continue; }
|
|
280
280
|
if (a === "--parties") { opts.parties = argv[++i]; continue; }
|
|
281
|
+
if (a === "--bundle") { opts.bundle = argv[++i]; continue; }
|
|
282
|
+
if (a === "--from-deal") { opts.fromDeal = argv[++i]; continue; }
|
|
281
283
|
if (a === "--output" || a === "-o") { opts.output = argv[++i]; continue; }
|
|
282
284
|
if (a === "--syntax") {
|
|
283
285
|
const v = argv[++i];
|
|
@@ -807,6 +809,95 @@ ${body.slice(0, 12000)}`;
|
|
|
807
809
|
return out;
|
|
808
810
|
}
|
|
809
811
|
|
|
812
|
+
/**
|
|
813
|
+
* v2 #4: LLM inference from a free-form deal description.
|
|
814
|
+
*
|
|
815
|
+
* Takes the prose deal description (the user's notes about parties, dates,
|
|
816
|
+
* amounts, etc.) and asks the configured T5 LLM provider to extract values
|
|
817
|
+
* for the placeholders the cascade has already detected. Returns
|
|
818
|
+
* `{values, extraKeys, warnings}`:
|
|
819
|
+
*
|
|
820
|
+
* - values: `{key: string}` for every placeholder key the LLM filled
|
|
821
|
+
* - extraKeys: any keys the LLM emitted that aren't in the placeholders list (Q4.2 → warn)
|
|
822
|
+
* - warnings: human-readable messages for malformed entries
|
|
823
|
+
*
|
|
824
|
+
* Throws on missing provider config, missing `fetch`, network/HTTP error, or
|
|
825
|
+
* non-JSON LLM response — same failure boundaries as `detectLlm`.
|
|
826
|
+
*
|
|
827
|
+
* @param {string} dealText — free-form deal description
|
|
828
|
+
* @param {Placeholder[]} placeholders — the post-detection placeholder list
|
|
829
|
+
* @param {ReturnType<llmProviderFromEnv>} providerCfg
|
|
830
|
+
* @param {{ fetcher?: typeof fetch | null }} [opts]
|
|
831
|
+
* @returns {Promise<{ values: Object<string,string>, extraKeys: string[], warnings: string[] }>}
|
|
832
|
+
*/
|
|
833
|
+
export async function inferFromDeal(dealText, placeholders, providerCfg, { fetcher = (typeof fetch !== "undefined" ? fetch : null) } = {}) {
|
|
834
|
+
if (!fetcher) {
|
|
835
|
+
const e = new Error("fetch is not available; Node 18+ is required for --from-deal");
|
|
836
|
+
e.exitCode = EXIT.LLM;
|
|
837
|
+
throw e;
|
|
838
|
+
}
|
|
839
|
+
if (!providerCfg) {
|
|
840
|
+
const e = new Error("--from-deal requires an LLM provider; set ANTHROPIC_API_KEY / OPENAI_API_KEY / DRAFT_LLM_* in .env");
|
|
841
|
+
e.exitCode = EXIT.LLM;
|
|
842
|
+
throw e;
|
|
843
|
+
}
|
|
844
|
+
const wantedKeys = placeholders.map((p) => ({
|
|
845
|
+
key: p.key,
|
|
846
|
+
aliases: (p.aliases || []).slice(0, 4),
|
|
847
|
+
first_seen_as: p.first_seen_as,
|
|
848
|
+
}));
|
|
849
|
+
if (wantedKeys.length === 0) {
|
|
850
|
+
return { values: {}, extraKeys: [], warnings: [] };
|
|
851
|
+
}
|
|
852
|
+
const fieldList = wantedKeys.map((w) =>
|
|
853
|
+
` - ${w.key} (template placeholder: "${w.first_seen_as}"${w.aliases.length > 1 ? `; aliases: ${w.aliases.join(", ")}` : ""})`
|
|
854
|
+
).join("\n");
|
|
855
|
+
const prompt = `You are filling parameters for a legal-document drafting tool.
|
|
856
|
+
A user has written prose describing a deal. Extract values for the following
|
|
857
|
+
fields from the deal description. Output JSON ONLY in this exact shape, with
|
|
858
|
+
no commentary:
|
|
859
|
+
|
|
860
|
+
{"values":{"<key>":"<extracted_value>",...}}
|
|
861
|
+
|
|
862
|
+
If a field can't be confidently extracted from the description, omit it (do
|
|
863
|
+
NOT guess). Do not invent additional fields not in the list. Match the deal's
|
|
864
|
+
language verbatim — don't reformat dates, currencies, or names.
|
|
865
|
+
|
|
866
|
+
FIELDS:
|
|
867
|
+
${fieldList}
|
|
868
|
+
|
|
869
|
+
DEAL DESCRIPTION:
|
|
870
|
+
${dealText.slice(0, 12000)}`;
|
|
871
|
+
const raw = await callLlm(providerCfg, prompt, fetcher);
|
|
872
|
+
let parsed;
|
|
873
|
+
try {
|
|
874
|
+
const jsonMatch = raw.match(/\{[\s\S]*\}/);
|
|
875
|
+
parsed = JSON.parse(jsonMatch ? jsonMatch[0] : raw);
|
|
876
|
+
} catch {
|
|
877
|
+
const e = new Error(`LLM returned non-JSON response for --from-deal`);
|
|
878
|
+
e.exitCode = EXIT.LLM;
|
|
879
|
+
throw e;
|
|
880
|
+
}
|
|
881
|
+
const rawValues = (parsed && typeof parsed.values === "object" && parsed.values) ? parsed.values : {};
|
|
882
|
+
const knownKeys = new Set(placeholders.map((p) => p.key));
|
|
883
|
+
const values = {};
|
|
884
|
+
const extraKeys = [];
|
|
885
|
+
const warnings = [];
|
|
886
|
+
for (const [k, v] of Object.entries(rawValues)) {
|
|
887
|
+
if (!knownKeys.has(k)) {
|
|
888
|
+
extraKeys.push(k);
|
|
889
|
+
continue;
|
|
890
|
+
}
|
|
891
|
+
if (v === null || v === undefined) continue;
|
|
892
|
+
if (typeof v !== "string" && typeof v !== "number") {
|
|
893
|
+
warnings.push(`--from-deal: value for "${k}" was ${typeof v}, expected string; skipped`);
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
values[k] = String(v);
|
|
897
|
+
}
|
|
898
|
+
return { values, extraKeys, warnings };
|
|
899
|
+
}
|
|
900
|
+
|
|
810
901
|
async function callLlm(cfg, prompt, fetcher) {
|
|
811
902
|
if (cfg.provider === "anthropic") {
|
|
812
903
|
const r = await fetcher("https://api.anthropic.com/v1/messages", {
|
|
@@ -1375,6 +1466,79 @@ export function resolveRefs(resolved, sources, parties) {
|
|
|
1375
1466
|
return { ok: errors.length === 0, errors };
|
|
1376
1467
|
}
|
|
1377
1468
|
|
|
1469
|
+
/**
|
|
1470
|
+
* Load and validate a bundle definition (v2 #6). Bundles describe
|
|
1471
|
+
* multiple templates that should be filled with the same set of
|
|
1472
|
+
* parameter values in one invocation:
|
|
1473
|
+
*
|
|
1474
|
+
* {
|
|
1475
|
+
* "_meta": { "schema_version": 1 },
|
|
1476
|
+
* "outputs": [
|
|
1477
|
+
* { "template": "msa/v3.md", "output": "out/msa.md" },
|
|
1478
|
+
* { "template": "order-form/v3.md", "output": "out/order-form.md" }
|
|
1479
|
+
* ]
|
|
1480
|
+
* }
|
|
1481
|
+
*
|
|
1482
|
+
* Returns the parsed bundle. Throws on missing file, invalid JSON, no
|
|
1483
|
+
* `outputs` array, empty `outputs`, missing `template`/`output` on an
|
|
1484
|
+
* entry, or duplicate output paths.
|
|
1485
|
+
*
|
|
1486
|
+
* @param {string} path
|
|
1487
|
+
* @returns {{ outputs: Array<{ template: string, output: string }> }}
|
|
1488
|
+
* @throws {Error} with `.exitCode = EXIT.IO`
|
|
1489
|
+
*/
|
|
1490
|
+
export function loadBundle(path) {
|
|
1491
|
+
if (!existsSync(path)) {
|
|
1492
|
+
const e = new Error(`bundle file not found: ${path}`);
|
|
1493
|
+
e.exitCode = EXIT.IO;
|
|
1494
|
+
throw e;
|
|
1495
|
+
}
|
|
1496
|
+
let parsed;
|
|
1497
|
+
try {
|
|
1498
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
1499
|
+
} catch (err) {
|
|
1500
|
+
const e = new Error(`could not parse bundle ${path}: ${err.message}`);
|
|
1501
|
+
e.exitCode = EXIT.IO;
|
|
1502
|
+
throw e;
|
|
1503
|
+
}
|
|
1504
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1505
|
+
const e = new Error(`bundle ${path} must be a JSON object`);
|
|
1506
|
+
e.exitCode = EXIT.IO;
|
|
1507
|
+
throw e;
|
|
1508
|
+
}
|
|
1509
|
+
if (!Array.isArray(parsed.outputs) || parsed.outputs.length === 0) {
|
|
1510
|
+
const e = new Error(`bundle ${path}: missing or empty "outputs" array`);
|
|
1511
|
+
e.exitCode = EXIT.IO;
|
|
1512
|
+
throw e;
|
|
1513
|
+
}
|
|
1514
|
+
const seenOutputs = new Set();
|
|
1515
|
+
for (let i = 0; i < parsed.outputs.length; i++) {
|
|
1516
|
+
const o = parsed.outputs[i];
|
|
1517
|
+
if (!o || typeof o !== "object" || Array.isArray(o)) {
|
|
1518
|
+
const e = new Error(`bundle ${path}: outputs[${i}] must be an object`);
|
|
1519
|
+
e.exitCode = EXIT.IO;
|
|
1520
|
+
throw e;
|
|
1521
|
+
}
|
|
1522
|
+
if (typeof o.template !== "string" || !o.template) {
|
|
1523
|
+
const e = new Error(`bundle ${path}: outputs[${i}].template must be a non-empty string`);
|
|
1524
|
+
e.exitCode = EXIT.IO;
|
|
1525
|
+
throw e;
|
|
1526
|
+
}
|
|
1527
|
+
if (typeof o.output !== "string" || !o.output) {
|
|
1528
|
+
const e = new Error(`bundle ${path}: outputs[${i}].output must be a non-empty string`);
|
|
1529
|
+
e.exitCode = EXIT.IO;
|
|
1530
|
+
throw e;
|
|
1531
|
+
}
|
|
1532
|
+
if (seenOutputs.has(o.output)) {
|
|
1533
|
+
const e = new Error(`bundle ${path}: outputs[${i}].output "${o.output}" is duplicated`);
|
|
1534
|
+
e.exitCode = EXIT.IO;
|
|
1535
|
+
throw e;
|
|
1536
|
+
}
|
|
1537
|
+
seenOutputs.add(o.output);
|
|
1538
|
+
}
|
|
1539
|
+
return { outputs: parsed.outputs.map(o => ({ template: o.template, output: o.output })) };
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1378
1542
|
/**
|
|
1379
1543
|
* Resolve a value for every placeholder using the locked precedence chain:
|
|
1380
1544
|
* CLI flag > `--params` JSON > `--interactive` prompt > schema default >
|
|
@@ -1386,7 +1550,7 @@ export function resolveRefs(resolved, sources, parties) {
|
|
|
1386
1550
|
* @param {{ prompter?: (p: Placeholder) => Promise<string|null> }} [io]
|
|
1387
1551
|
* @returns {Promise<ResolvedValues>}
|
|
1388
1552
|
*/
|
|
1389
|
-
export async function resolveValues(placeholders, opts, paramsObj, { prompter = nodePrompter } = {}) {
|
|
1553
|
+
export async function resolveValues(placeholders, opts, paramsObj, { prompter = nodePrompter, inferred = null } = {}) {
|
|
1390
1554
|
const resolved = {};
|
|
1391
1555
|
const missing = [];
|
|
1392
1556
|
const sources = {};
|
|
@@ -1401,6 +1565,12 @@ export async function resolveValues(placeholders, opts, paramsObj, { prompter =
|
|
|
1401
1565
|
sources[p.key] = "params";
|
|
1402
1566
|
continue;
|
|
1403
1567
|
}
|
|
1568
|
+
// v2 #4: --from-deal LLM-inferred values, between --params and --interactive.
|
|
1569
|
+
if (inferred && Object.prototype.hasOwnProperty.call(inferred, p.key)) {
|
|
1570
|
+
resolved[p.key] = String(inferred[p.key]);
|
|
1571
|
+
sources[p.key] = "deal-llm";
|
|
1572
|
+
continue;
|
|
1573
|
+
}
|
|
1404
1574
|
if (opts.interactive) {
|
|
1405
1575
|
const v = await prompter(p);
|
|
1406
1576
|
if (v !== null && v !== undefined && v !== "") {
|
|
@@ -2015,7 +2185,7 @@ export async function cmdListPlaceholders(opts, input, schema, envObj, { fetcher
|
|
|
2015
2185
|
return EXIT.OK;
|
|
2016
2186
|
}
|
|
2017
2187
|
|
|
2018
|
-
export async function cmdValidate(opts, input, schema, paramsObj, envObj, { fetcher, out, err, parties = null } = {}) {
|
|
2188
|
+
export async function cmdValidate(opts, input, schema, paramsObj, envObj, { fetcher, out, err, parties = null, dealText = null } = {}) {
|
|
2019
2189
|
const result = await runCascade(input, opts, schema, envObj, { fetcher });
|
|
2020
2190
|
if (result.tier === "none") {
|
|
2021
2191
|
err.write(paint("error: no placeholders detected by any tier\n", "red", err));
|
|
@@ -2035,7 +2205,24 @@ export async function cmdValidate(opts, input, schema, paramsObj, envObj, { fetc
|
|
|
2035
2205
|
}
|
|
2036
2206
|
return EXIT.VALIDATION;
|
|
2037
2207
|
}
|
|
2038
|
-
|
|
2208
|
+
// v2 #4: --from-deal LLM inference (when dealText is present and
|
|
2209
|
+
// --no-llm not set). Provider config comes from env. Errors are fatal
|
|
2210
|
+
// to keep the user from running with partial inferred values.
|
|
2211
|
+
let inferred = null;
|
|
2212
|
+
if (dealText && !opts.noLlm) {
|
|
2213
|
+
try {
|
|
2214
|
+
const r = await inferFromDeal(dealText, result.placeholders, llmProviderFromEnv(envObj), { fetcher });
|
|
2215
|
+
inferred = r.values;
|
|
2216
|
+
for (const k of r.extraKeys) {
|
|
2217
|
+
err.write(paint(`warning: --from-deal LLM emitted unknown key "${k}" (not in template/schema)\n`, "yellow", err));
|
|
2218
|
+
}
|
|
2219
|
+
for (const w of r.warnings) err.write(paint(`warning: ${w}\n`, "yellow", err));
|
|
2220
|
+
} catch (e) {
|
|
2221
|
+
err.write(paint(`error: ${e.message}\n`, "red", err));
|
|
2222
|
+
return e.exitCode || EXIT.LLM;
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
const { resolved, missing, sources } = await resolveValues(result.placeholders, opts, paramsObj, { inferred });
|
|
2039
2226
|
if (missing.length > 0) {
|
|
2040
2227
|
printMissing(missing, err);
|
|
2041
2228
|
if (opts.json) {
|
|
@@ -2096,7 +2283,7 @@ export async function cmdValidate(opts, input, schema, paramsObj, envObj, { fetc
|
|
|
2096
2283
|
return EXIT.OK;
|
|
2097
2284
|
}
|
|
2098
2285
|
|
|
2099
|
-
export async function cmdDraft(opts, input, schema, paramsObj, envObj, { fetcher, out, err, parties = null } = {}) {
|
|
2286
|
+
export async function cmdDraft(opts, input, schema, paramsObj, envObj, { fetcher, out, err, parties = null, dealText = null } = {}) {
|
|
2100
2287
|
const result = await runCascade(input, opts, schema, envObj, { fetcher });
|
|
2101
2288
|
if (result.tier === "none") {
|
|
2102
2289
|
const hasProvider = Boolean(llmProviderFromEnv(envObj));
|
|
@@ -2147,7 +2334,25 @@ export async function cmdDraft(opts, input, schema, paramsObj, envObj, { fetcher
|
|
|
2147
2334
|
return EXIT.VALIDATION;
|
|
2148
2335
|
}
|
|
2149
2336
|
|
|
2150
|
-
|
|
2337
|
+
// v2 #4: --from-deal LLM inference (when dealText is present and
|
|
2338
|
+
// --no-llm not set). Provider config comes from env. Errors are fatal
|
|
2339
|
+
// to keep the user from running with partial inferred values.
|
|
2340
|
+
let inferred = null;
|
|
2341
|
+
if (dealText && !opts.noLlm) {
|
|
2342
|
+
try {
|
|
2343
|
+
const r = await inferFromDeal(dealText, result.placeholders, llmProviderFromEnv(envObj), { fetcher });
|
|
2344
|
+
inferred = r.values;
|
|
2345
|
+
for (const k of r.extraKeys) {
|
|
2346
|
+
err.write(paint(`warning: --from-deal LLM emitted unknown key "${k}" (not in template/schema)\n`, "yellow", err));
|
|
2347
|
+
}
|
|
2348
|
+
for (const w of r.warnings) err.write(paint(`warning: ${w}\n`, "yellow", err));
|
|
2349
|
+
} catch (e) {
|
|
2350
|
+
err.write(paint(`error: ${e.message}\n`, "red", err));
|
|
2351
|
+
return e.exitCode || EXIT.LLM;
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
const { resolved, missing, sources } = await resolveValues(result.placeholders, opts, paramsObj, { inferred });
|
|
2151
2356
|
// Footgun guard: flag --typo'd-key VALUE that didn't match any detected
|
|
2152
2357
|
// placeholder. Without this warning, a typo'd flag is silently dropped and
|
|
2153
2358
|
// the user sees only a "missing required" error without the connection.
|
|
@@ -2282,6 +2487,151 @@ export async function cmdDraft(opts, input, schema, paramsObj, envObj, { fetcher
|
|
|
2282
2487
|
return EXIT.OK;
|
|
2283
2488
|
}
|
|
2284
2489
|
|
|
2490
|
+
/**
|
|
2491
|
+
* cmdBundle — orchestrate filling multiple templates with one shared
|
|
2492
|
+
* parameter set (v2 #6). For each bundle entry:
|
|
2493
|
+
* 1. resolveInput + loadSchema (per template)
|
|
2494
|
+
* 2. runCascade (per template)
|
|
2495
|
+
* 3. union placeholders by key
|
|
2496
|
+
* Then resolve values once across the union (CLI/--params/interactive/
|
|
2497
|
+
* default), run typed-param normalization + computed values, and write
|
|
2498
|
+
* each output. Q3.2 locked: any pre-write error (no-detection in an
|
|
2499
|
+
* entry, missing required param across the union, type / computed
|
|
2500
|
+
* failure) aborts the whole bundle before any file is written.
|
|
2501
|
+
*
|
|
2502
|
+
* @param {Object} opts
|
|
2503
|
+
* @param {{outputs: Array<{template: string, output: string}>}} bundle
|
|
2504
|
+
* @param {Object} paramsObj
|
|
2505
|
+
* @param {Object} envObj
|
|
2506
|
+
* @returns {Promise<number>} exit code
|
|
2507
|
+
*/
|
|
2508
|
+
export async function cmdBundle(opts, bundle, paramsObj, envObj, { fetcher, out, err, spawner, stdinReader, parties = null } = {}) {
|
|
2509
|
+
// Phase 1: load each template + schema, run detection.
|
|
2510
|
+
const entries = [];
|
|
2511
|
+
for (let i = 0; i < bundle.outputs.length; i++) {
|
|
2512
|
+
const o = bundle.outputs[i];
|
|
2513
|
+
let input, schema, cascade;
|
|
2514
|
+
try {
|
|
2515
|
+
input = await resolveInput(o.template, { spawner, stdinReader });
|
|
2516
|
+
schema = loadSchema(input.path);
|
|
2517
|
+
} catch (e) {
|
|
2518
|
+
err.write(paint(`error: bundle entry ${i} "${o.template}": ${e.message}\n`, "red", err));
|
|
2519
|
+
return e.exitCode || EXIT.IO;
|
|
2520
|
+
}
|
|
2521
|
+
cascade = await runCascade(input, opts, schema, envObj, { fetcher });
|
|
2522
|
+
if (cascade.tier === "none") {
|
|
2523
|
+
err.write(paint(`error: bundle entry ${i} "${o.template}": no placeholders detected by any tier\n`, "red", err));
|
|
2524
|
+
return EXIT.VALIDATION;
|
|
2525
|
+
}
|
|
2526
|
+
// v2 #7 positional errors per template — abort early.
|
|
2527
|
+
if (cascade.positional_errors && cascade.positional_errors.length > 0) {
|
|
2528
|
+
for (const pe of cascade.positional_errors) {
|
|
2529
|
+
err.write(paint(`error: bundle entry ${i} "${o.template}" positional placeholder "${pe.key}": ${pe.reason}\n`, "red", err));
|
|
2530
|
+
}
|
|
2531
|
+
return EXIT.VALIDATION;
|
|
2532
|
+
}
|
|
2533
|
+
// Orphan check per template (schema declares something not detected here).
|
|
2534
|
+
const orphans = findOrphans(schema, cascade.placeholders, cascade.detected_schema_keys);
|
|
2535
|
+
if (orphans.length > 0) {
|
|
2536
|
+
for (const oo of orphans) {
|
|
2537
|
+
err.write(paint(`error: bundle entry ${i} "${o.template}" schema declares "${oo.key}" but no matching phrase was detected by tier '${cascade.tier}'.\n`, "red", err));
|
|
2538
|
+
}
|
|
2539
|
+
return EXIT.VALIDATION;
|
|
2540
|
+
}
|
|
2541
|
+
entries.push({ output: o.output, input, schema, cascade });
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
// Phase 2: union placeholders by key. Q3.3 locked: union semantics —
|
|
2545
|
+
// a key declared/detected in any template applies to all. First
|
|
2546
|
+
// occurrence's metadata wins (required, default, type, format, etc.);
|
|
2547
|
+
// a per-template later occurrence may have richer aliases but we keep
|
|
2548
|
+
// the first canonical entry.
|
|
2549
|
+
const unionPlaceholders = [];
|
|
2550
|
+
const seenKeys = new Set();
|
|
2551
|
+
for (const e of entries) {
|
|
2552
|
+
for (const p of e.cascade.placeholders) {
|
|
2553
|
+
if (seenKeys.has(p.key)) continue;
|
|
2554
|
+
seenKeys.add(p.key);
|
|
2555
|
+
unionPlaceholders.push(p);
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
// Phase 3: shared value resolution + footgun guard.
|
|
2560
|
+
const { resolved, missing, sources } = await resolveValues(unionPlaceholders, opts, paramsObj);
|
|
2561
|
+
const declaredKeys = new Set(unionPlaceholders.map((p) => p.key));
|
|
2562
|
+
const unusedFlags = Object.keys(opts.paramFlags).filter((k) => !declaredKeys.has(k));
|
|
2563
|
+
for (const u of unusedFlags) {
|
|
2564
|
+
err.write(paint(`warning: flag --${u.replace(/_/g, "-")} did not match any placeholder in any bundle template (possible typo?)\n`, "yellow", err));
|
|
2565
|
+
}
|
|
2566
|
+
if (missing.length > 0) {
|
|
2567
|
+
printMissing(missing, err);
|
|
2568
|
+
return EXIT.VALIDATION;
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
// v2 #5: parties.json refs resolve across the union before typed
|
|
2572
|
+
// normalization (same order as cmdDraft / cmdValidate).
|
|
2573
|
+
const refCheck = resolveRefs(resolved, sources, parties);
|
|
2574
|
+
if (!refCheck.ok) {
|
|
2575
|
+
for (const re of refCheck.errors) {
|
|
2576
|
+
err.write(paint(`error: parties reference failed for "${re.key}": ${re.message}\n`, "red", err));
|
|
2577
|
+
}
|
|
2578
|
+
return EXIT.VALIDATION;
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2581
|
+
// Phase 4: typed-parameter + computed pipelines (same as cmdDraft).
|
|
2582
|
+
const typeCheck = normalizeTypedValues(unionPlaceholders, resolved);
|
|
2583
|
+
if (!typeCheck.ok) {
|
|
2584
|
+
for (const te of typeCheck.errors) {
|
|
2585
|
+
err.write(paint(`error: type validation failed for "${te.key}": ${te.message}\n`, "red", err));
|
|
2586
|
+
}
|
|
2587
|
+
return EXIT.VALIDATION;
|
|
2588
|
+
}
|
|
2589
|
+
const computeCheck = computeValues(unionPlaceholders, resolved);
|
|
2590
|
+
if (!computeCheck.ok) {
|
|
2591
|
+
for (const ce of computeCheck.errors) {
|
|
2592
|
+
err.write(paint(`error: computed value failed for "${ce.key}": ${ce.message}\n`, "red", err));
|
|
2593
|
+
}
|
|
2594
|
+
return EXIT.VALIDATION;
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
// Phase 5: substitute per template + write. Q3.2: any write failure
|
|
2598
|
+
// exits with EXIT.IO; earlier successful writes are NOT rolled back
|
|
2599
|
+
// (atomicity at the filesystem is best-effort).
|
|
2600
|
+
for (let i = 0; i < entries.length; i++) {
|
|
2601
|
+
const e = entries[i];
|
|
2602
|
+
const outputText = substitute(e.input.body, e.cascade.placeholders, resolved, e.cascade.tier);
|
|
2603
|
+
try {
|
|
2604
|
+
// For .docx input with .docx output: round-trip via substituteDocxXml.
|
|
2605
|
+
if (e.input.kind === "docx" && extname(e.output) === ".docx") {
|
|
2606
|
+
const { xml: newXml, warnings: dw } = substituteDocxXml(
|
|
2607
|
+
e.input.docxXml, e.cascade.placeholders, resolved, e.cascade.tier
|
|
2608
|
+
);
|
|
2609
|
+
if (dw.length) for (const w of dw) err.write(paint(`warning (entry ${i}): ${w}\n`, "yellow", err));
|
|
2610
|
+
const buf = await writeDocxBuffer(e.input.path, newXml);
|
|
2611
|
+
writeFileSync(e.output, buf);
|
|
2612
|
+
} else {
|
|
2613
|
+
writeFileSync(e.output, outputText, "utf8");
|
|
2614
|
+
}
|
|
2615
|
+
} catch (writeErr) {
|
|
2616
|
+
err.write(paint(`error: bundle entry ${i} could not write ${e.output}: ${writeErr.message}\n`, "red", err));
|
|
2617
|
+
return EXIT.IO;
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
if (!opts.silent && !opts.json) {
|
|
2622
|
+
err.write(paint(`ok: wrote ${entries.length} document(s) — ${entries.map(e => e.output).join(", ")}\n`, "green", err));
|
|
2623
|
+
}
|
|
2624
|
+
if (opts.json) {
|
|
2625
|
+
out.write(JSON.stringify({
|
|
2626
|
+
ok: true,
|
|
2627
|
+
outputs: entries.map(e => ({ template: e.input.path, output: e.output, tier: e.cascade.tier })),
|
|
2628
|
+
resolved_keys: Object.keys(resolved),
|
|
2629
|
+
sources,
|
|
2630
|
+
}, null, 2) + "\n");
|
|
2631
|
+
}
|
|
2632
|
+
return EXIT.OK;
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2285
2635
|
function describeInput(input) {
|
|
2286
2636
|
if (input.path) return input.path;
|
|
2287
2637
|
if (input.kind === "text") return "stdin";
|
|
@@ -2583,6 +2933,33 @@ export async function main(argv, io = {}) {
|
|
|
2583
2933
|
return await runCheckLlm(envObj, out, err, { fetcher });
|
|
2584
2934
|
}
|
|
2585
2935
|
|
|
2936
|
+
// v2 #6: bundle mode. `--bundle PATH` reads a bundle definition and
|
|
2937
|
+
// orchestrates filling each entry's template with shared parameters.
|
|
2938
|
+
// In bundle mode, no positional template arg is required (the bundle
|
|
2939
|
+
// declares them).
|
|
2940
|
+
if (opts.bundle) {
|
|
2941
|
+
if (opts.positional.length > 0) {
|
|
2942
|
+
err.write(paint(`error: --bundle does not take a positional template arg (the bundle declares them)\n`, "red", err));
|
|
2943
|
+
return EXIT.IO;
|
|
2944
|
+
}
|
|
2945
|
+
let bundle, paramsObj, envObj, parties;
|
|
2946
|
+
try {
|
|
2947
|
+
bundle = loadBundle(opts.bundle);
|
|
2948
|
+
paramsObj = loadParamsFile(opts.params);
|
|
2949
|
+
envObj = effectiveEnv(cwd, processEnv);
|
|
2950
|
+
parties = loadParties(opts.parties || null);
|
|
2951
|
+
} catch (e) {
|
|
2952
|
+
err.write(paint(`error: ${e.message}\n`, "red", err));
|
|
2953
|
+
return e.exitCode || EXIT.IO;
|
|
2954
|
+
}
|
|
2955
|
+
try {
|
|
2956
|
+
return await cmdBundle(opts, bundle, paramsObj, envObj, { fetcher, out, err, spawner, stdinReader, parties });
|
|
2957
|
+
} catch (e) {
|
|
2958
|
+
err.write(paint(`error: ${e.message}\n`, "red", err));
|
|
2959
|
+
return e.exitCode || EXIT.IO;
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2586
2963
|
if (opts.positional.length === 0) {
|
|
2587
2964
|
err.write(paint(`error: no template given\n`, "red", err));
|
|
2588
2965
|
err.write(`run \`draft --help\` for usage.\n`);
|
|
@@ -2593,13 +2970,22 @@ export async function main(argv, io = {}) {
|
|
|
2593
2970
|
return EXIT.IO;
|
|
2594
2971
|
}
|
|
2595
2972
|
|
|
2596
|
-
let input, schema, paramsObj, envObj, parties;
|
|
2973
|
+
let input, schema, paramsObj, envObj, parties, dealText;
|
|
2597
2974
|
try {
|
|
2598
2975
|
input = await resolveInput(opts.positional[0], { spawner, stdinReader });
|
|
2599
2976
|
schema = loadSchema(input.path);
|
|
2600
2977
|
paramsObj = loadParamsFile(opts.params);
|
|
2601
2978
|
envObj = effectiveEnv(cwd, processEnv);
|
|
2602
2979
|
parties = loadParties(opts.parties || null);
|
|
2980
|
+
// v2 #4: --from-deal PATH reads a free-form deal description.
|
|
2981
|
+
if (opts.fromDeal) {
|
|
2982
|
+
if (!existsSync(opts.fromDeal)) {
|
|
2983
|
+
const e = new Error(`deal description file not found: ${opts.fromDeal}`);
|
|
2984
|
+
e.exitCode = EXIT.IO;
|
|
2985
|
+
throw e;
|
|
2986
|
+
}
|
|
2987
|
+
dealText = readFileSync(opts.fromDeal, "utf8");
|
|
2988
|
+
}
|
|
2603
2989
|
} catch (e) {
|
|
2604
2990
|
err.write(paint(`error: ${e.message}\n`, "red", err));
|
|
2605
2991
|
return e.exitCode || EXIT.IO;
|
|
@@ -2610,9 +2996,9 @@ export async function main(argv, io = {}) {
|
|
|
2610
2996
|
return await cmdListPlaceholders(opts, input, schema, envObj, { fetcher, out, err });
|
|
2611
2997
|
}
|
|
2612
2998
|
if (opts.validate) {
|
|
2613
|
-
return await cmdValidate(opts, input, schema, paramsObj, envObj, { fetcher, out, err, parties });
|
|
2999
|
+
return await cmdValidate(opts, input, schema, paramsObj, envObj, { fetcher, out, err, parties, dealText });
|
|
2614
3000
|
}
|
|
2615
|
-
return await cmdDraft(opts, input, schema, paramsObj, envObj, { fetcher, out, err, parties });
|
|
3001
|
+
return await cmdDraft(opts, input, schema, paramsObj, envObj, { fetcher, out, err, parties, dealText });
|
|
2616
3002
|
} catch (e) {
|
|
2617
3003
|
err.write(paint(`error: ${e.message}\n`, "red", err));
|
|
2618
3004
|
return e.exitCode || EXIT.IO;
|