⏳ This skill is pending AI review.

Scores will appear once the review pipeline completes.

version unknown

contract-writer

@jakeschincariol⭐ 32 stars

>-

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.

—/10

// RATINGS

⭐GitHub Stars
⭐⭐ 32 on GitHubGitHub ↗

Growing

🟢ProSkills Score
—
📍

Not yet listed on ClawHub or SkillsMP

// 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

skilljobwhat it does
record-keeperRecord KeeperLogs every email and call for you: syncs your inbox threads with each contact, saves call notes, shows anyone's full history.
prospectorProspectorFinds 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-upFollow-UpFollows up until a call is booked: a bump, something useful, a last note. Stops at the first no.
sales-coachSales CoachListens to your call through live captions and tells you what to say next when an objection lands. Reviews the call after.
contract-writerContract WriterTurns what was agreed into a plain-English services agreement and PDF, checks the fee against the call, and sends it on your yes.
researcherResearcherReads the prospect's own website before the call and writes a one-page brief with talking points and questions.
schedulerSchedulerTurns "Thursday at 2 works" into a calendar invite with a Meet link, with no double-booking.
pipelinePipeline ManagerOne board for every deal, from lead to won: the CRM, in closer/pipeline.json.
proposal-writerProposal WriterTurns the call into a priced proposal with two or three clear options, in their own words.
win-backWin-BackBrings back the deals that went quiet, once, at the right time.
forecasterForecasterTells 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.

  1. pipeline sets up the board with your offer: $2,500, or a $399 monthly plan, and one true line about what it pays back.
  2. prospector finds 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.
  3. follow-up sends 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."
  4. scheduler books Thursday, October 29, 2:00 PM on your calendar with a Meet link.
  5. researcher writes the brief on Harbor Lane Dental before the call.
  6. sales-coach reads 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?"
  7. contract-writer has the agreement for $399 a month out at 2:31 PM, on your yes.
  8. record-keeper logged every email and the call. forecaster puts it in November's commit. proposal-writer and win-back handle 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

// REPO STATS

32 stars