CRITICAL EXECUTION RULE: You MUST use the
exectool to run the bash scripts below. Do NOT callsessions_spawn. Do NOT create ACP sessions. These are standalone shell scripts. Resolve script paths relative to this skill directory and run the resulting absolute path withexec.
You are not allowed to answer any question about expenses, income, recurring items, or budget data from memory or conversation history. Ever. Not once.
Every single response must begin by running the appropriate script from the list below. The script output is your only source of truth. Conversation history, prior responses, and your own assumptions are all irrelevant and must be ignored.
User asks aboutRun this script firstToday's / a specific date's expensesfind_expenses.sh --date YYYY-MM-DD --limit 20A specific expense (to find, fix, or delete)find_expenses.sh "<query>" 10Monthly spending totalsfind_summary.sh --month YYYY-MMToday's / a specific date's incomefind_income.sh --date YYYY-MM-DD --limit 20A specific income entryfind_income.sh "<query>" 10Recurring itemsfind_recurring.sh --month YYYY-MM or find_recurring.sh --query "<name>"Logging a new expenselog.sh "<user message>"Logging new incomewrite_income.sh
If you have not run the script yet, you have not started your response yet. Run the script. Then respond conversationally based on what it returns.
WRONG (never do this):
"Here are suggested categories: Fuel/Transport, Food & Drink, Medical/Pharmacy. Please confirm."
CORRECT (always do this):
"Here's what I'll log: Shell → Transport 🚙, Baker's Cottage → Groceries 🍎, Klinik → Medical 🩺. Shall I go ahead?"
Rules that must never be broken:
question field from preview_expense.sh verbatimget_categories.sh — the full category list is hardcoded right here:FormulaName=zategory1Housing 🏡=zategory2Transport 🚙=zategory3Groceries 🍎=zategory4Dining Out 🍕=zategory5Personal Care ❤️=zategory6Shopping 🛍️=zategory7Utilities 💡=zategory8Fun 🎬=zategory9Business 💻️=zategory10Other ❓=zategory11Donation 🕌=zategory12Childcare 🐣=zategory13Travel ✈️=zategory14Zakat 🌟=zategory15Debt Payment 💸=zategory16Fitness 💪=zategory17Family Support 🏘️=zategory18Taxes 💵=zategory19Maintenance 🧰=zategory20Painting 🎨=zategory21TestGround 🤖=zategory22Learning 📚=zategory23Sports 🏀=zategory24Pet 🐶=zategory25Gifts 🎁=zategory26Special Occasions 🥰=zategory27Dress 👚=zategory28Hobby 🪂=zategory29Insurance 🛡️=zategory30Medical 🩺
These are the ONLY valid categories. Use the name exactly as shown above (including emoji) when confirming with the user.
Required environment variables:
GOOGLE_SA_FILE — absolute path to the Google service account JSON fileSPREADSHEET_ID — the Simplify Budget spreadsheet IDTRACKER_CURRENCY — the base currency code for the tracker (for example EUR)Optional environment variables:
TRACKER_CURRENCY_SYMBOL — display symbol for the base currency (for example €)If the user says "log a pencil for 10 euro under business" — log it. Do not ask the user to rephrase, reformat, or provide the amount differently. Extract what you need from their message and run the script. This applies to every possible phrasing. The model's job is to parse natural language, not to ask the user to become a CLI.
If a script returns an error, report the actual error message. Never invent an error or silently fail.
TRACKER_CURRENCY is the system of record for stored amounts.TRACKER_CURRENCY."50 MYR" or "12 USD"."10 euro" → --amount "10 euro" ✅"€10" → --amount "€10" ✅"10 euros" → --amount "10 euros" ✅"50 ringgit" → --amount "50 ringgit" ✅"12 dollars" → --amount "12 dollars" ✅"10" → --amount "10" ✅TRACKER_CURRENCY, and store the converted amount in the sheet.[auto-fx] audit line to notes with the original amount, converted amount, rate, and rate date.get_categories.sh during normal operation"Dining Out", "Business", "Transport"). The script resolves it to the correct formula internally. Never construct =zategory{N} yourself.When the user mentions spending money, buying something, paying for something, or asks to log an expense — follow this flow:
Step 1 — Preview:
text
bash <skill_dir>/scripts/log.sh --preview "<user's message, word-for-word>"
Pass the raw message. Do not reformat. Add --date YYYY-MM-DD if the user names a date, --account "<name>" if they name an account (default is Cash).
The script parses amount, description, and category. It returns JSON with question, category, explicitCategory, and categorySource.
Step 2 — Confirm or write directly:
explicitCategory is true: skip confirmation, go straight to Step 3.categorySource is "builtin" or "learned": relay the question field verbatim and wait for the user's reply.question field (it includes a "best guess" note) and wait.When the user replies:
Step 3 — Write:
text
bash <skill_dir>/scripts/log.sh "<user's message>" --category "<confirmed category>"
The script writes to the sheet and outputs a REPLY: line. Send everything after REPLY: verbatim. Do not paraphrase.
Examples:
User: i bought a pencil for 10 euro under business category
→ preview detects explicit category → write directly (no confirmation needed)
User: spent 23 on mcdonalds
→ preview suggests Dining Out (builtin match) → ask "Log mcdonalds under Dining Out 🍕?" → on yes, write
User: €12 coffee
→ preview suggests Dining Out → confirm → write
Rules:
DUPLICATE_FOUND:, tell the user and ask if they want to log anyway. If yes, re-run with --skip-duplicate-check.REPLY: lines verbatim — do not paraphrase or confirm from memory.When the user uploads a receipt image or asks you to log a receipt:
description.Examples:
When the user wants to inspect, fix, or delete an expense, resolve it from the sheet first:
Build a short natural-language query from the user's message.
Search the sheet: text
bash <skill_dir>/scripts/find_expenses.sh "<query>" 10
Matching MUST consider both description and notes.
If one clear match exists, proceed. If multiple plausible matches exist, ask one short disambiguation question.
When the user provides income (amount + name, with optional date/account/source/notes):
Extract from the user's message:
amount — required. If the user mentions a foreign currency, preserve it in the amount string you pass to the script, for example "500 USD" or "1000 MYR". If they give no currency, pass a plain number.name — required. This is the income title, e.g. Salary, BMW Sale, Etoro withdrawaldate — in YYYY-MM-DD format. Default to today if not specified.account — default to Other if not specified.source — default to Other if not specified. Use the user’s wording when it is clear, e.g. Salary, Capital Gains, Remittance, Crypto, MTS.notes — optional supporting context.Write the income: text
bash <skill_dir>/scripts/write_income.sh "<amount_or_amount_with_currency>" "<name>" "<YYYY-MM-DD>" "<account>" "<source>" "<notes>"
Confirm to the user in a concise way: "✅ Logged income [name] — [amount] into [account] from [source] on [date]" Include notes only when present.
When the user wants to inspect, fix, or delete income, resolve it from the sheet first:
Build a short natural-language query from the user's message.
Search the sheet: text
bash <skill_dir>/scripts/find_income.sh "<query>" 10
Matching MUST consider name, source, and notes.
If one clear match exists, proceed. If multiple plausible matches exist, ask one short disambiguation question.
Triggers: "check my expenses for today", "what are my expenses today", "what did I spend today", "show me today's expenses", "expenses for [date]", "what did I spend on [date]", or any question about spending on a specific day.
Always run find_expenses.sh with a date filter. This is not optional.
text
bash <skill_dir>/scripts/find_expenses.sh --date <YYYY-MM-DD> --limit 20
[description] — [amount] under [category] ([account])[].find_summary.sh is for month-level questions only. Never use it for today/yesterday/specific-date queries.
Triggers: "what did I spend this month", "what's my income this month", "monthly totals", "how much did I save this month". These are month-level questions with no specific date.
text
bash <skill_dir>/scripts/find_summary.sh --month YYYY-MM
Rules:
Dontedit is the source of truth for monthly totals.find_summary.sh for today/yesterday or any specific date — that always goes to find_expenses.sh.When the user asks read-only recurring questions like:
what is due this monthwhen is capcut duewhat subscriptions are due nextUse the recurring query script. Do NOT write anything into Expenses or Income.
Examples:
text
bash <skill_dir>/scripts/find_recurring.sh --month 2026-03
bash <skill_dir>/scripts/find_recurring.sh --query "CapCut" --date 2026-03-28
bash <skill_dir>/scripts/find_recurring.sh --query "CapCut" --mode next --date 2026-03-28
Rules:
Recurring using the same recurrence rules as the existing Apps Script logic.when is X due, prefer --mode next.When the user wants to change or delete a recurring item in the Recurring tab:
Resolve the target recurring row using find_recurring.sh.
If one clear match exists, mutate the Recurring row itself. Never write anything into Expenses or Income for this task.
To update a recurring row, use __KEEP__ for unchanged fields and __CLEAR__ for optional end date / notes / source:
text
bash <skill_dir>/scripts/update_recurring.sh "<recurring_id>" "<YYYY-MM-DD_or___KEEP__>" "<name_or___KEEP__>" "<category_or___KEEP__>" "<expense_or_income_or___KEEP__>" "<Monthly_or_Quarterly_or_Yearly_or___KEEP__>" "<amount_or___KEEP__>" "<account_or___KEEP__>" "<YYYY-MM-DD_or___KEEP___or___CLEAR__>" "<notes_or___KEEP___or___CLEAR__>" "<source_or___KEEP___or___CLEAR__>"
To delete a recurring row, clear the row by recurring id: text
bash <skill_dir>/scripts/delete_recurring.sh "<recurring_id>"
Confirm concisely what changed.
When the user wants to add a recurring expense or recurring income to the Recurring tab:
Extract:
start_date in YYYY-MM-DDnamecategory must use the live active category list; never invent a categorytype as expense or incomefrequency as Monthly, Quarterly, or Yearlyamountaccount, end_date, notes, sourceWrite it with: text
bash <skill_dir>/scripts/write_recurring.sh "<YYYY-MM-DD>" "<name>" "<category>" "<expense_or_income>" "<Monthly_or_Quarterly_or_Yearly>" "<amount>" "<account>" "<YYYY-MM-DD_optional_end_date>" "<notes>" "<source>"
This must reuse the first empty row in Recurring starting from row 6, matching the SB_LIVE hole-reuse behavior.
Recurring categories follow SB_LIVE rules:
=zategory<stableId> formula — use the Active Categories table above to resolve itIncome 💵Never ask the user to pick a category unless they explicitly want to choose. Best-match into an existing category.
Confirm concisely.
When the user wants to change amount, name, date, account, source, or notes for an income row:
Resolve the target income from the sheet using find_income.sh. Do NOT trust chat memory as the source of truth.
Infer the correction from their message. If the new amount is in a foreign currency, preserve that currency in the amount argument you pass to the script.
Run the update. Use __KEEP__ for unchanged fields and __CLEAR__ to blank notes:
text
bash <skill_dir>/scripts/update_income.sh "<transaction_id>" "<amount_or_amount_with_currency_or___KEEP__>" "<name_or___KEEP__>" "<YYYY-MM-DD_or___KEEP__>" "<account_or___KEEP__>" "<source_or___KEEP__>" "<notes_or___KEEP___or___CLEAR__>"
Confirm: "✅ Updated income — now [name] — [amount] into [account] from [source]"
If the user asks to undo or delete an income entry:
Resolve the target income from the sheet using find_income.sh.
If there is one clear match, clear the row by transaction id: text
bash <skill_dir>/scripts/delete_income.sh "<transaction_id>"
Confirm that the income row was cleared.
Never delete sheet rows. Clear the existing row contents instead.
When the user says things like "fix that", "that was wrong", "change the amount", "put that under X instead", "it was 4 not 5", "actually that was yesterday", "actually I bought that on [date]", "wrong date":
Run find_expenses.sh to resolve the target from the sheet. Do not use chat memory.
Infer the correction (amount, category, description, date, account, notes).
Run the update. Use __KEEP__ for unchanged fields, __CLEAR__ to blank notes. Pass category as plain English name:
text
bash <skill_dir>/scripts/update_expense.sh --id "<transaction_id>" --amount "<amount_or___KEEP__>" --description "<description_or___KEEP__>" --category "<category_name_or___KEEP__>" --date "<YYYY-MM-DD_or___KEEP__>" --account "<account_or___KEEP__>" --notes "<notes_or___KEEP___or___CLEAR__>"
Relay the REPLY: line from the script output word for word.
If the user asks to undo or delete an entry:
Run find_expenses.sh to resolve the target from the sheet.
If one clear match, delete it: text
bash <skill_dir>/scripts/delete_expense.sh "<transaction_id>"
Relay the REPLY: line from the script output word for word.
Never delete sheet rows — the script clears the row contents.
=zategory{N} yourselfget_categories.sh at runtime — the category table is hardcoded in the scriptsname, account, source, and notes fields.REPLY: lines from scripts verbatim — do not paraphrase or confirm from memoryTRACKER_CURRENCYTRACKER_CURRENCY