Tracks receipts and expenses — captures, categorizes, and stores expense records in ~/expenses/YYYY/MM/receipts.json with Israeli VAT compliance and multi-currency support. Trigger on: 'add expense', 'log receipt', 'track this expense', 'I spent', 'receipt', 'expense report', 'how much did I spend', 'monthly expenses', 'track spending', 'log this purchase', 'add receipt', 'expense this'. Even if the user just says 'coffee 15 NIS' or 'track that last purchase', use this skill. Do NOT use for budgeting or financial planning — use a dedicated budgeting tool. Do NOT use for invoice generation or sending — use an invoicing tool. Do NOT use for bank reconciliation or importing bank statements.
From tandemnpx claudepluginhub binatrixai/tandem-marketplace --plugin tandemThis skill uses the workspace's default tool permissions.
evals/evals.jsonreferences/categories.mdreferences/currency.mdreferences/reports.mdreferences/schema.mdreferences/vat-rules.mdExecutes pre-written implementation plans: critically reviews, follows bite-sized steps exactly, runs verifications, tracks progress with checkpoints, uses git worktrees, stops on blockers.
Guides idea refinement into designs: explores context, asks questions one-by-one, proposes approaches, presents sections for approval, writes/review specs before coding.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
Captures, categorizes, and stores expense records in ~/expenses/ as monthly JSON files. Three capture modes: manual text entry (Phase 25+), photo OCR (Phase 26+), email paste (Phase 27+). All records use a unified 17-field JSON schema (see references/schema.md). All monetary values stored as integer agorot — never floats. Directory auto-created on first use.
On any expense action, check if ~/expenses/ exists. If not:
Before each write, auto-create the current month's directory:
~/expenses/YYYY/MM/ (e.g., ~/expenses/2026/03/)
Never prompt the user to create directories — create them silently.
ALWAYS use the atomic write pattern. Never write directly to receipts.json.
Steps for every save:
Step 6 is atomic on any standard filesystem. A crash before step 6 leaves the existing receipts.json intact.
Generate a deterministic ID for each expense:
EXP-YYYY-MMDD-NNN where NNN is a 3-digit sequence starting at 001 per dayEXP-2026-0320-001Full field spec in references/schema.md. Quick reference:
| Field | Type | Example |
|---|---|---|
| id | string | "EXP-2026-0320-001" |
| date | string (YYYY-MM-DD) | "2026-03-20" |
| vendor | string | "Cafe Aroma" |
| amount_agorot | integer | 4590 (= 45.90 NIS) |
| currency | string | "NIS" |
| nis_amount | integer | 4590 |
| exchange_rate | number or null | null (NIS) or 3.75 (USD) |
| category | string | "food" |
| type | string | "business" or "personal" |
| client | string or null | "Acme Ltd" |
| project | string or null | "website-redesign" |
| vat_amount | integer | 706 (= 7.06 NIS VAT) |
| vat_eligible | boolean | true |
| description | string | "Client lunch" |
| source | string | "manual", "ocr", or "email" |
| receipt_path | string or null | "2026/03/receipts/EXP-2026-0320-001.jpg" |
Quick classification (top-level only — full taxonomy in references/categories.md):
For type: default to "business" unless the expense is clearly personal. Ask user to confirm when ambiguous.
Run these steps during every capture's confirmation phase — after amount is confirmed, before atomic write.
VAT calculation — run during every capture:
1. Determine gross_agorot (the nis_amount in agorot)
2. vat_agorot = gross_agorot - floor(gross_agorot / 1.18)
3. Set vat_amount = vat_agorot
4. Set vat_eligible = true if type is "business", false if "personal"
Also false if vendor is a foreign entity (billed in foreign currency — no Israeli VAT)
See references/vat-rules.md for full eligibility rules
350 NIS threshold — check during confirmation:
If nis_amount > 35000 agorot AND vat_eligible is true:
Display in confirmation block:
"[WARNING] Amount exceeds 350 NIS — formal tax invoice (חשבונית מס) required to reclaim VAT.
Do you have a tax invoice? (yes / no)"
User "yes" → keep vat_eligible: true
User "no" / "receipt only" → set vat_eligible: false; append "[receipt only — no חשבונית מס]" to description
Multi-currency — run when currency is NOT NIS:
1. User provides amount in foreign currency (e.g., "50 USD")
2. NOTE: Direct API calls are not available in this environment (see METHODOLOGY.md — No Direct API Calls rule).
Automated exchange rate lookup is pending API Gateway implementation (see docs/architecture/api-gateway-design.md, planned for v11.0).
3. Ask user: "What is the current {CURRENCY}/ILS exchange rate?"
- If user provides rate: use it for conversion; note "rate: user-provided" in description
- If user says "skip" or "I don't know": store in original currency only, mark nis_amount as null, note "rate: pending" in description
4. When rate is available: exchange_rate = user-provided rate
5. nis_amount = floor(amount_agorot * exchange_rate) [integer math]
6. Store both: amount_agorot (original in minor units), exchange_rate, nis_amount
7. Use nis_amount for all VAT calculations and reports — never recalculate at report time
Confirmation display — currency section: Add to confirmation block when foreign currency:
Amount: {X.XX} {CURRENCY} (Rate: {exchange_rate}, NIS: {nis_amount / 100:.2f})
Full eligibility rules: see references/vat-rules.md. Currency handling details: see references/currency.md.
Triggers: "add expense", "I spent", "log receipt", "track this", "expense this", or any phrase matching the skill description. Also triggers on bare input like "coffee 15 NIS".
Accept tokens in any order: [vendor] [amount] [currency] [@category] [#client] [biz|personal]
[inferred]Show this summary before every save. User must respond before any write occurs.
Ready to save:
Vendor: [vendor]
Amount: [X.XX NIS] (or [X.XX USD = Y.YY NIS] for foreign currency)
Category: [category] [inferred] if not from @tag
Type: [business|personal]
Client: [client or —]
Date: [today's date]
Source: manual
[Warnings, e.g.: "Amount exceeds 350 NIS — tax invoice (חשבונית מס) may be required"]
Confirm? (yes / edit [field] [value] / cancel)
Edit inline: User says "edit vendor Cafe Aroma" or "edit category office" → update field, re-display summary. Repeat until user says "yes" or "cancel".
vat_agorot = gross_agorot - floor(gross_agorot / 1.18)For foreign currency: ask user for exchange rate (see Multi-currency section) before confirmation, display conversion in summary, store exchange_rate and nis_amount on record.
| Situation | Handling |
|---|---|
| Missing vendor | Prompt: "What's the vendor name?" |
| Amount over 350 NIS | Warn in confirmation summary; still allow save |
| Foreign currency, API unavailable | Prompt: "Enter exchange rate for [currency] → NIS" |
| Ambiguous type (could be biz or personal) | Show both options in confirmation; ask user to choose |
| Category not in canonical list | Reject @tag; show list of valid categories |
For deeper parsing patterns or additional edge cases, see references/capture-parsing.md (planned for Phase 26+).
Triggers: User provides a file path to a receipt image, or says "scan this receipt" / "OCR this receipt" with an image path. Supported formats: JPEG, PNG, PDF (single-page).
Use the Read tool with the provided file path. The Read tool supports image files natively — Claude processes the image visually. No external OCR service or library is needed. For Hebrew receipts: read Hebrew text as-is; transliterate vendor name to ASCII only if user explicitly prefers. For PDFs: read first page only; note if receipt appears to span multiple pages.
Extract these fields and assign a confidence level to each:
| Field | What to look for |
|---|---|
| vendor | Merchant name — usually largest text or header on receipt |
| date | Purchase date — check multiple positions on the receipt |
| amount | Final grand total — look for "סה"כ לתשלום" or "Total"; NOT a subtotal or line item |
| currency | Usually NIS/₪ for Israeli receipts; note if ambiguous |
| description | Summary of items if legible; otherwise "grocery receipt" or similar |
Confidence levels:
Multiple totals visible (subtotal + tax + tip): always pick the final "Total" or "סה"כ לתשלום" line.
Show the same format as ## Manual Capture, extended with confidence indicators:
Scanned receipt — please confirm:
Vendor: [vendor] ([confidence])
Date: [date] ([confidence])
Amount: [X.XX NIS] ([confidence])
Category: [inferred] (please confirm)
Type: [business|personal]
Client: [—]
[List any fields rated "uncertain" as warnings:]
⚠ Date could not be read clearly — verify before saving
Confirm? (yes / edit [field] [value] / cancel)
If any field is "uncertain", add a warning line. Do NOT silently save uncertain amounts — always pause for user review. If the image is unreadable (too dark, blurry): respond "Could not extract fields from image. Please try manual entry or retake the photo." and stop. If amount is visible but vendor is not: proceed with confirmation, prompt for vendor name.
Before the atomic write, copy the receipt image:
mkdir -p ~/expenses/YYYY/MM/receipts/
cp [source_path] ~/expenses/YYYY/MM/receipts/[ID].[ext]
Set receipt_path in the record to the relative path: "YYYY/MM/receipts/EXP-YYYY-MMDD-NNN.[ext]"
source: "ocr"receipt_path: relative path set in Step 4For deeper OCR extraction guidance, see references/ocr-guidelines.md (planned for future phases).
Triggers: User pastes email content (HTML or plain text) into the conversation, or provides a file path to a saved .eml or .txt file containing the email.
Forwarded emails contain metadata before the actual receipt content. Skip past any of these markers before parsing:
Parse the content AFTER the forwarding header block — not the header itself.
Extract from the email body:
| Field | What to look for |
|---|---|
| vendor | Sender name or company name — From: display name, or header logo/company name in HTML |
| date | Order/purchase date — look for "Order Date", "Date", "תאריך"; NOT the forwarding date |
| amount | Final total charge — target labels: "Order Total", "Total", "Grand Total", "Amount Due", "Total Charged", "סה"כ לתשלום", "סכום לחיוב", "סה"כ"; do NOT use subtotals or line item prices |
| currency | From symbol or code near the total; default NIS if not specified |
| description | Subject line of the email, or brief item summary (e.g., "Wolt food order", "Amazon order #123-456") |
For HTML emails: parse visible text content — focus on table cells and large text amounts; ignore CSS values and image src attributes.
Common vendor patterns:
Use the same confirmation format as ## Manual Capture:
Email receipt parsed — please confirm:
Vendor: [vendor]
Date: [date]
Amount: [X.XX NIS]
Category: [inferred] (please confirm)
Type: [business|personal]
Client: [—]
Source: email
Confirm? (yes / edit [field] [value] / cancel)
If vendor or amount could not be extracted, prompt user to fill them before showing confirmation.
source: "email"receipt_path: null (no image in this capture mode)| Situation | Handling |
|---|---|
| Multiple charges in one email (cart with 3 items) | Extract single final total; list items in description |
| No recognizable total found | "Could not identify a total amount — please enter amount manually"; switch to manual confirmation with other fields pre-filled |
| Currency ambiguous (e.g., "$" with Israeli vendor) | Default to USD; show in confirmation for user to correct |
Trigger: "monthly summary", "expense summary", "how much did I spend [this month / last month / March]"
Output (markdown table in conversation):
## Expenses — [Month YYYY]
Total: X,XXX.XX NIS (Business: X,XXX.XX | Personal: X,XXX.XX)
Category | Amount (NIS)
----------------|-------------
food | 1,250.00
transport | 340.00
...
Other | 120.00
Sort categories by total descending. Show personal vs business split in header line.
Trigger: "tax export", "export expenses", "export to Excel", "export [month] expenses" Output: ~/expenses/YYYY/MM/expenses-YYYY-MM.xlsx (openpyxl) See references/reports.md for full column spec and Python script template. After creating the file, confirm: "Saved: ~/expenses/YYYY/MM/expenses-YYYY-MM.xlsx ([N] records)"
Trigger: "category breakdown", "spending by category", "where did I spend [this month]"
Output (markdown table):
## Category Breakdown — [Month YYYY]
Total: X,XXX.XX NIS
Category | Amount (NIS) | Share
----------------|-------------|-------
food | 1,250.00 | 42.3%
transport | 340.00 | 11.5%
...
Sort descending by amount. Percentages rounded to 1 decimal.
Trigger: "client billing", "billing report", "expenses for [client name]", "reimbursable expenses" Input: optionally filter by client name (fuzzy match against client field)
Output (markdown table):
## Billing Report — [Client or All Clients] — [Month YYYY]
(Business expenses only)
Client: Acme Ltd
Project: website-redesign
2026-03-15 Taxi to client office transport 45.00 NIS
2026-03-18 Client lunch food 125.00 NIS
Subtotal: 170.00 NIS
Total: 170.00 NIS
Only include records where type = "business". Group by client → project → date.
Full Excel column spec and Python script template: see references/reports.md.