contract_cli/commands/
skill.rs1use crate::cli::SkillCmd;
2use crate::error::Result;
3use crate::output::{print_success, Ctx};
4
5const SKILL_MD: &str = r#"---
9name: contract-cli
10description: >
11 Draft, render, and sign business contracts as beautiful PDFs — NDA, NCNDA
12 (non-circumvention), consulting, MSA, SOW, service, loan. Use when the user
13 asks to draft, generate, render, e-sign, or manage a contract, NDA, NCNDA,
14 or loan agreement. Pick a PDF template by look with `contract template find
15 "<description>"` and a contract kind with `contract kinds find "<need>"`.
16 Shares issuers/clients with invoice-cli. Run `contract agent-info` for all
17 commands, flags, and exit codes.
18---
19
20`contract` is a stateful CLI for drafting and rendering business contracts.
21
22- Run `contract agent-info | jq` for the full manifest (commands, exit codes, envelope).
23- The SQLite DB is SHARED with invoice-cli: ALWAYS `contract issuer list` and
24 `contract clients list` before creating entities — reuse existing slugs.
25- `contract kinds find "<what you need>"` and `contract template find "<look>"`
26 resolve fuzzy descriptions to exact names; you make the final pick.
27- Drafts render with a DRAFT watermark; `--final` produces the signing copy.
28- Not legal advice — suggest lawyer review for material engagements.
29"#;
30
31fn targets() -> [std::path::PathBuf; 3] {
32 [
33 dirs_path(".claude/skills/contract-cli/SKILL.md"),
34 dirs_path(".codex/skills/contract-cli/SKILL.md"),
35 dirs_path(".gemini/skills/contract-cli/SKILL.md"),
36 ]
37}
38
39pub fn run(cmd: SkillCmd, ctx: Ctx) -> Result<()> {
40 match cmd {
41 SkillCmd::Install => {
42 let mut written = Vec::new();
43 for t in targets() {
44 if let Some(parent) = t.parent() {
45 std::fs::create_dir_all(parent)?;
46 }
47 std::fs::write(&t, SKILL_MD)?;
48 written.push(t.display().to_string());
49 }
50 print_success(ctx, &written, |paths| {
51 for p in paths {
52 println!("installed → {}", p);
53 }
54 });
55 Ok(())
56 }
57 SkillCmd::Status => {
58 #[derive(serde::Serialize)]
59 struct SkillStatus {
60 path: String,
61 installed: bool,
62 current: bool,
63 }
64 let statuses: Vec<SkillStatus> = targets()
65 .iter()
66 .map(|t| {
67 let on_disk = std::fs::read_to_string(t).ok();
68 SkillStatus {
69 path: t.display().to_string(),
70 installed: on_disk.is_some(),
71 current: on_disk.as_deref() == Some(SKILL_MD),
72 }
73 })
74 .collect();
75 print_success(ctx, &statuses, |ss| {
76 for s in ss {
77 let state = if s.current {
78 "current"
79 } else if s.installed {
80 "STALE — run: contract skill install"
81 } else {
82 "not installed"
83 };
84 println!(" {} — {}", s.path, state);
85 }
86 });
87 Ok(())
88 }
89 }
90}
91
92fn dirs_path(rel: &str) -> std::path::PathBuf {
93 let home = std::env::var("HOME").unwrap_or_else(|_| ".".into());
94 std::path::PathBuf::from(home).join(rel)
95}