contract_cli/commands/
skill.rs1use crate::cli::SkillCmd;
2use crate::error::Result;
3use crate::output::{print_success, Ctx};
4
5const SKILL_MD: &str = r#"---
6name: contract-cli
7description: >
8 Generate beautiful, plain-English contracts (NDA, consulting, MSA, SOW,
9 service) as PDF. Stateful — shares issuers + clients with invoice-cli via
10 the Paperfoot accounting SQLite store. Composable clause packs let the user
11 / agent pick, drop, reorder, or rewrite clauses. Use when the user asks to
12 draft, generate, render, or sign a contract or NDA.
13---
14
15## contract-cli
16
17`contract` is a stateful CLI for drafting and rendering business contracts.
18
19### Critical: shared state with invoice-cli
20
21The shared SQLite at `~/Library/Application Support/com.paperfoot.accounting/accounting.db`
22is the single source of truth for **issuers** (your companies) and **clients**
23(counterparties) across the whole accounting suite. `invoice` writes here too.
24
25**Before creating a new issuer or client, ALWAYS list first:**
26
27```
28contract issuer list # or: invoice issuer list
29contract clients list # or: invoice clients list
30```
31
32If the entity already exists under any slug, reuse it. Don't create
33`alberto-pertusa` if `london-psychiatry-clinic` already covers the same
34person — pick whichever slug the user means, or `contract clients edit`
35to enrich it (legal-name, company-no, jurisdiction are the contract-only
36fields, added in V7).
37
38### Quick start
39
40```
41contract issuer list # is there already a "boris"?
42contract clients list # is there already a client?
43
44# If not, add — same way invoice-cli adds them, same DB:
45contract issuer add acme --name "Acme Studio" --jurisdiction sg \
46 --address "1 Marina Bay\nSingapore 018989"
47contract clients add meridian --name "Meridian & Co." \
48 --legal-name "Meridian & Co. Pty Ltd" --company-no "ACN 600 123 456" \
49 --jurisdiction "Victoria, Australia" \
50 --address "401 Collins Street\nMelbourne VIC 3000\nAustralia"
51
52# Mutual NDA, 3-year term
53contract new --kind nda --as acme --client meridian \
54 --purpose "evaluation of a joint product line" --term-years 3
55
56# Consulting agreement, fixed fee, 3-month term, IP to client
57contract new --kind consulting --as acme --client meridian \
58 --purpose "design and build a customer dashboard" \
59 --fee fixed:8400:SGD --fee-schedule on-completion --term-months 3 \
60 --deliverable "Discovery" --deliverable "Designs" --deliverable "Implementation" \
61 --ip-assignment client --governing-law Singapore
62
63contract list
64contract render CTR-acme-2026-0001 --open # DRAFT watermark by default
65contract render CTR-acme-2026-0001 --final --open # clean copy for signature
66contract sign CTR-acme-2026-0001 --side us --name "B. Djordjevic" --title CEO
67contract sign CTR-acme-2026-0001 --side them --name "Sophie Lin" --title "Head of Marketing"
68# status auto-bumps to 'signed' once both sides have signed
69```
70
71### Composing clauses
72
73Every contract is built from a clause pack (`pack show <kind>` lists them).
74By default it includes the pack's `default_clauses` in order. Customise per
75contract:
76
77```
78contract clauses list CTR-acme-2026-0001
79contract clauses add CTR-acme-2026-0001 non_solicit --from-file ./extra.md --position 8
80contract clauses edit CTR-acme-2026-0001 termination --from-file ./our-termination.md
81contract clauses remove CTR-acme-2026-0001 warranties
82contract clauses move CTR-acme-2026-0001 governing_law 12
83contract clauses reset CTR-acme-2026-0001 # back to pack default
84```
85
86A clause's `--body` / `--from-file` may use plain Markdown with paragraphs,
87`- ` bullets, and `1. ` numbered lists. Pack clauses use `{{vars}}` like
88`{{our_legal_name}}`, `{{their_legal_name}}`, `{{effective_date}}`,
89`{{term_text}}`, `{{governing_law}}`, `{{jurisdiction_phrase}}`,
90`{{fee_text}}`, `{{deliverables_block}}`, `{{ip_assignment_text}}`,
91`{{confidentiality_years}}`, `{{termination_notice_days}}`, `{{purpose}}`.
92
93### Tips
94
95- Run `contract agent-info` for the full JSON manifest.
96- Run `contract doctor --json` to verify typst + DB + packs + templates.
97- Template chain at render: `--template` > `contract.default_template` > `"helvetica-nera"`.
98- Templates: `helvetica-nera` (Swiss monochrome), `vienna-legal` (Bauhaus/terracotta), `editorial` (serif).
99- Drafts render with a diagonal DRAFT watermark by default. Use `--final`
100 for clean copies and `--draft` to force the watermark on a signed contract.
101- These clauses are practical plain-English starting points, **not legal advice** —
102 have a lawyer review for material engagements.
103"#;
104
105pub fn run(_cmd: SkillCmd, ctx: Ctx) -> Result<()> {
106 let targets = [
107 dirs_path(".claude/skills/contract-cli/SKILL.md"),
108 dirs_path(".codex/skills/contract-cli/SKILL.md"),
109 dirs_path(".gemini/skills/contract-cli/SKILL.md"),
110 ];
111 let mut written = Vec::new();
112 for t in targets {
113 if let Some(parent) = t.parent() {
114 std::fs::create_dir_all(parent)?;
115 }
116 std::fs::write(&t, SKILL_MD)?;
117 written.push(t.display().to_string());
118 }
119 print_success(ctx, &written, |paths| {
120 for p in paths {
121 println!("installed → {}", p);
122 }
123 });
124 Ok(())
125}
126
127fn dirs_path(rel: &str) -> std::path::PathBuf {
128 let home = std::env::var("HOME").unwrap_or_else(|_| ".".into());
129 std::path::PathBuf::from(home).join(rel)
130}