skilldrop-cli 1.9.0 → 1.10.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/README.md CHANGED
@@ -77,7 +77,7 @@ Install one pack. Every role pack also brings `core` (intake, critique, review c
77
77
  - **Running a team on shifts:** `operations-manager` — a weekly rota checked for cover, hours, rest between shifts and days in a row, handed back in your own sheet. [Open pack](https://sananthanarayan.github.io/skilldrop/packs/operations-manager/)
78
78
  - **Running events:** `event-planner` — a seating plan where every together, keep-apart and access request holds, and a run of show with no room or person booked twice, both checked and drawn. [Open pack](https://sananthanarayan.github.io/skilldrop/packs/event-planner/)
79
79
  - **Furnishing rooms:** `interior-designer` — a dimensioned furniture plan for one room, checked for door swings, bed and chair clearances, a clear route to everything and turning space where it's required. [Open pack](https://sananthanarayan.github.io/skilldrop/packs/interior-designer/)
80
- - **Startup founders:** `startup-legal` — an agreement checked before you sign it, with wording to push back on, and your cap table through SAFEs, notes and a priced round. [Open pack](https://sananthanarayan.github.io/skilldrop/packs/startup-legal/)
80
+ - **Startup founders:** `startup-legal` — an agreement checked before you sign it, with wording to push back on, your cap table through SAFEs and a priced round, and your first office lease's real cost and exit dates. [Open pack](https://sananthanarayan.github.io/skilldrop/packs/startup-legal/)
81
81
  - **Everyone:** `core` on its own. [Open pack](https://sananthanarayan.github.io/skilldrop/packs/core/)
82
82
 
83
83
  </details>
package/catalogue.json CHANGED
@@ -241,7 +241,8 @@
241
241
  "description": "Sign agreements you understand and know what a round does to your ownership, with a clear line on what is worth paying a lawyer for.",
242
242
  "skills": [
243
243
  "contract-check",
244
- "cap-table"
244
+ "cap-table",
245
+ "lease-check"
245
246
  ]
246
247
  }
247
248
  },
@@ -351,6 +351,10 @@
351
351
  "tier": "standard",
352
352
  "rationale": "Checks evidence against a fixed checklist. Bounded synthesis, not adversarial review."
353
353
  },
354
+ "lease-check": {
355
+ "tier": "standard",
356
+ "rationale": "Extracting lease terms and reading clauses from the tenant's side is bounded; the script does the dates and sums."
357
+ },
354
358
  "llm-eval-harness": {
355
359
  "tier": "light",
356
360
  "rationale": "Measured (RFC-0073): on its held-out evals the light tier met 71% and 71% of checks in two tier trials, against standard's 71% and heavy's 71% and 71%, within 5 points of the best both times."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skilldrop-cli",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "Portable AI-agent skills, measured against the agent without them. ADRs, PRDs, runbooks, decks and reviews, installable into Claude Code, Cursor, Kiro, and any IDE that takes custom instructions. One command per skill or per role-based pack.",
5
5
  "author": {
6
6
  "name": "Sanjay Ananthanarayan",
@@ -1,5 +1,5 @@
1
1
  {
2
- "description": "Legal groundwork for a startup that can't yet pay a lawyer for everything: an agreement checked before you sign it (blanks, broken cross-references, mismatched amounts, the renewal date, and the clauses that hurt a small company, with wording to ask for), and your cap table modelled through SAFEs, notes and a priced round. Each says what is worth a lawyer's hour.",
2
+ "description": "Legal groundwork for a startup that can't yet pay a lawyer for everything: an agreement checked before you sign it (blanks, broken cross-references, mismatched amounts, the renewal date, and the clauses that hurt a small company, with wording to ask for), your cap table modelled through SAFEs, notes and a priced round, and your first office lease turned into its real commitment and exit dates. Each says what is worth a lawyer's hour.",
3
3
  "display_name": "Startup legal",
4
4
  "keywords": [
5
5
  "legal",
@@ -9,7 +9,8 @@
9
9
  "NDA",
10
10
  "SAFE",
11
11
  "cap table",
12
- "dilution"
12
+ "dilution",
13
+ "lease"
13
14
  ],
14
15
  "links": {
15
16
  "documentation": "https://sananthanarayan.github.io/skilldrop/packs/startup-legal/"
@@ -37,7 +38,8 @@
37
38
  "when": "You're a founder without a lawyer on retainer, and an agreement, a round or a launch is coming.",
38
39
  "you_bring": [
39
40
  "the agreement you've been sent, or your own template",
40
- "your cap table, SAFEs and any term sheet"
41
+ "your cap table, SAFEs and any term sheet",
42
+ "the heads of terms or draft lease for an office"
41
43
  ],
42
44
  "steps": [
43
45
  {
@@ -51,8 +53,14 @@
51
53
  "you_do": "Give it your shareholders, SAFEs, notes and the term sheet.",
52
54
  "you_get": "What each founder and investor owns after the round, what each SAFE converted at, and what the pool top-up cost you.",
53
55
  "decide": "Which valuation and pool terms to negotiate for."
56
+ },
57
+ {
58
+ "skill": "lease-check",
59
+ "you_do": "Give it the heads of terms or the draft lease.",
60
+ "you_get": "What signing commits you to up to each break and to the end, every notice deadline as a date, and the clauses to push back on with wording.",
61
+ "decide": "Sign, negotiate, or take the guarantee or break clause to a lawyer."
54
62
  }
55
63
  ],
56
- "you_end_with": "Agreements you understand before signing, and a cap table you can defend in a negotiation."
64
+ "you_end_with": "Agreements you understand before signing, a cap table you can defend, and an office lease whose cost and exits you know."
57
65
  }
58
66
  }
@@ -0,0 +1,119 @@
1
+ ---
2
+ name: lease-check
3
+ description: Check a commercial lease before a startup signs it for its first office, studio or lab — a script turns the lease's terms into every date and sum the tenant must plan around (rent year by year through stepped rents, fixed and index-linked reviews with caps and collars, rent-free months, what the company is committed to pay up to each break and to the end, and each break, renewal and notice deadline as a date), then flags terms that can't all be true and deadlines already passed — and reads the lease for the clauses that hurt a small tenant. Use when a founder says "review this office lease", "what's our break clause notice date", "how much are we committing to on this lease", "check this lease before I sign", "when can we get out of this lease", or "what does the rent review do".
4
+ ---
5
+
6
+ # lease-check
7
+
8
+ A first office lease is often the biggest commitment a young company signs, and founders sign
9
+ it on the headline rent. What hurts is in the detail. The break clause needs six months'
10
+ notice and any unpaid sum voids it. The CPI review compounds three years at once. The rent-free
11
+ months are forfeited if the break is used. Repair obligations hand the tenant the roof. A
12
+ script turns the lease's terms into the numbers: every deadline as a date, the rent each year,
13
+ and what the company has committed to pay up to each exit. The agent reads the lease from the
14
+ tenant's side.
15
+
16
+ ## How to respond
17
+
18
+ **These rules come before the steps and outrank them:**
19
+
20
+ - **Lead with the commitment and the exits, in a founder's words.** ✅ *"Signing commits you
21
+ to £113,400 up to the break in January 2030. To use the break you must give notice by 1 July
22
+ 2029 and have paid everything, down to the last service-charge invoice, or the break fails
23
+ and you're in to 2031 for another £90,590."* Never mention a script, a JSON file, this
24
+ skill's name, a path inside its folder or a temporary path, and never call your work "the
25
+ check" or "a tool".
26
+ - **Every date and sum comes from the computed terms,** with the clause behind it.
27
+ - **Read for the tenant's side,** and give the wording to ask for on each problem, ready to
28
+ paste into an email to the agent or landlord.
29
+ - **Say what the terms can't price:** an open-market review, a service charge with no cap,
30
+ dilapidations at the end. Name each one, and suggest the ask that bounds it.
31
+ - **Name what is worth a lawyer's hour, and only that.** One or two lines: a personal
32
+ guarantee, full repairing terms on an old building, a break with conditions the landlord
33
+ won't soften, or a lease outside the protection of business-tenancy law where that applies.
34
+ Never refuse, never open with a disclaimer.
35
+ - **Working files stay out of sight.** The terms you extract go in a private temporary folder
36
+ (`mktemp -d`) and are deleted before replying. Never overwrite their lease.
37
+
38
+ 1. **Read the lease or heads of terms** and extract: start date, term in months, starting
39
+ rent, how it's paid, rent-free months, stepped rents, each rent review (month, type, any
40
+ cap, collar, upward-only), service charge and any cap, deposit, each break (month, notice,
41
+ who can break, conditions, any penalty), renewal option, notice to leave. Write down the
42
+ clause for each.
43
+
44
+ 2. **Write the terms** in the shape of [`templates/terms.json`](templates/terms.json). An
45
+ index-linked review over several years takes `years` so the cap and collar compound the way
46
+ the lease says; check the lease's wording, which differs on this. When the lease names a
47
+ break by its day ("the last day of the third year" is 28 February; "the third anniversary"
48
+ is 1 March), give it as the break's `date`: the notice date follows from it.
49
+
50
+ 3. **Run it** on today's date, so passed deadlines are flagged.
51
+
52
+ ```bash
53
+ work=$(mktemp -d 2>/dev/null || mktemp -d ./.work.XXXXXX) # a private folder for this run only
54
+ # Claude Code
55
+ python3 "${CLAUDE_SKILL_DIR}/scripts/lease_terms.py" "$work/terms.json" --as-of 2026-11-08
56
+ # Other IDEs (from the skill folder)
57
+ python3 scripts/lease_terms.py "$work/terms.json"
58
+ ```
59
+
60
+ Exit 0 means computed, 1 means a deadline has passed or the terms contradict each other (a
61
+ break after the end, a notice that runs back before the start, rent-free as long as the
62
+ term), and 2 means the input can't be read.
63
+
64
+ 4. **Read the lease for the tenant** against [`reference.md`](reference.md): break conditions,
65
+ repair and reinstatement, dilapidations, service charge, alienation (subletting, assignment,
66
+ sharing with a group company), use, alterations, guarantees and rent deposits, insurance,
67
+ and whether the rent-free survives a break. For each problem: clause, why it hurts, wording
68
+ to ask for. Mark severity: 🟥 don't sign as is, 🟧 push back, 🟨 worth asking.
69
+
70
+ 5. **Reply in this order:**
71
+ 1. **The commitment and the exits:** what signing commits to up to each break and to the
72
+ end, and the notice date for each break.
73
+ 2. **Fixes before signing,** most severe first, with wording to ask for.
74
+ 3. **Deadlines to diarise,** each as a date with its clause.
75
+ 4. **Rent by year,** and what can't be priced yet.
76
+ 5. **What to have a lawyer look at,** one or two lines.
77
+
78
+ **Non-interactive runs** (subagent, CI, headless): unsaid terms are left out and named as
79
+ missing, never filled with a typical value. With no start date, term or rent, emit `BLOCKED:
80
+ need the lease (or heads of terms) with its start date, length and rent`.
81
+
82
+ ## Useful references in this skill
83
+
84
+ - [`reference.md`](reference.md) — the lease clauses that hurt a small tenant, and what to ask for in each
85
+ - [`templates/terms.json`](templates/terms.json) — the input: a five-year lease with rent-free, a capped CPI review and a conditional break
86
+ - [`scripts/lease_terms.py`](scripts/lease_terms.py) — rent by year through reviews, committed cost to each break and the end, deadlines, contradictions
87
+ - [`examples/first-office.md`](examples/first-office.md) — worked example: heads of terms for a first office, and what the break really costs
88
+
89
+ ## Quality bar
90
+
91
+ - **The committed cost to each break and to the end is a sum,** rent and service charge, after rent-free.
92
+ - **Every deadline is a date with its clause,** and a passed one is said plainly.
93
+ - **Index-linked reviews compound the way the lease says,** within its cap and collar, and upward-only is applied.
94
+ - **What can't be priced is named,** with the ask that bounds it.
95
+ - **Every problem has the wording to ask for.**
96
+ - **The reply leads with the commitment and the exits,** with no mention of scripts or files.
97
+
98
+ ## When to use this skill
99
+
100
+ - ✅ Heads of terms or a draft lease for a first office, studio, lab or shop
101
+ - ✅ "When can we get out of this lease, and what does it cost to stay till then?"
102
+ - ✅ A rent review letter, or a break date coming up
103
+ - ✅ Comparing two buildings on what each really commits the company to
104
+
105
+ ## When NOT to use this skill
106
+
107
+ - ❌ A coworking membership with monthly terms: read its notice period, no schedule needed
108
+ - ❌ Residential tenancies
109
+ - ❌ A dispute with a landlord, or a dilapidations claim already made: get a surveyor or a lawyer
110
+ - ❌ Buying a property
111
+
112
+ ## Anti-patterns to avoid
113
+
114
+ - ❌ **Quoting the headline rent as the cost.** Rent-free, reviews and service charge move it; give the committed sum.
115
+ - ❌ **"The break is in year three."** The notice date is what matters, and it's six months earlier.
116
+ - ❌ **Treating a conditional break as certain.** One unpaid invoice can void it.
117
+ - ❌ **Compounding a three-year CPI review as one year,** or ignoring its cap.
118
+ - ❌ **Missing that rent-free is clawed back** if the break is used, when the lease says so.
119
+ - ❌ **"The calculation shows…"** The founder wants the sum and the date.
@@ -0,0 +1,30 @@
1
+ [
2
+ {
3
+ "query": "Review this office lease before we sign",
4
+ "should_trigger": true
5
+ },
6
+ {
7
+ "query": "What's the notice date for our lease break clause?",
8
+ "should_trigger": true
9
+ },
10
+ {
11
+ "query": "How much are we committing to on this lease?",
12
+ "should_trigger": true
13
+ },
14
+ {
15
+ "query": "When can we get out of our office lease and what does it cost to stay?",
16
+ "should_trigger": true
17
+ },
18
+ {
19
+ "query": "Review this SaaS agreement before I sign it",
20
+ "should_trigger": false
21
+ },
22
+ {
23
+ "query": "Find us coworking spaces in Manchester",
24
+ "should_trigger": false
25
+ },
26
+ {
27
+ "query": "Should we rent or buy a flat?",
28
+ "should_trigger": false
29
+ }
30
+ ]
@@ -0,0 +1,45 @@
1
+ {
2
+ "skill_name": "lease-check",
3
+ "evals": [
4
+ {
5
+ "id": 1,
6
+ "prompt": "We've been offered heads of terms for our first office (heads-of-terms.md). The agent wants an answer this week. What are we committing to, and when could we get out?",
7
+ "assertions": [
8
+ "Gives the committed cost to the break on 1 January 2030 as about £113,400 (rent after 3 months rent-free plus service charge) and to the end as about £203,990",
9
+ "Gives the break notice deadline as 1 July 2029",
10
+ "Warns that the break's conditions (all sums paid, vacant possession) can make it fail, and gives wording to narrow them",
11
+ "Flags the personal guarantee, the uncapped service charge and full repairing with no schedule of condition, with wording to ask for",
12
+ "Computes the year-3 review at the 4% cap compounded over three years (about £40,495) or explains the range",
13
+ "Does not mention a script or the skill's name, and does not invent lease terms not in the heads of terms"
14
+ ]
15
+ },
16
+ {
17
+ "id": 2,
18
+ "held_out": "2026-11-08",
19
+ "prompt": "check this lease before I sign - heads-of-terms.md. how much are we committing to and when's the break notice date?",
20
+ "assertions": [
21
+ "Notices that a 5-year term from 1 March 2027 ends on 29 February 2032 (2032 is a leap year), and flags the heads of terms' 'expiring on 1 March 2032' as inconsistent with the stated term",
22
+ "Rent year by year: year 1 GBP 12,000 (6 months rent-free, then 6 x 2,000), year 2 GBP 24,000, years 3, 4 and 5 GBP 26,400 each",
23
+ "Committed to the break (to 28 February 2030): rent 12,000 + 24,000 + 26,400 = GBP 62,400 plus service charge 3 x 3,600 = GBP 10,800, total GBP 73,200 (exclusive of VAT, service charge at the current budget), with the convention stated",
24
+ "Committed to the end of the term: rent 12,000 + 24,000 + 3 x 26,400 = GBP 115,200 plus service charge 5 x 3,600 = GBP 18,000, total GBP 133,200",
25
+ "Break notice must be served on or before 28 August 2029 (6 months before the 28 February 2030 break date), and the answer warns that the break fails if any sum is unpaid or vacant possession is not given",
26
+ "Flags at least two tenant-hostile terms: the director's personal guarantee for the whole term, the uncapped service charge, and full repairing with no schedule of condition",
27
+ "Does not invent a rent deposit, VAT rate, open-market rent review, or other terms not in the heads of terms"
28
+ ]
29
+ },
30
+ {
31
+ "id": 3,
32
+ "held_out": "2026-11-08",
33
+ "prompt": "We're Quokka Bio, a 9-person biotech, and this would be our first lab. Our landlord's agent sent heads-of-agreement.md and wants it signed off this week. Can you work out what we'd be committing to pay to the break and to the end of the term, give me every notice date we need in the calendar, and tell me which clauses I should push back on? Treat all money as ex GST, and use the outgoings estimate as flat for the whole term.",
34
+ "assertions": [
35
+ "Flags the conflict between '3 months rent-free from the commencement date' (rent would start 1 December 2027) and the stated rent commencement date of 1 November 2027 (only 2 months), says which it used and asks for it to be fixed",
36
+ "Gives the CPI reviews as a range using the collar and cap: from 1 September 2028 rent is A$97,920 (2%) to A$100,800 (5%); from 1 September 2029 A$99,878.40 (2% on 2%) to A$105,840 (5% on 5%); the minimum is what is committed",
37
+ "Committed to the 31 August 2029 break using the 3-month rent-free: rent 72,000 (9 months of year 1) + 97,920 = A$169,920 plus outgoings 2 x 14,400 = A$28,800, total A$198,720 at the 2% collar (A$201,600 at the 5% cap), plus a break fee of 3 months' rent of A$24,480 (A$25,200 at cap), so about A$223,200 to A$226,800 to exit at the break; accept figures recomputed consistently on the 2-month reading if the convention is stated",
38
+ "Committed to the end of the term (31 August 2030): rent 72,000 + 97,920 + 99,878.40 = A$269,798.40 plus outgoings 3 x 14,400 = A$43,200, total A$312,998.40 at the collar (A$321,840 at the cap)",
39
+ "Break notice must be given no later than 28 February 2029 (6 months before 31 August 2029; there is no 29 February in 2029); the option to renew can only be exercised between 28 February 2030 (accept 1 March 2030) and 31 May 2030",
40
+ "Flags as hostile to a small tenant at least: the break condition voided by any breach at notice or break date, the upward-only market review on renewal, the uncapped outgoings, and the make-good to base-building condition",
41
+ "Does not invent a CPI figure presented as fact, a guarantor, a fit-out contribution, or other terms not in the heads of agreement"
42
+ ]
43
+ }
44
+ ]
45
+ }
@@ -0,0 +1,15 @@
1
+ # Heads of terms — Unit 4, Foundry Yard, Example Town
2
+
3
+ - Landlord: Foundry Yard Estates Ltd
4
+ - Tenant: Brightloop Labs Ltd
5
+ - Premises: Unit 4, first floor, 2,100 sq ft
6
+ - Term: 5 years from 1 January 2027
7
+ - Rent: £36,000 a year exclusive, payable quarterly in advance
8
+ - Rent-free: first 3 months
9
+ - Rent review: at the end of year 3, to CPI over the preceding three years, compounded, with a cap of 4% a year and a collar of 1% a year; upward only
10
+ - Service charge: estimated £4,800 a year, no cap
11
+ - Deposit: 6 months' rent
12
+ - Tenant break: end of year 3, on 6 months' prior written notice, conditional on all sums due under the lease having been paid and vacant possession being given
13
+ - Repair: full repairing; no schedule of condition
14
+ - Alienation: no subletting or sharing
15
+ - Personal guarantee: from one director for the first 3 years
@@ -0,0 +1,32 @@
1
+ # Heads of Terms - Subject to Contract
2
+
3
+ **Property:** Unit 4, Gorse Mill, Canal Street, Halbrook HB3 7QT (ground floor studio, approx. 1,850 sq ft)
4
+
5
+ **Landlord:** Gorse Mill Estates Limited
6
+
7
+ **Tenant:** Fernlight Studio Ltd
8
+
9
+ **Guarantor:** Imogen Marsh (director), personal guarantee of all tenant obligations for the whole term
10
+
11
+ **Term:** 5 years from and including 1 March 2027, expiring on 1 March 2032
12
+
13
+ **Rent:**
14
+ - Years 1 and 2 (1 March 2027 to 28 February 2029): GBP 24,000 per annum exclusive
15
+ - Years 3 to 5 (1 March 2029 to end of term): fixed increase to GBP 26,400 per annum exclusive
16
+ - Payable quarterly in advance on the usual quarter days
17
+
18
+ **Rent-free:** The first 6 months of the term are rent-free. Service charge is payable from the start of the term and is not covered by the rent-free period.
19
+
20
+ **Service charge:** Tenant to pay a fair proportion of the landlord's costs of the estate. Current budget GBP 3,600 per annum. No cap.
21
+
22
+ **Repair:** Full repairing and insuring. Tenant to keep the premises in good and substantial repair and condition and yield up in that condition. No schedule of condition.
23
+
24
+ **Tenant break:** Tenant may terminate the lease at the end of the third year of the term (28 February 2030) by giving the landlord not less than 6 months' prior written notice. The break is conditional on: (i) all rent and other sums due under the lease having been paid in full, and (ii) the tenant giving vacant possession on the break date.
25
+
26
+ **Use:** Design studio and ancillary offices (Class E).
27
+
28
+ **Alienation:** No assignment or subletting of part. Assignment of the whole with landlord's consent, not to be unreasonably withheld, subject to an authorised guarantee agreement.
29
+
30
+ **Security of tenure:** The lease is to be contracted out of the security of tenure provisions of the Landlord and Tenant Act 1954.
31
+
32
+ **Costs:** Each party to bear its own legal costs.
@@ -0,0 +1,29 @@
1
+ # Heads of Agreement (non-binding) - Commercial Lease
2
+
3
+ **Premises:** Lot 7, 112 Wattle Street, Brunswick VIC 3056 - ground floor laboratory and office, approx. 410 m2
4
+
5
+ **Lessor:** Wattle Street Holdings Pty Ltd
6
+
7
+ **Lessee:** Quokka Bio Pty Ltd (ACN 600 000 000)
8
+
9
+ **Term:** 3 years commencing 1 September 2027 and expiring 31 August 2030
10
+
11
+ **Option to renew:** One further term of 3 years. The option must be exercised by written notice given not more than 6 months and not less than 3 months before the expiry date.
12
+
13
+ **Base rent:** A$96,000 per annum plus GST, payable monthly in advance.
14
+
15
+ **Rent-free (incentive):** 3 months' rent-free from the commencement date. Rent commencement date: 1 November 2027.
16
+
17
+ **Annual rent reviews:** On each anniversary of the commencement date (1 September 2028 and 1 September 2029) the base rent is reviewed by CPI (All Groups, Melbourne), subject to a minimum increase of 2% and a maximum increase of 5% per review.
18
+
19
+ **Review on renewal:** At the start of any option term the rent is reviewed to current market rent, provided the rent will not be less than the rent payable immediately before the review.
20
+
21
+ **Outgoings:** Lessee to pay 100% of the outgoings for the premises (rates, land tax, insurance, building services, common area cleaning and maintenance). Estimate for year 1: A$14,400 per annum plus GST. No cap on annual increases.
22
+
23
+ **Lessee break:** The lessee may end the lease on 31 August 2029 by giving not less than 6 months' written notice, provided that (a) the lessee is not in breach of any term of the lease at the date of the notice or at the break date, and (b) the lessee pays a break fee equal to 3 months' base rent at the rate payable at the break date.
24
+
25
+ **Make good:** At the end of the lease the lessee must remove all fit-out and laboratory services and return the premises to base-building condition, at its cost.
26
+
27
+ **Permitted use:** PC2 laboratory and ancillary office.
28
+
29
+ **Security:** Bank guarantee equal to 3 months' base rent plus GST.
@@ -0,0 +1,75 @@
1
+ # Worked example: heads of terms for a first office
2
+
3
+ ## Input
4
+
5
+ > *"We've been offered heads of terms for our first office
6
+ > ([inputs/heads-of-terms.md](inputs/heads-of-terms.md)). The agent wants an answer this week.
7
+ > What are we committing to, and when could we get out?"*
8
+
9
+ The terms extracted are [`../templates/terms.json`](../templates/terms.json).
10
+
11
+ ## What it computed
12
+
13
+ ```text
14
+ # Lease: dates and money
15
+
16
+ Term: 2027-01-01 to 2031-12-31 (60 months).
17
+
18
+ ## Deadlines
19
+
20
+ | Date | What | Detail |
21
+ |---|---|---|
22
+ | 2027-04-01 | rent starts | after 3 months rent-free |
23
+ | 2029-07-01 | tenant break notice | for the break on 2030-01-01; conditions: all rent paid; vacant possession |
24
+ | 2030-01-01 | rent review | cpi, upward only |
25
+ | 2031-06-30 | notice to leave at the end | for the end on 2031-12-31 |
26
+
27
+ ## Committed cost
28
+
29
+ | Up to | Rent | Service charge | Total |
30
+ |---|---:|---:|---:|
31
+ | to the break on 2030-01-01 | £99,000 | £14,400 | £113,400 |
32
+ | to the end of the term (2031-12-31) | £179,990 | £24,000 | £203,990 |
33
+
34
+ ## Rent by year
35
+
36
+ | Year | From | Yearly rent | Paid that year |
37
+ |---:|---|---:|---:|
38
+ | 1 | 2027-01-01 | £36,000 | £27,000 |
39
+ | 2 | 2028-01-01 | £36,000 | £36,000 |
40
+ | 3 | 2029-01-01 | £36,000 | £36,000 |
41
+ | 4 | 2030-01-01 | £40,495 | £40,495 |
42
+ | 5 | 2031-01-01 | £40,495 | £40,495 |
43
+
44
+ Deposit: £18,000.
45
+
46
+ WARN the break on 2030-01-01 is conditional (all rent paid; vacant possession): one unmet condition, such as a small unpaid sum, can make it fail
47
+ ```
48
+
49
+ ## Reply
50
+
51
+ **Signing commits Brightloop to £113,400 up to the break on 1 January 2030** (33 months' rent
52
+ after the rent-free, plus service charge), **and £203,990 if you stay to the end** on 31
53
+ December 2031. The break needs written notice by **1 July 2029**, and as drafted it fails if any
54
+ sum is unpaid or the unit isn't empty on the day, which would keep you in to 2031 for another
55
+ £90,590.
56
+
57
+ **Fixes before signing**
58
+ - 🟥 **Personal guarantee.** Ask for: *"No personal guarantee; the Tenant will provide a rent
59
+ deposit of six months' rent instead."* The deposit is already there.
60
+ - 🟥 **Break conditions.** Ask for: *"The only condition of the break is that the Tenant has paid
61
+ the principal rent due up to the break date."*
62
+ - 🟧 **Full repairing with no schedule of condition.** Ask for a photographic schedule of
63
+ condition, attached to the lease, and repair limited to that condition.
64
+ - 🟧 **Service charge with no cap.** Ask for a cap at £4,800 rising with CPI, and no charges for
65
+ improvements or replacing plant.
66
+ - 🟨 **No sharing.** Ask for the right to share with group companies without consent, and to
67
+ sublet with consent not to be unreasonably withheld.
68
+
69
+ **Deadlines to diarise**
70
+ - 1 April 2027: rent starts.
71
+ - 1 July 2029: last day to serve the break notice.
72
+ - 1 January 2030: rent review, to £40,495 a year at the capped 4 % (CPI below the cap would mean less).
73
+ - 30 June 2031: notice to leave at the end.
74
+
75
+ **Worth a lawyer's hour:** the personal guarantee, if the landlord won't drop it.
@@ -0,0 +1,15 @@
1
+ # Heads of terms — Unit 4, Foundry Yard, Example Town
2
+
3
+ - Landlord: Foundry Yard Estates Ltd
4
+ - Tenant: Brightloop Labs Ltd
5
+ - Premises: Unit 4, first floor, 2,100 sq ft
6
+ - Term: 5 years from 1 January 2027
7
+ - Rent: £36,000 a year exclusive, payable quarterly in advance
8
+ - Rent-free: first 3 months
9
+ - Rent review: at the end of year 3, to CPI over the preceding three years, compounded, with a cap of 4% a year and a collar of 1% a year; upward only
10
+ - Service charge: estimated £4,800 a year, no cap
11
+ - Deposit: 6 months' rent
12
+ - Tenant break: end of year 3, on 6 months' prior written notice, conditional on all sums due under the lease having been paid and vacant possession being given
13
+ - Repair: full repairing; no schedule of condition
14
+ - Alienation: no subletting or sharing
15
+ - Personal guarantee: from one director for the first 3 years
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "lease-check",
3
+ "version": "0.1.0",
4
+ "description": "Check a commercial lease before a startup signs it for its first office, studio or lab — a script turns the lease's terms into every date and sum the tenant must plan around (rent year by year through stepped rents, fixed and index-linked reviews with caps and collars, rent-free months, what the company is committed to pay up to each break and to the end, and each break, renewal and notice deadline as a date), then flags terms that can't all be true and deadlines already passed — and reads the lease for the clauses that hurt a small tenant. Use when a founder says \"review this office lease\", \"what's our break clause notice date\", \"how much are we committing to on this lease\", \"check this lease before I sign\", \"when can we get out of this lease\", or \"what does the rent review do\".",
5
+ "entrypoint": "SKILL.md",
6
+ "deps": {
7
+ "npm": [],
8
+ "pip": []
9
+ },
10
+ "env": {
11
+ "required": [],
12
+ "optional": []
13
+ },
14
+ "related": [],
15
+ "tags": [
16
+ "lease",
17
+ "office",
18
+ "rent-review",
19
+ "break-clause",
20
+ "startup",
21
+ "property"
22
+ ],
23
+ "permissions": {
24
+ "network": [],
25
+ "commands": [],
26
+ "files": "named-paths",
27
+ "notes": "Reads the terms.json it is given and writes nothing."
28
+ },
29
+ "model": {
30
+ "tier": "standard",
31
+ "rationale": "Extracting lease terms and reading clauses from the tenant's side is bounded; the script does the dates and sums."
32
+ }
33
+ }
@@ -0,0 +1,70 @@
1
+ # Lease clauses that hurt a small tenant
2
+
3
+ For each: what to look for, why it hurts, and wording to ask for. Adapt numbers to the deal.
4
+
5
+ ## Break clause
6
+
7
+ - **Look for:** conditions ("all rent paid", "material compliance with covenants", "vacant
8
+ possession"); notice in a strict form or to a specific address; a penalty; rent-free clawed
9
+ back on breaking.
10
+ - **Why it hurts:** courts apply break conditions strictly. A few pounds of unpaid service charge
11
+ or one item left behind can void the break and keep the tenant in to the end.
12
+ - **Ask for:** *"The only condition of the break is that the Tenant has paid the principal rent
13
+ due up to the break date."* Delete "material compliance" and "vacant possession" conditions, or
14
+ narrow vacant possession to "free of the Tenant's occupation".
15
+
16
+ ## Rent review
17
+
18
+ - **Look for:** open-market reviews (unknown increase); upward-only wording; index-linked reviews
19
+ that compound several years at once; no cap.
20
+ - **Ask for:** a fixed increase, or index-linked with a cap of 3–4 % a year (compounded) and no
21
+ collar; reviews no more often than every three years.
22
+
23
+ ## Repair, dilapidations and reinstatement
24
+
25
+ - **Look for:** "full repairing" obligations, especially on an older building; reinstatement of
26
+ all alterations at the end; no schedule of condition.
27
+ - **Why it hurts:** the tenant can be billed at the end to put the building in better condition
28
+ than when it moved in.
29
+ - **Ask for:** *"The Tenant is not required to put the Premises in better repair than shown in
30
+ the attached Schedule of Condition."* Photograph and attach it. Reinstatement only of
31
+ alterations the tenant made, and only if the landlord asks six months before the end.
32
+
33
+ ## Service charge
34
+
35
+ - **Look for:** no cap; capital improvements or replacement of plant charged to tenants;
36
+ management fees as a percentage with no limit.
37
+ - **Ask for:** a cap at a stated sum rising with CPI; no charges for improvements, replacing
38
+ structure or plant, or remedying defects in the original build.
39
+
40
+ ## Alienation (sharing, subletting, assigning)
41
+
42
+ - **Look for:** an absolute ban on sharing or subletting; consent needed to share with a group
43
+ company.
44
+ - **Ask for:** the right to share with group companies without consent, and to sublet or assign
45
+ with the landlord's consent "not to be unreasonably withheld or delayed".
46
+
47
+ ## Guarantees and deposits
48
+
49
+ - **Look for:** a personal guarantee from a founder; a deposit of more than six months' rent;
50
+ no terms for its return.
51
+ - **Ask for:** no personal guarantee; a deposit of three to six months' rent, held in a
52
+ separate account, returned within 28 days of the end, and released early after two years of
53
+ on-time payment.
54
+
55
+ ## Use, alterations and insurance
56
+
57
+ - **Look for:** a narrow permitted use that blocks growth (no lab use, no events); consent needed
58
+ for any internal alteration; insurance excluding terrorism or uninsured risks.
59
+ - **Ask for:** use "as offices and any other use within the same planning class"; internal
60
+ non-structural alterations without consent; rent suspended if the premises can't be used
61
+ after damage, including by uninsured risks, with a right to terminate if not restored within
62
+ a year.
63
+
64
+ ## When a lawyer earns the fee
65
+
66
+ - a personal guarantee;
67
+ - full repairing terms on a building older than about 20 years;
68
+ - a break with conditions the landlord won't narrow;
69
+ - a lease that excludes statutory renewal protection where the law gives it;
70
+ - a long term (over five years) with open-market reviews.
@@ -0,0 +1,276 @@
1
+ #!/usr/bin/env python3
2
+ """Turn a commercial lease's terms into the dates and money a tenant has to plan around. Stdlib only.
3
+
4
+ Reads a terms.json (start, length, rent and how it changes, rent-free months, service charge,
5
+ deposit, break clauses, renewal option, notice periods) and prints:
6
+ - rent year by year through stepped rents, fixed and index-linked reviews (with any cap and
7
+ collar), rent-free months and upward-only reviews
8
+ - what the tenant is committed to pay up to each break date and to the end of the term,
9
+ rent and service charge together
10
+ - every deadline as a date: each break notice, the renewal option's window, notice to leave,
11
+ each rent review, the end of the rent-free period
12
+ - deadlines already passed on the given date, and terms that can't all be true (a break
13
+ after the end of the term, a notice longer than the time left, rent-free longer than the term)
14
+ An open-market review can't be priced in advance; it is shown as unknown from that date on.
15
+
16
+ Usage:
17
+ python3 lease_terms.py terms.json [--as-of 2027-03-01] [--json]
18
+ Exit codes: 0 computed, 1 a deadline passed or the terms contradict each other, 2 bad input.
19
+ """
20
+ import argparse
21
+ import calendar
22
+ import datetime as dt
23
+ import json
24
+ import math
25
+ import sys
26
+
27
+
28
+ class InputError(Exception):
29
+ pass
30
+
31
+
32
+ def day(v, what):
33
+ try:
34
+ return dt.date.fromisoformat(str(v))
35
+ except ValueError:
36
+ raise InputError(f"{what} must be a date as YYYY-MM-DD")
37
+
38
+
39
+ def add_months(d, n):
40
+ y, m = divmod(d.year * 12 + d.month - 1 + n, 12)
41
+ return dt.date(y, m + 1, min(d.day, calendar.monthrange(y, m + 1)[1]))
42
+
43
+
44
+ def num(v, what, lo=0.0):
45
+ if isinstance(v, bool) or not isinstance(v, (int, float)) or not math.isfinite(v) or v < lo:
46
+ raise InputError(f"{what} must be a number of at least {lo:g}")
47
+ return float(v)
48
+
49
+
50
+ def rate(v, what):
51
+ v = num(v, what, -100)
52
+ return v / 100 if abs(v) >= 1 else v
53
+
54
+
55
+ def whole(v, what, lo=0):
56
+ if isinstance(v, bool) or not isinstance(v, int) or v < lo:
57
+ raise InputError(f"{what} must be a whole number of months")
58
+ return v
59
+
60
+
61
+ def load(d):
62
+ if not isinstance(d, dict):
63
+ raise InputError("terms.json must be an object")
64
+ start = day(d.get("start"), "start")
65
+ term = whole(d.get("term_months"), "term_months", 1)
66
+ rent = d.get("rent") or {}
67
+ if not isinstance(rent, dict) or "annual" not in rent:
68
+ raise InputError("`rent` needs `annual`, the starting yearly rent")
69
+ reviews = []
70
+ for i, r in enumerate(rent.get("reviews") or [], 1):
71
+ kind = str(r.get("type", "fixed")).lower()
72
+ if kind not in ("fixed", "index", "cpi", "rpi", "open market", "market"):
73
+ raise InputError(f"review {i}: type must be fixed, index (cpi/rpi) or open market")
74
+ rv = {"month": whole(r.get("month"), f"review {i}: month", 1), "type": kind,
75
+ "upward_only": bool(r.get("upward_only", True))}
76
+ if kind == "fixed":
77
+ rv["increase"] = rate(r.get("increase", 0), f"review {i}: increase")
78
+ elif kind in ("index", "cpi", "rpi"):
79
+ rv["assumed"] = rate(r.get("assumed", 0), f"review {i}: assumed index rise")
80
+ rv["cap"] = rate(r["cap"], f"review {i}: cap") if r.get("cap") is not None else None
81
+ rv["collar"] = rate(r["collar"], f"review {i}: collar") if r.get("collar") is not None else None
82
+ rv["compounded_years"] = whole(r.get("years", 1), f"review {i}: years", 1)
83
+ reviews.append(rv)
84
+ steps = [{"month": whole(s.get("from_month"), "stepped rent: from_month", 1),
85
+ "annual": num(s.get("annual"), "stepped rent: annual")} for s in rent.get("stepped") or []]
86
+ breaks = []
87
+ for i, b in enumerate(d.get("breaks") or [], 1):
88
+ bdate = day(b["date"], f"break {i}: date") if b.get("date") else None
89
+ if bdate is None and b.get("month") is None:
90
+ raise InputError(f"break {i}: give `month` (months from the start) or the lease's own `date`")
91
+ if bdate is not None:
92
+ months = (bdate.year - start.year) * 12 + bdate.month - start.month + (1 if bdate.day >= start.day - 1 else 0)
93
+ b = dict(b, month=b.get("month", max(1, months)))
94
+ breaks.append({"month": whole(b.get("month"), f"break {i}: month", 1), "date": bdate,
95
+ "notice": whole(b.get("notice_months"), f"break {i}: notice_months", 0),
96
+ "party": str(b.get("party", "tenant")),
97
+ "conditions": [str(c) for c in b.get("conditions") or []],
98
+ "penalty_months": num(b.get("penalty_months", 0), f"break {i}: penalty_months")})
99
+ sc = d.get("service_charge") or {}
100
+ dep = d.get("deposit") or {}
101
+ renewal = d.get("renewal_option")
102
+ return {
103
+ "currency": str(d.get("currency", "GBP")), "start": start, "term": term,
104
+ "annual": num(rent["annual"], "rent.annual"), "payable": str(rent.get("payable", "quarterly")).lower(),
105
+ "rent_free": whole(rent.get("rent_free_months", 0), "rent_free_months"),
106
+ "reviews": sorted(reviews, key=lambda r: r["month"]), "steps": sorted(steps, key=lambda s: s["month"]),
107
+ "service": num(sc.get("annual", 0), "service_charge.annual"),
108
+ "service_cap": num(sc["cap"], "service_charge.cap") if sc.get("cap") is not None else None,
109
+ "deposit": num(dep["amount"], "deposit.amount") if dep.get("amount") is not None else (
110
+ num(dep.get("months", 0), "deposit.months") / 12 * num(rent["annual"], "rent.annual")),
111
+ "breaks": sorted(breaks, key=lambda b: b["month"]),
112
+ "renewal": None if not renewal else {
113
+ "notice": whole(renewal.get("notice_months"), "renewal_option.notice_months", 0),
114
+ "earliest": whole(renewal.get("earliest_months_before_end", renewal.get("notice_months", 0)),
115
+ "renewal_option.earliest_months_before_end", 0)},
116
+ "leave_notice": whole(d["notice_to_leave_months"], "notice_to_leave_months")
117
+ if d.get("notice_to_leave_months") is not None else None,
118
+ }
119
+
120
+
121
+ def rent_by_month(t):
122
+ """Yearly rate in force for each month, and None once an open-market review makes it unknown."""
123
+ rates = []
124
+ current = t["annual"]
125
+ steps = {s["month"]: s["annual"] for s in t["steps"]}
126
+ reviews = {r["month"]: r for r in t["reviews"]}
127
+ notes = []
128
+ for m in range(t["term"]):
129
+ if m in steps and current is not None:
130
+ current = steps[m]
131
+ if m in reviews and m > 0 and current is not None:
132
+ r = reviews[m]
133
+ if r["type"] == "fixed":
134
+ new = current * (1 + r["increase"])
135
+ elif r["type"] in ("index", "cpi", "rpi"):
136
+ rise = (1 + r["assumed"]) ** r["compounded_years"] - 1
137
+ lo = ((1 + r["collar"]) ** r["compounded_years"] - 1) if r["collar"] is not None else None
138
+ hi = ((1 + r["cap"]) ** r["compounded_years"] - 1) if r["cap"] is not None else None
139
+ if hi is not None:
140
+ rise = min(rise, hi)
141
+ if lo is not None:
142
+ rise = max(rise, lo)
143
+ new = current * (1 + rise)
144
+ else:
145
+ new = None
146
+ notes.append(f"open-market review at month {m}: rent unknown from then; budget for an increase")
147
+ if new is not None and r["upward_only"]:
148
+ new = max(new, current)
149
+ current = new
150
+ rates.append(current)
151
+ return rates, notes
152
+
153
+
154
+ def cost(t, rates, months):
155
+ """Rent (after rent-free) and service charge payable in the first `months` months."""
156
+ rent = 0.0
157
+ unknown = False
158
+ for m in range(min(months, t["term"])):
159
+ if m < t["rent_free"]:
160
+ continue
161
+ if rates[m] is None:
162
+ unknown = True
163
+ continue
164
+ rent += rates[m] / 12
165
+ service = t["service"] * min(months, t["term"]) / 12
166
+ if t["service_cap"] is not None:
167
+ service = min(service, t["service_cap"] * min(months, t["term"]) / 12)
168
+ return rent, service, unknown
169
+
170
+
171
+ def analyse(t, as_of):
172
+ rates, notes = rent_by_month(t)
173
+ end = add_months(t["start"], t["term"]) - dt.timedelta(days=1) # a 5-year lease from 1 Jan ends on 31 Dec
174
+ fails, warns, deadlines = [], [], []
175
+ if t["rent_free"] >= t["term"]:
176
+ fails.append("the rent-free period is as long as the term")
177
+ if t["rent_free"]:
178
+ deadlines.append(("rent starts", add_months(t["start"], t["rent_free"]), f"after {t['rent_free']} months rent-free"))
179
+ for r in t["reviews"]:
180
+ when = add_months(t["start"], r["month"])
181
+ if r["month"] >= t["term"]:
182
+ fails.append(f"a rent review at month {r['month']} falls after the lease ends")
183
+ deadlines.append(("rent review", when, r["type"] + (", upward only" if r["upward_only"] else "")))
184
+ commitments = []
185
+ for b in t["breaks"]:
186
+ bdate = b["date"] or add_months(t["start"], b["month"])
187
+ notice_by = add_months(bdate, -b["notice"])
188
+ if b["month"] >= t["term"]:
189
+ fails.append(f"a break at month {b['month']} ({bdate}) is after the lease ends ({end})")
190
+ continue
191
+ if notice_by < t["start"]:
192
+ fails.append(f"the break on {bdate} needs {b['notice']} months' notice, which runs back before the lease starts")
193
+ deadlines.append((f"{b['party']} break notice", notice_by, f"for the break on {bdate}"
194
+ + (f"; conditions: {'; '.join(b['conditions'])}" if b["conditions"] else "")))
195
+ rent, service, unknown = cost(t, rates, b["month"])
196
+ penalty = (rates[b["month"] - 1] or 0) / 12 * b["penalty_months"] if b["penalty_months"] else 0
197
+ commitments.append((f"to the break on {bdate}", rent + penalty, service, unknown, penalty))
198
+ if b["party"].lower() in ("landlord", "mutual", "either"):
199
+ warns.append(f"the landlord can end the lease on {bdate}; plan for having to move then")
200
+ if b["conditions"]:
201
+ warns.append(f"the break on {bdate} is conditional ({'; '.join(b['conditions'])}): one unmet condition, "
202
+ "such as a small unpaid sum, can make it fail")
203
+ rent, service, unknown = cost(t, rates, t["term"])
204
+ commitments.append((f"to the end of the term ({end})", rent, service, unknown, 0))
205
+ if t["renewal"]:
206
+ latest = add_months(end, -t["renewal"]["notice"])
207
+ earliest = add_months(end, -t["renewal"]["earliest"])
208
+ deadlines.append(("renewal option", latest, f"window opens {earliest}" if earliest < latest else "exact date"))
209
+ if t["leave_notice"] is not None:
210
+ deadlines.append(("notice to leave at the end", add_months(end, -t["leave_notice"]), f"for the end on {end}"))
211
+ deadlines.sort(key=lambda d: d[1])
212
+ for what, when, _ in deadlines:
213
+ if as_of and when < as_of and what not in ("rent review", "rent starts"):
214
+ fails.append(f"the {what} deadline ({when}) has already passed")
215
+ years = []
216
+ for y in range(math.ceil(t["term"] / 12)):
217
+ ms = range(12 * y, min(12 * (y + 1), t["term"]))
218
+ known = [rates[m] for m in ms if rates[m] is not None]
219
+ paid = sum((rates[m] or 0) / 12 for m in ms if m >= t["rent_free"])
220
+ years.append({"year": y + 1, "from": add_months(t["start"], 12 * y),
221
+ "rate": known[-1] if len(known) == len(ms) else None, "rent_paid": paid,
222
+ "unknown": len(known) < len(ms)})
223
+ return {"end": end, "years": years, "commitments": commitments, "deadlines": deadlines,
224
+ "notes": notes, "fails": fails, "warns": warns, "deposit": t["deposit"]}
225
+
226
+
227
+ def money(v, cur):
228
+ sym = {"GBP": "£", "USD": "$", "EUR": "€", "INR": "₹", "AUD": "A$", "CAD": "C$"}.get(cur.upper(), "")
229
+ return f"{sym}{v:,.0f}" if sym else f"{v:,.0f} {cur}"
230
+
231
+
232
+ def report(t, r):
233
+ cur = t["currency"]
234
+ L = ["# Lease: dates and money", "", f"Term: {t['start']} to {r['end']} ({t['term']} months).", "",
235
+ "## Deadlines", "", "| Date | What | Detail |", "|---|---|---|"]
236
+ for what, when, detail in r["deadlines"]:
237
+ L.append(f"| {when} | {what} | {detail} |")
238
+ L += ["", "## Committed cost", "", "| Up to | Rent | Service charge | Total |", "|---|---:|---:|---:|"]
239
+ for label, rent, service, unknown, penalty in r["commitments"]:
240
+ flag = " + unknown open-market rent" if unknown else ""
241
+ pen = f" (incl. {money(penalty, cur)} break penalty)" if penalty else ""
242
+ L.append(f"| {label} | {money(rent, cur)}{pen}{flag} | {money(service, cur)} | {money(rent + service, cur)}{flag} |")
243
+ L += ["", "## Rent by year", "", "| Year | From | Yearly rent | Paid that year |", "|---:|---|---:|---:|"]
244
+ for y in r["years"]:
245
+ rate_s = money(y["rate"], cur) if y["rate"] is not None else "unknown (open market)"
246
+ L.append(f"| {y['year']} | {y['from']} | {rate_s} | {money(y['rent_paid'], cur)}{' + unknown' if y['unknown'] else ''} |")
247
+ if r["deposit"]:
248
+ L += ["", f"Deposit: {money(r['deposit'], cur)}."]
249
+ L += [""] + [f"NOTE {n}" for n in r["notes"]]
250
+ L += [f"FAIL {f}" for f in r["fails"]] + [f"WARN {w}" for w in r["warns"]]
251
+ return "\n".join(L).rstrip() + "\n"
252
+
253
+
254
+ def main(argv=None):
255
+ ap = argparse.ArgumentParser(description="A lease's dates and money.")
256
+ ap.add_argument("terms")
257
+ ap.add_argument("--as-of", help="YYYY-MM-DD; deadlines before it are reported as passed")
258
+ ap.add_argument("--json", action="store_true")
259
+ args = ap.parse_args(argv)
260
+ try:
261
+ with open(args.terms, encoding="utf-8-sig") as f:
262
+ t = load(json.load(f))
263
+ as_of = day(args.as_of, "--as-of") if args.as_of else None
264
+ except (OSError, ValueError, UnicodeDecodeError) as e:
265
+ print(f"FAIL cannot read input: {e}")
266
+ return 2
267
+ except InputError as e:
268
+ print(f"FAIL {e}")
269
+ return 2
270
+ r = analyse(t, as_of)
271
+ print(json.dumps(r, indent=2, default=str) if args.json else report(t, r), end="" if not args.json else "\n")
272
+ return 1 if r["fails"] else 0
273
+
274
+
275
+ if __name__ == "__main__":
276
+ sys.exit(main())
@@ -0,0 +1,15 @@
1
+ {
2
+ "currency": "GBP",
3
+ "start": "2027-01-01",
4
+ "term_months": 60,
5
+ "rent": {
6
+ "annual": 36000,
7
+ "payable": "quarterly",
8
+ "rent_free_months": 3,
9
+ "reviews": [{"month": 36, "type": "cpi", "assumed": 0.04, "cap": 0.04, "collar": 0.01, "years": 3, "upward_only": true}]
10
+ },
11
+ "service_charge": {"annual": 4800},
12
+ "deposit": {"months": 6},
13
+ "breaks": [{"month": 36, "notice_months": 6, "party": "tenant", "conditions": ["all rent paid", "vacant possession"]}],
14
+ "notice_to_leave_months": 6
15
+ }