Tyche Lite â Free Invoice Preview
Track up to 5 invoices and get a friendly reminder template.
Free vs Pro
| Feature | Tyche Lite (Free) | Tyche Pro |
|---|---|---|
| Invoices | 5 max | Unlimited |
| Tax calculation | â | â |
| Late fee calculation | â | â |
| Reminder tiers | 1 (friendly only) | 3 tiers |
| Multi-currency | â | â |
| Project grouping | â | â |
| Aged receivables | â | â 30/60/90d |
| Analytics export | â | â |
ð Upgrade: openclaw skills install tyche-pro â key at ko-fi.com/occupythemilkyway
Step 1 â Install
pip3 install rich --break-system-packages --quiet
Step 2 â Invoice overview (Lite)
import os, csv, re
from datetime import datetime, timedelta
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich import box
console = Console()
INVOICES_FILE = os.environ.get("INVOICES_FILE","").strip()
YOUR_NAME = os.environ.get("YOUR_NAME","Your Name")
CURRENCY = os.environ.get("CURRENCY","USD").upper()
SYM = {"USD":"$","EUR":"â¬","GBP":"£"}.get(CURRENCY,"$")
INVOICE_LIMIT = 5 # Lite cap
now = datetime.now()
def fmt(a): return f"{SYM}{a:,.2f}"
def parse_amount(r):
c = re.sub(r"[^0-9.]","",str(r)) or "0"
try: return float(c) if c.count(".")<=1 else 0.0
except: return 0.0
def parse_date(raw):
for f in ("%Y-%m-%d","%m/%d/%Y","%d/%m/%Y"):
try: return datetime.strptime(raw.strip(),f)
except: pass
return None
invoices = []
if INVOICES_FILE and os.path.exists(INVOICES_FILE):
with open(INVOICES_FILE,encoding="utf-8",errors="replace") as f:
reader = csv.DictReader(f)
for i,row in enumerate(reader,1):
if i > INVOICE_LIMIT:
console.print(f"[yellow]â ï¸ Lite limit: showing first {INVOICE_LIMIT} invoices only. Upgrade to Pro for unlimited.[/yellow]")
break
rk = {k.lower().strip():v.strip() for k,v in row.items()}
invoices.append({
"inv_number": rk.get("inv_number",f"INV-{i:04d}"),
"client_name": rk.get("client_name","Client"),
"client_email": rk.get("client_email",""),
"description": rk.get("description","Services"),
"amount": parse_amount(rk.get("amount","0")),
"due_date": rk.get("due_date",""),
"status": rk.get("status","unpaid").lower(),
})
else:
console.print("[yellow]â¹ï¸ No INVOICES_FILE â using demo data.[/yellow]\n")
invoices = [
{"inv_number":"INV-0001","client_name":"Acme Corp","client_email":"billing@acme.com","description":"Website redesign","amount":2500,"due_date":(now-timedelta(days=15)).strftime("%Y-%m-%d"),"status":"overdue"},
{"inv_number":"INV-0002","client_name":"Globex Inc","client_email":"ap@globex.com","description":"Consulting","amount":1800,"due_date":(now+timedelta(days=10)).strftime("%Y-%m-%d"),"status":"unpaid"},
{"inv_number":"INV-0003","client_name":"Initech Ltd","client_email":"pay@initech.com","description":"Logo design","amount":750,"due_date":(now-timedelta(days=5)).strftime("%Y-%m-%d"),"status":"paid"},
]
for inv in invoices:
due = parse_date(inv["due_date"])
inv["due_dt"] = due
inv["days_late"] = max(0,(now-due).days) if due and inv["status"] in ("overdue","unpaid") else 0
total_invoiced = sum(i["amount"] for i in invoices)
total_paid = sum(i["amount"] for i in invoices if i["status"]=="paid")
total_outstanding = total_invoiced - total_paid
console.print()
console.print(Panel.fit(
f"[bold yellow]âï¸ Tyche Lite â Invoice Overview[/bold yellow]\n"
f"Invoiced: [white]{fmt(total_invoiced)}[/white] Paid: [green]{fmt(total_paid)}[/green] Outstanding: [red]{fmt(total_outstanding)}[/red]\n"
f"[dim]Lite: showing {len(invoices)}/{INVOICE_LIMIT} invoices max[/dim]",
border_style="yellow"
))
SC = {"paid":"green","unpaid":"yellow","overdue":"red","partial":"cyan"}
console.print()
tbl = Table(title="Invoice Status", box=box.ROUNDED, border_style="yellow")
tbl.add_column("Inv #", width=10, style="dim")
tbl.add_column("Client", width=18, style="cyan")
tbl.add_column("Amount", width=12, justify="right")
tbl.add_column("Due", width=12, style="dim")
tbl.add_column("Late", width=8, style="red", justify="right")
tbl.add_column("Status", width=10)
for inv in sorted(invoices, key=lambda x: -(x["days_late"] or 0)):
sc = SC.get(inv["status"],"white")
late = f"{inv['days_late']}d" if inv["days_late"] else "â"
tbl.add_row(inv["inv_number"],inv["client_name"][:16],fmt(inv["amount"]),inv["due_date"],late,f"[{sc}]{inv['status'].title()}[/{sc}]")
console.print(tbl)
# 1 reminder (Lite: friendly only)
overdue = [i for i in invoices if i["days_late"] > 0]
if overdue:
inv = overdue[0]
console.print()
console.print(Panel(
f"To: {inv['client_email']}\nSubject: Friendly Reminder â Invoice {inv['inv_number']}\n\n"
f"Dear {inv['client_name']},\n\n"
f"I hope you're well. Invoice {inv['inv_number']} for {fmt(inv['amount'])} was due {inv['due_date']}. "
f"Could you confirm when payment will be processed?\n\n"
f"Kind regards,\n{YOUR_NAME}",
title=f"[yellow]Friendly Reminder â {inv['client_name']} ({inv['days_late']}d overdue)[/yellow]",
border_style="yellow"
))
if len(overdue) > 1:
console.print(f"[dim]+ {len(overdue)-1} more overdue invoice(s) â upgrade to Pro for all reminder tiers.[/dim]")
console.print()
console.print(Panel(
f"[bold yellow]ð Want more?[/bold yellow]\n\n"
f"Tyche Pro handles [bold]unlimited invoices[/bold], calculates [bold]late fees & tax[/bold], "
f"sends [bold]3-tier reminders[/bold], groups by project, and gives you an aged receivables dashboard.\n\n"
f"[bold cyan]openclaw skills install tyche-pro[/bold cyan]\n"
f"Get your key â [bold]ko-fi.com/occupythemilkyway[/bold]",
title="Upgrade to Tyche Pro",
border_style="cyan"
))