Claude
Skills
Sign in
Back

analyze-spending

Included with Lifetime
$97 forever

Analyze finances using LunchMoney API. Use when the user asks about spending patterns, budget analysis, subscription audit, recurring expenses, or financial overview for periods like "this month", "last month", "this year", or custom date ranges.

Backend & APIsassets

What this skill does


# Analyze Spending with LunchMoney

Analyze your finances, identify spending patterns, review budgets, and find subscriptions you might want to cancel using data from LunchMoney.

## Trigger Phrases

- `/analyze-spending`
- "analyze my spending"
- "review my finances"
- "find subscriptions to cancel"
- "audit my recurring expenses"
- "how much did I spend on [category]?"
- "budget review for [period]"
- "where is my money going?"

## Prerequisites

1. **LunchMoney Account**: Active subscription with API access
2. **API Key**: Generate at https://my.lunchmoney.app/developers
3. **1Password Storage**: Store the API key in 1Password:
   - Item name: `Lunchmoney`
   - Field: `API Key`

## File Structure

```
skills/analyze-spending/
├── SKILL.md                      # This file
├── assets/
│   ├── MEMORY.md                 # Template for user preferences
│   └── subscription-audit-template.md  # Template for categorized report
└── data/                         # Runtime data (gitignored)
    ├── MEMORY.md                 # User preferences & decisions (copy from assets/)
    ├── cache/                    # Cached API responses
    │   ├── user.json             # User info (refreshed weekly)
    │   ├── categories.json       # Categories (refreshed weekly)
    │   ├── recurring_items.json  # Subscriptions (refreshed daily)
    │   └── transactions/         # Transaction data by month
    │       ├── 2025-12.json
    │       └── 2026-01.json
    ├── cache_meta.json           # Cache timestamps
    └── reports/                  # Generated markdown reports
        ├── 2026-01-19_spending-overview.md
        └── 2026-01-19_subscription-audit.md
```

---

## Memory & Preferences

The `MEMORY.md` file stores user preferences, decisions, and learned patterns across sessions.

### Initialize Memory

```bash
SKILL_DIR="skills/analyze-spending"
MEMORY_FILE="${SKILL_DIR}/data/MEMORY.md"
MEMORY_TEMPLATE="${SKILL_DIR}/assets/MEMORY.md"

# Create data directory and copy template if MEMORY.md doesn't exist
mkdir -p "${SKILL_DIR}/data"
if [ ! -f "$MEMORY_FILE" ]; then
  cp "$MEMORY_TEMPLATE" "$MEMORY_FILE"
  echo "Created MEMORY.md from template"
fi
```

### Reading Preferences from Memory

Extract YAML configuration blocks from MEMORY.md:

```bash
# Extract essential subscriptions (won't flag for cancellation)
ESSENTIAL_SUBS=$(sed -n '/^essential_subscriptions:/,/^[a-z]/p' "$MEMORY_FILE" | \
  grep -E '^\s+-\s+payee:' | sed 's/.*payee:\s*"\(.*\)"/\1/')

# Extract excluded categories
EXCLUDED_CATS=$(sed -n '/^excluded_categories:/,/^[a-z]/p' "$MEMORY_FILE" | \
  grep -E '^\s+-\s+"' | sed 's/.*"\(.*\)"/\1/')

# Extract watchlist
WATCHLIST=$(sed -n '/^watchlist:/,/^[a-z]/p' "$MEMORY_FILE" | \
  grep -E '^\s+-\s+payee:' | sed 's/.*payee:\s*"\(.*\)"/\1/')
```

### Updating Memory After Decisions

When the user makes a decision about a subscription, append to the Session History:

```bash
# Add a decision to memory
add_decision() {
  local date="$1"
  local subscription="$2"
  local decision="$3"
  local reason="$4"
  local savings="$5"

  cat >> "$MEMORY_FILE" << EOF

#### ${date}
- **${subscription}**: ${decision}
  - Reason: ${reason}
  - Annual savings: \$${savings}
EOF
}

# Example: User decided to cancel a subscription
add_decision "2026-01-19" "Crunch Gym" "cancel" "Keeping Chelsea Piers instead" "1053"
```

### Adding to Watchlist

```bash
add_to_watchlist() {
  local payee="$1"
  local reason="$2"
  local review_date=$(date -v+30d +%Y-%m-%d)

  # This would need more sophisticated YAML editing
  # For now, prompt user to manually add:
  echo "Add to watchlist section in MEMORY.md:"
  echo "  - payee: \"${payee}\""
  echo "    added: $(date +%Y-%m-%d)"
  echo "    review_date: ${review_date}"
  echo "    reason: \"${reason}\""
}
```

### Using Memory in Analysis

When analyzing, filter based on preferences:

