⏳ This skill is pending AI review.
Scores will appear once the review pipeline completes.
contract-writer
>-
Choose how to use this skill
You do not need every option. Choose the path your AI client supports. The stable page stays the same; versioned files are immutable.
1. Native installer
This listing has no registered native installer command. Use the complete package or source fallback below, depending on what your client supports.
Do not guess an installer command or replace an existing version without reviewing the diff.
2. Complete package recommended
Download the ZIP when available. It includes SKILL.md plus the references, security notes and version metadata.
No complete ProSkills package is published for this listing yet.3. Prompt-only
Copy the prompt above when the agent can read the stable page or when you want to adopt the workflow without installing a skill.
Need only the instruction file?
Download SKILL.md only if your client requires a single file. The complete ZIP is safer for a full installation because it preserves the references and release context.
No path installs or executes anything by itself. Your agent still needs access to the project files. Before updating, compare the installed version and review the diff.
// RATINGS
// README
The Closer skill
Eleven Claude skills that run your whole sales team. Free, MIT, no signup, no API key, no CRM seats.
record-keeper prospector follow-up sales-coach contract-writer researcher
scheduler pipeline proposal-writer win-back forecaster
One logs every email and call for you. Another finds new leads and does the cold outreach. One keeps following up until a call is booked. And the craziest one is the coach: it listens to your sales call and tells you exactly what to say next. When they say "it's too expensive", the perfect answer is already on your screen. Then it writes the contract and sends it over.
So instead of paying for a CRM, a lead tool, an email sequencer and a call coach, you run the whole thing inside Claude, from one file in your project.
Nothing is ever sent, booked or signed without your yes. Claude drafts, you approve, it goes out from your own accounts.
Install
Paste this repo link into Claude and say install skill:
https://github.com/Jakeschincariol/closer-skill
install skill
Or as a plugin, in Claude Code:
/plugin marketplace add Jakeschincariol/closer-skill
/plugin install closer-skill@closer-skill
Claude Code namespaces plugin skills, so installed as a plugin they show up as
/closer-skill:prospector and so on. Copy the folders instead if you want plain
/prospector:
git clone https://github.com/Jakeschincariol/closer-skill.git
cd closer-skill && cp -r record-keeper prospector follow-up sales-coach contract-writer researcher \
scheduler pipeline proposal-writer win-back forecaster ~/.claude/skills/
Keep all eleven together: they share pipeline/deals.py. No Claude Code at all? Paste any single
SKILL.md at the top of a chat and it runs as a mode. You lose the Python tools, but the method works.
The tools need Python 3.8 or newer. Nothing to pip install.
Works best with Claude's Gmail and Google Calendar connectors (email and invites go out from your own accounts) and Claude in Chrome (the coach reads Google Meet's live captions).
The eleven
| skill | job | what it does |
|---|---|---|
record-keeper | Record Keeper | Logs every email and call for you: syncs your inbox threads with each contact, saves call notes, shows anyone's full history. |
prospector | Prospector | Finds local businesses from public listings, checks each website for the gap you fix (no booking, not mobile, slow, no ads, no reviews) and writes the cold email. |
follow-up | Follow-Up | Follows up until a call is booked: a bump, something useful, a last note. Stops at the first no. |
sales-coach | Sales Coach | Listens to your call through live captions and tells you what to say next when an objection lands. Reviews the call after. |
contract-writer | Contract Writer | Turns what was agreed into a plain-English services agreement and PDF, checks the fee against the call, and sends it on your yes. |
researcher | Researcher | Reads the prospect's own website before the call and writes a one-page brief with talking points and questions. |
scheduler | Scheduler | Turns "Thursday at 2 works" into a calendar invite with a Meet link, with no double-booking. |
pipeline | Pipeline Manager | One board for every deal, from lead to won: the CRM, in closer/pipeline.json. |
proposal-writer | Proposal Writer | Turns the call into a priced proposal with two or three clear options, in their own words. |
win-back | Win-Back | Brings back the deals that went quiet, once, at the right time. |
forecaster | Forecaster | Tells you what you'll close this month: commit, likely and best case, deal by deal. |
How to use it
Everything lands in a closer/ folder in your project, and each skill reads what the last one wrote.
An example, the one in the reel: a small web agency in Austin selling booking-ready websites.
pipelinesets up the board with your offer: $2,500, or a $399 monthly plan, and one true line about what it pays back.prospectorfinds 47 local businesses with no online booking, ranks them with the reason, and writes the first email to Dana Ortiz at Harbor Lane Dental. You say yes. It goes out Monday 9:02 AM.follow-upsends a bump on Wednesday, a 1-minute mockup on Friday and a last note on Tuesday, each on your yes. Dana replies at 11:48 AM: "Sorry, crazy week! Thursday at 2 works."schedulerbooks Thursday, October 29, 2:00 PM on your calendar with a Meet link.researcherwrites the brief on Harbor Lane Dental before the call.sales-coachreads the captions. Dana says, "It looks great, but honestly, it's too expensive for us right now." On your screen: "Totally fair. Most practices win the cost back with 2 new patients a month. Want to start on the $399 monthly plan instead, with no setup fee?"contract-writerhas the agreement for $399 a month out at 2:31 PM, on your yes.record-keeperlogged every email and the call.forecasterputs it in November's commit.proposal-writerandwin-backhandle the deals that need a quote or went quiet.
(The agency, Dana, Harbor Lane Dental and every lead are fictional.)
The tools
All standard-library Python. The two that read websites read public pages only, identify themselves, and go slowly.
python3 pipeline/deals.py board # the board
python3 record-keeper/log.py sync --emails closer/inbox.json # log every email
python3 prospector/leads.py rank --csv closer/leads.csv # ranked leads -> closer/leads.json
python3 follow-up/followup.py due # who to follow up with today
python3 sales-coach/coach.py listen --captions closer/calls/live.txt --follow
python3 contract-writer/contract.py draft --help
python3 researcher/brief.py build --contact <id>
python3 scheduler/slots.py parse --text "Thursday at 2 works"
python3 proposal-writer/proposal.py draft --help
python3 win-back/winback.py list
python3 forecaster/forecast.py month
Try any of them on the reel's example board with --file pipeline/pipeline.example.json.
The tests live next to each tool:
for d in */; do python3 -m unittest discover -s "$d" -p "test_*.py"; done
Fine print
Nothing goes out without your yes. Every email, follow-up, invite, proposal and contract is a draft until you approve that exact one. One yes per message, never "yes to all". Email goes out from your own inbox, invites from your own calendar.
Cold email, done legally. Every opening email has a real subject, your real name and business, your postal address and a plain way to opt out (CAN-SPAM). In the UK, EU and Canada, cold email to people needs a lawful basis or consent first: check before you email there. At most 50 new openings a day.
Stop rules. At the first "no" it stops, everywhere, for good. Follow-ups stop the moment a call is booked, after 3 follow-ups, or 30 days after the opening. Win-backs are one per deal. An unsubscribe goes on a suppression list no skill will email.
Calls. Tell everyone on the call that captions or notes are on. Some places (California, Florida, Washington and many countries) need everyone's consent to record. The coach reads captions as text, keeps no audio, and shows its cards only to you. It never speaks on the call.
Never invents anything. No made-up facts about a prospect, numbers, results, clients, reviews or deadlines. Answers and proposals are filled from your own offer and what was really said.
Public business information only. The researcher and prospector read a business's own website and public listings, never a person's private life and nothing behind a login.
Not legal or financial advice. Contracts and proposals are templates: have a lawyer read your standard terms once. Forecasts are estimates.
Files
pipeline/ SKILL.md, deals.py, pipeline.example.jso
// HOW IT'S BUILT
// FILES
4 files shipped with this skill. Inline text is the catalog copy. The upstream link is the source repository.
contract-writer/SKILL.md4.4 KBb041428908adupstream ↗
--- name: contract-writer description: >- Contract Writer. Creates the contract and sends it over for you, after your yes. Turns what was agreed on the call into a plain-English services agreement (services, fee, term, launch, payment, ownership, liability, signature lines), checks the fee against what was actually said on the call or in the proposal, writes a print-ready PDF, drafts the email to send it, and marks the deal won when the signed copy comes back. Use when the user says "write the contract", "send the agreement", "draft a services agreement", "send the paperwork", "they said yes", or "they signed". argument-hint: "[draft <deal> | pdf <deal> | signed <deal>]" --- # contract-writer The Contract Writer closes the loop the same day: the call ends, the agreement goes out within the hour while they still remember why they said yes. `${CLAUDE_SKILL_DIR}` below is this skill's folder (where this SKILL.md lives). ```bash python3 "${CLAUDE_SKILL_DIR}/contract.py" draft --deal harbor-lane-website \ --services "A booking-ready website and a managed Google Ads campaign for Harbor Lane Dental." \ --fee "\$399 per month, no setup fee, billed monthly." \ --term "Month to month. Cancel anytime with 30 days notice." \ --launch "Website live within 14 days of signing." --date "October 29, 2026" python3 "${CLAUDE_SKILL_DIR}/contract.py" pdf --deal harbor-lane-website python3 "${CLAUDE_SKILL_DIR}/contract.py" record --deal harbor-lane-website --approval "yes" --at 2026-10-29T14:31 python3 "${CLAUDE_SKILL_DIR}/contract.py" signed --deal harbor-lane-website --today 2026-11-02 ``` ## Steps 1. Read the call notes (`/record-keeper timeline`) and the proposal if there was one. Write the four deal sections from what was agreed, in plain words: Services, Fee, Term, Launch. 2. `contract.py draft`. It adds the standard sections (payment, ownership, confidentiality, liability, changes) and signature lines, and writes `closer/contracts/<company>-agreement.md` and `.html`. 3. Read any `CHECK:` line out loud to the user. The fee must be an amount that was said on the call or in the proposal. If it is not, ask; never "fix" it silently. 4. Show the agreement. Ask: "Is this exactly what you agreed with Dana? Reply yes or no." 5. `contract.py pdf` (headless Chrome), or the user opens the .html and prints to PDF. 6. Draft the email: short, warm, what is attached, what happens next. "Hi Dana, great talking today! Here's the agreement for the $399 monthly plan. Sign whenever you're ready and we'll start Monday." Ask: "Send this to Dana with the agreement attached? Reply yes or no." 7. On a yes: a Gmail draft with the PDF attached from the user's account, which the user sends (or sent through the Gmail connector right after that yes). Then `contract.py record --approval "<their yes>"`. 8. For e-signature, the user can upload the PDF to the e-sign tool they already use; this skill never signs for anyone. When the signed copy comes back, `contract.py signed`. ## The user's own terms The standard sections in `contract.py` (STANDARD) are a sensible starting point for a small services business, not legal advice. The first time, suggest the user has a lawyer read them once and edit STANDARD to match. Every agreement after that uses the same reviewed terms. ## Example from the reel At 2:31 PM, seventeen minutes after Dana's "too expensive", the Services Agreement between Your Agency and Harbor Lane Dental, dated October 29, 2026: a booking-ready website and a managed Google Ads campaign; $399 per month, no setup fee, billed monthly; month to month, cancel anytime with 30 days notice; website live within 14 days of signing. Sent as Harbor-Lane-Dental-Agreement.pdf with the subject "Your agreement with Your Agency". (Illustrative: both businesses are fictional.) ## What it never does - Never sends the agreement without the user's yes for that exact email and attachment. - Never signs, or adds anyone's signature, name-as-signature or initials. - Never puts a price, discount or promise in the agreement that was not agreed. ## Consent and truth - Nothing is sent, booked or signed without the user's explicit yes for that exact message, invite or document. Claude drafts, the user says "yes", then it goes out from the user's own accounts. One yes per message. Never "yes to all". - Contracts are templates, not legal advice. - Never invent a number, a result, a client or a deadline. Fill in only what is true and ask when something is missing.contract-writer/contract.py9.7 KBfd828798a73cupstream ↗
#!/usr/bin/env python3 """Writes the contract and sends it over (after your yes). Standard library only. draft writes a plain-English services agreement for a deal from what was agreed: closer/contracts/<company>-agreement.md and a print-ready .html beside it pdf prints the .html to PDF with headless Chrome or Chromium, if one is installed record logs the agreement AFTER it went out, with your yes word for word (deal -> contract-sent) signed marks the deal won when the signed copy comes back python3 contract.py draft --deal harbor-lane-website --services "A booking-ready website and a managed \ Google Ads campaign for Harbor Lane Dental." --fee "$399 per month, no setup fee, billed monthly." \ --term "Month to month. Cancel anytime with 30 days notice." --launch "Website live within 14 days of signing." python3 contract.py pdf --deal harbor-lane-website python3 contract.py record --deal harbor-lane-website --approval "yes" --at 2026-10-29T14:31 python3 contract.py signed --deal harbor-lane-website --today 2026-11-02 This is a template, not legal advice. Have a lawyer look at your standard terms once; after that the skill fills them in the same way every time. It never signs for anyone, and it checks the fee against what was said on the call. """ import argparse import datetime import html as htmllib import importlib.util import os import re import shutil import subprocess import sys HERE = os.path.dirname(os.path.abspath(__file__)) OUT_DIR = os.path.join("closer", "contracts") MONEY_RE = re.compile(r"\$\s?\d[\d,]*(?:\.\d{2})?") STANDARD = [ ("Payment", "Invoices are due within 7 days. Work pauses if an invoice is more than 14 days late."), ("Ownership", "Once paid, the client owns the website content and design made for them. " "Ad accounts are set up in the client's name."), ("Confidentiality", "Each side keeps the other's private business information private."), ("Liability", "Neither side is liable for indirect losses. Total liability is capped at the fees paid " "in the 3 months before the claim."), ("Changes", "Changes to this agreement are made in writing and agreed by both sides."), ] def _pipeline(): path = os.path.join(HERE, "..", "pipeline", "deals.py") if not os.path.exists(path): sys.exit("The contract-writer skill needs the pipeline skill next to it (../pipeline/deals.py).") spec = importlib.util.spec_from_file_location("closer_deals", path) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) return mod P = _pipeline() class ContractError(Exception): pass def amounts(text): return {re.sub(r"[\s,]", "", m) for m in MONEY_RE.findall(text or "")} def check_fee(data, deal, fee): """Warnings when the fee is not what was said on the call or in the proposal.""" said = set() for a in P.history(data, deal_id=deal["id"]): if a["type"] in ("call", "proposal", "reply", "note"): said |= amounts(a.get("text")) fee_amounts = amounts(fee) if not fee_amounts: return ["The fee has no dollar amount in it."] if said and not fee_amounts & said: return ["The fee (%s) is not an amount said on the call or in the proposal (%s). Check it." % ( ", ".join(sorted(fee_amounts)), ", ".join(sorted(said)))] return [] def build(data, deal, date, sections): me = data.get("me", {}) c = P.find(data["contacts"], deal["contact_id"]) for h, t in sections: if not t or "[fill" in t: raise ContractError("The %s section is empty. Say what was agreed." % h) if not me.get("company") or not c.get("company"): raise ContractError("Both parties need a company name (me.company and the contact's company).") parties = (me["company"], c["company"]) md = ["# Services Agreement", "", "**%s** and **%s**" % parties, "", date, ""] n = 0 for h, t in list(sections) + STANDARD: n += 1 md += ["## %d. %s" % (n, h), "", t, ""] md += ["## Signatures", "", "%s: ______________________ Date: ________" % me["company"], "", "%s, %s: ______________________ Date: ________" % (c["name"], c["company"]), ""] return "\n".join(md) def to_html(md): body = [] for line in md.splitlines(): esc = htmllib.escape(line) esc = re.sub(r"\*\*(.+?)\*\*", r"<strong>\1</strong>", esc) if line.startswith("# "): body.append("<h1>%s</h1>" % esc[2:]) elif line.startswith("## "): body.append("<h2>%s</h2>" % esc[3:]) elif line.strip(): body.append("<p>%s</p>" % esc) css = ("@page{size:Letter;margin:22mm}body{font:11.5pt/1.55 Georgia,serif;color:#1c1c1c;max-width:680px;" "margin:40px auto}h1{font:600 22pt Helvetica,Arial,sans-serif;margin:0 0 6px}h2{font:600 12pt " "Helvetica,Arial,sans-serif;margin:22px 0 4px}p{margin:0 0 8px}") return ("<!doctype html><html><head><meta charset=\"utf-8\"><title>Services Agreement</title><style>%s</style>" "</head><body>%s</body></html>\n") % (css, "\n".join(body)) def paths_for(data, deal): c = P.find(data["contacts"], deal["contact_id"]) base = os.path.join(OUT_DIR, P.slug(c.get("company") or c["name"]) + "-agreement") return base + ".md", base + ".html", base + ".pdf" def chrome(): for name in ("google-chrome", "chromium", "chromium-browser", "chrome"): if shutil.which(name): return shutil.which(name) mac = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" return mac if os.path.exists(mac) else None def record(data, deal_id, approval, when): ok, why = P.parse_yes(approval) if not ok: return False, "Not recorded: %s." % why deal = P.find(data["deals"], deal_id) if deal is None: return False, "No deal %s" % deal_id md, html_path, pdf = paths_for(data, deal) if not os.path.exists(md): return False, "Not recorded: no agreement drafted for %s yet." % deal_id c = P.find(data["contacts"], deal["contact_id"]) P.add_activity(data, date=when, contact_id=c["id"], deal_id=deal_id, channel="email", type="contract", direction="out", subject="Your agreement with %s" % data["me"].get("company"), text="Agreement sent", approval=approval, attachment=pdf if os.path.exists(pdf) else html_path) deal["stage"] = "contract-sent" if not deal.get("close_date"): deal["close_date"] = (datetime.date.fromisoformat(when[:10]) + datetime.timedelta(days=7)).isoformat() deal["next_step"] = "%s signs the agreement" % c["name"].split()[0] return True, "Logged: agreement sent to %s. Deal is contract-sent." % c["name"] def main(argv=None): p = argparse.ArgumentParser(description="Write the contract.") p.add_argument("--file", default=P.DEFAULT_FILE) sub = p.add_subparsers(dest="cmd", required=True) s = sub.add_parser("draft") s.add_argument("--deal", required=True) s.add_argument("--services", required=True) s.add_argument("--fee", required=True) s.add_argument("--term", required=True) s.add_argument("--launch", required=True) s.add_argument("--date", help="the agreement date as written, e.g. 'October 29, 2026'") s = sub.add_parser("pdf") s.add_argument("--deal", required=True) s = sub.add_parser("record") s.add_argument("--deal", required=True) s.add_argument("--approval", required=True) s.add_argument("--at", required=True) s = sub.add_parser("signed") s.add_argument("--deal", required=True) s.add_argument("--today") a = p.parse_args(argv) try: data = P.load(a.file) deal = P.find(data["deals"], a.deal) if deal is None: raise ContractError("No deal %s" % a.deal) if a.cmd == "draft": date = a.date or datetime.date.today().strftime("%B %d, %Y").replace(" 0", " ") sections = [("Services", a.services), ("Fee", a.fee), ("Term", a.term), ("Launch", a.launch)] md = build(data, deal, date, sections) md_path, html_path, _ = paths_for(data, deal) os.makedirs(OUT_DIR, exist_ok=True) with open(md_path, "w", encoding="utf-8") as fh: fh.write(md) with open(html_path, "w", encoding="utf-8") as fh: fh.write(to_html(md)) print("Wrote %s and %s" % (md_path, html_path)) for w in check_fee(data, deal, a.fee): print("CHECK: " + w) print("A template, not legal advice. Read it, then: contract.py pdf, and send only on a yes.") elif a.cmd == "pdf": _, html_path, pdf = paths_for(data, deal) exe = chrome() if not exe: print("No Chrome found. Open %s and Print > Save as PDF." % html_path) return 1 subprocess.run([exe, "--headless", "--disable-gpu", "--no-pdf-header-footer", "--print-to-pdf=" + os.path.abspath(pdf), "file://" + os.path.abspath(html_path)], check=True, capture_output=True, timeout=60) print("Wrote %s" % pdf) elif a.cmd == "record": ok, msg = record(data, a.deal, a.approval, a.at) if ok: P.save(data, a.file) print(msg) return 0 if ok else 1 elif a.cmd == "signed": P.move(data, a.deal, "won", P.today_arg(a.today), next_step="Kickoff") P.save(data, a.file) print("%s is won." % deal["title"]) except (ContractError, P.PipelineError, OSError, subprocess.SubprocessError) as e: print(str(e), file=sys.stderr) return 1 return 0 if __name__ == "__main__": sys.exit(main())contract-writer/test_contract.py3.1 KB3765f3f19c8bupstream ↗
import importlib.util import json import os import shutil import tempfile import unittest HERE = os.path.dirname(os.path.abspath(__file__)) spec = importlib.util.spec_from_file_location("closer_contract_test", os.path.join(HERE, "contract.py")) C = importlib.util.module_from_spec(spec) spec.loader.exec_module(C) EXAMPLE = os.path.join(HERE, "..", "pipeline", "pipeline.example.json") REEL = [("Services", "A booking-ready website and a managed Google Ads campaign for Harbor Lane Dental."), ("Fee", "$399 per month, no setup fee, billed monthly."), ("Term", "Month to month. Cancel anytime with 30 days notice."), ("Launch", "Website live within 14 days of signing.")] def example(): with open(EXAMPLE, encoding="utf-8") as fh: return json.load(fh) class Build(unittest.TestCase): def test_the_reel_agreement(self): data = example() deal = C.P.find(data["deals"], "harbor-lane-website") md = C.build(data, deal, "October 29, 2026", REEL) self.assertTrue(md.startswith("# Services Agreement")) self.assertIn("**Your Agency** and **Harbor Lane Dental**", md) self.assertIn("## 2. Fee\n\n$399 per month, no setup fee, billed monthly.", md) self.assertIn("## 9. Changes", md) self.assertIn("Dana Ortiz, Harbor Lane Dental: ______", md) html = C.to_html(md) self.assertIn("<h1>Services Agreement</h1>", html) self.assertIn("<strong>Your Agency</strong>", html) def test_empty_sections_are_refused(self): data = example() deal = C.P.find(data["deals"], "harbor-lane-website") with self.assertRaises(C.ContractError): C.build(data, deal, "today", [("Fee", "[fill: fee]")]) def test_fee_is_checked_against_the_call(self): data = example() deal = C.P.find(data["deals"], "harbor-lane-website") self.assertEqual(C.check_fee(data, deal, "$399 per month"), []) self.assertTrue(C.check_fee(data, deal, "$349 per month")) self.assertTrue(C.check_fee(data, deal, "Monthly, as agreed")) class Record(unittest.TestCase): def test_record_needs_a_draft_and_a_yes(self): tmp = tempfile.mkdtemp() cwd = os.getcwd() try: os.chdir(tmp) data = example() deal = C.P.find(data["deals"], "juniper-booking") self.assertFalse(C.record(data, "juniper-booking", "yes", "2026-11-03T11:00")[0]) md_path, html_path, _ = C.paths_for(data, deal) os.makedirs(C.OUT_DIR) with open(md_path, "w") as fh: fh.write("x") with open(html_path, "w") as fh: fh.write("x") self.assertFalse(C.record(data, "juniper-booking", "yes to all", "2026-11-03T11:00")[0]) ok, msg = C.record(data, "juniper-booking", "yes", "2026-11-03T11:00") self.assertTrue(ok, msg) self.assertEqual(deal["stage"], "contract-sent") self.assertEqual(C.P.validate(data)[0], []) finally: os.chdir(cwd) shutil.rmtree(tmp) if __name__ == "__main__": unittest.main()README.mdupstream ↗
Contents are not inlined in the catalog. Open the upstream file.
Plain-text copy, no login: /skill-bundles/jakeschincariol-closer-skill-contract-writer.md