```bash
# Filter out essential subscriptions from cancellation suggestions
filter_subscriptions() {
  jq --argjson essential "$(echo "$ESSENTIAL_SUBS" | jq -R . | jq -s .)" '
    map(select(.payee as $p | $essential | index($p) | not))
  '
}

# Exclude certain categories from spending totals
filter_transactions() {
  jq --argjson excluded "$(echo "$EXCLUDED_CATS" | jq -R . | jq -s .)" '
    .transactions | map(select(.category_name as $c | $excluded | index($c) | not))
  '
}
```

---

## Workflow

### 0. Interview Mode (Always On)

The skill includes an interactive interview to validate subscription data and make decisions. This is critical because:
- LunchMoney's `transactions_within_range` field is often unreliable
- Users forget what's cancelled vs still active
- Categorization may need user confirmation

**Interview Categories** (ask about each in order):
1. **Fitness** - Gym memberships, fitness apps
2. **Streaming** - Video, music, audiobooks
3. **AI/Dev Tools** - AI subscriptions, hosting, developer tools
4. **Reading/News** - RSS, read-later, newsletters
5. **Productivity** - Software subscriptions
6. **Memberships** - Patreon, donations, co-ops

**Interview Questions Pattern**:
```
Use AskUserQuestion tool with:
- Show actual charges found from 12 months of transactions
- Group by category with totals (monthly/annual)
- Include "Last Charged" date and status indicators (✅/⚠️/🔴/❌)
- Distinguish cancelled (❌) from irregular (🔴 Xd ago)
- Offer actionable choices: Keep all, Cancel all, Review each, etc.
```

**Key Learnings to Apply**:
- **Use 12-month window** to catch annual subscriptions and show real activity
- Always cross-check recurring_items with actual transactions using normalized payee matching
- Show "Last Charged: Dec 2025" not confusing "Missed 3" status
- Price variations are normal (e.g., Starlink $120 → $5) - match by name not amount
- Ask follow-up questions based on responses
- Record all decisions in MEMORY.md

**After Interview**:
- Update MEMORY.md with decisions
- Generate action items list
- Calculate estimated savings
- Set review dates for watchlist items

### 1. Initialize Data Directory

```bash
SKILL_DIR="skills/analyze-spending"
DATA_DIR="${SKILL_DIR}/data"
CACHE_DIR="${DATA_DIR}/cache"
REPORTS_DIR="${DATA_DIR}/reports"
TRANSACTIONS_DIR="${CACHE_DIR}/transactions"

mkdir -p "${CACHE_DIR}" "${REPORTS_DIR}" "${TRANSACTIONS_DIR}"

# Initialize MEMORY.md if needed
MEMORY_FILE="${DATA_DIR}/MEMORY.md"
if [ ! -f "$MEMORY_FILE" ]; then
  cp "${SKILL_DIR}/assets/MEMORY.md" "$MEMORY_FILE"
fi
```

### 2. Retrieve API Key

```bash
LUNCHMONEY_API_KEY=$(op item get "Lunchmoney" --fields "API Key" --reveal)
```

### 3. Check Cache Validity

Before fetching, check if cached data is still fresh:

```bash
CACHE_META="${CACHE_DIR}/cache_meta.json"

# Initialize cache meta if it doesn't exist
if [ ! -f "$CACHE_META" ]; then
  echo '{}' > "$CACHE_META"
fi

# Check if cache is fresh (returns "true" or "false")
is_cache_fresh() {
  local cache_key="$1"
  local max_age_hours="$2"

  local last_fetch=$(jq -r ".${cache_key} // 0" "$CACHE_META")
  local now=$(date +%s)
  local age_hours=$(( (now - last_fetch) / 3600 ))

  [ "$age_hours" -lt "$max_age_hours" ] && echo "true" || echo "false"
}

# Update cache timestamp
update_cache_meta() {
  local cache_key="$1"
  local now=$(date +%s)
  local tmp=$(mktemp)
  jq ".${cache_key} = ${now}" "$CACHE_META" > "$tmp" && mv "$tmp" "$CACHE_META"
}
```

**Cache freshness thresholds:**
| Data Type | Max Age | Rationale |
|-----------|---------|-----------|
| user | 168 hours (7 days) | Rarely changes |
| categories | 168 hours (7 days) | Rarely changes |
| recurring_items | 24 hours | May change daily |
| transactions (current month) | 4 hours | Active updates |
| transactions (past months) | 168 hours | Historical, stable |

### 4. Fetch and Cache User Info

```bash
USER_CACHE="${CACHE_DIR}/user.json"

if [ "$(is_cache_fresh user 168)" = "false" ] || [ ! -f "$USER_CACHE" ]; then
  curl -s "https://dev.lunchmoney.app/v1/me" \
    -H "Authorization: Bearer ${LUNCHMONEY_API_KEY}" \
    > "$USER_CACHE"
  update_cach

Related in Backend & APIs