← Back to Skills Marketplace
alexwong27

Subscription Killer

by alexwong27 · GitHub ↗ · v1.2.1 · MIT-0
cross-platform ✓ Security Clean
80
Downloads
1
Stars
0
Active Installs
4
Versions
Install in OpenClaw
/install subscription-killer
Description
Analyse a bank transactions CSV to detect recurring subscriptions, score cancellation priority, and surface actionable savings recommendations. Designed as t...
README (SKILL.md)

Subscription Killer

Analyse a bank transactions CSV file and surface every recurring subscription, ranked by cancellation / downgrade priority with estimated monthly savings.

Skill overview

This skill takes a single CSV file of bank transactions and:

  1. Sniffs the CSV structure to detect column names automatically.
  2. Normalises merchant names using a curated alias dictionary plus fuzzy matching (Levenshtein distance ≤ 2 tokens).
  3. Detects recurring charges by cadence (monthly / quarterly / annual) and amount consistency (±2 % tolerance).
  4. Scores each subscription by confidence (0–100).
  5. Ranks subscriptions by a composite priority score: spend × inactivity risk × price-creep factor × duplication penalty.
  6. Outputs a structured report with clear cancellation and downgrade actions.

Input format

The skill accepts any CSV export from a personal bank account. Common formats are auto-detected:

Bank / Provider Typical columns
Monzo Date, Name, Amount, Category
Starling Date, Counter Party, Amount, Balance
Revolut Started Date, Description, Amount, Currency
Standard OFX export Date, Description, Debit Amount
Generic date, merchant / description, amount (minimum required)

Column detection priority:

  • Date: looks for columns named date, transaction date, started date, posted date (case-insensitive). Falls back to first column that parses as ISO 8601 or DD/MM/YYYY.
  • Amount: looks for amount, debit amount, debit. Ignores credit / top-up rows (positive amounts in debit-positive conventions).
  • Merchant: looks for name, description, merchant, counter party, payee. Falls back to longest non-numeric column.

Core logic

Step 1 — Parse CSV

import csv, re, difflib
from datetime import datetime, timedelta
from collections import defaultdict

def sniff_columns(header: list[str]) -> dict:
    """Return {'date': col, 'amount': col, 'merchant': col} best guesses."""
    header_lower = [h.lower().strip() for h in header]
    DATE_HINTS    = ['date', 'transaction date', 'started date', 'posted date']
    AMOUNT_HINTS  = ['amount', 'debit amount', 'debit']
    MERCHANT_HINTS = ['name', 'description', 'merchant', 'counter party',
                      'payee', 'narrative']
    def first_match(hints):
        for hint in hints:
            if hint in header_lower:
                return header[header_lower.index(hint)]
        return None
    return {
        'date':     first_match(DATE_HINTS)     or header[0],
        'amount':   first_match(AMOUNT_HINTS)   or header[1],
        'merchant': first_match(MERCHANT_HINTS) or header[2],
    }

def parse_amount(raw: str) -> float | None:
    """Return a positive debit amount, or None for credits / errors."""
    cleaned = re.sub(r'[£$€,\s]', '', raw.strip())
    if not cleaned:
        return None
    try:
        val = float(cleaned)
        # Debit-positive CSV (most UK banks): only keep debits (negatives after
        # sign flip are credits — skip them)
        # Some CSVs use negative for debits; normalise to positive debit.
        return abs(val) if val != 0 else None
    except ValueError:
        return None

Step 2 — Normalise merchant names

Use a two-pass approach:

Pass 1 — Known alias dictionary (curated, highest-confidence)

MERCHANT_ALIASES = {
    # Streaming
    r'netflix':                 'Netflix',
    r'spotify':                 'Spotify',
    r'apple\.com/bill':         'Apple Subscriptions',
    r'itunes':                  'Apple Subscriptions',
    r'amazon prime':            'Amazon Prime',
    r'amzn\s?prime':            'Amazon Prime',
    r'disney\+|disneyplus':     'Disney+',
    r'youtube premium':         'YouTube Premium',
    r'hbo|max\.com':            'Max (HBO)',
    r'paramount':               'Paramount+',
    r'dazn':                    'DAZN',
    # Music
    r'tidal':                   'Tidal',
    r'deezer':                  'Deezer',
    r'soundcloud':              'SoundCloud',
    # Productivity / SaaS
    r'github':                  'GitHub',
    r'notion':                  'Notion',
    r'slack':                   'Slack',
    r'dropbox':                 'Dropbox',
    r'google\s?(one|storage|workspace)': 'Google One/Workspace',
    r'microsoft 365|office 365|msft': 'Microsoft 365',
    r'adobe':                   'Adobe Creative Cloud',
    r'figma':                   'Figma',
    r'zoom':                    'Zoom',
    r'1password|lastpass|bitwarden': 'Password Manager',
    r'nordvpn|expressvpn|surfshark':  'VPN Service',
    # News / Reading
    r'medium':                  'Medium',
    r'substack':                'Substack',
    r'kindle unlimited':        'Kindle Unlimited',
    r'audible':                 'Audible',
    r'the times|thetimes':      'The Times',
    r'financial times|ft\.com': 'Financial Times',
    r'economist':               'The Economist',
    # Fitness
    r'peloton':                 'Peloton',
    r'strava':                  'Strava',
    r'myfitnesspal':            'MyFitnessPal',
    r'calm|headspace':          'Meditation App',
    # Utilities / services
    r'amazon web services|aws': 'AWS',
    r'google cloud|gcp':        'Google Cloud',
    r'digitalocean':            'DigitalOcean',
    r'cloudflare':              'Cloudflare',
    r'railway\.app':            'Railway',
    r'openai':                  'OpenAI',
    r'anthropic':               'Anthropic',
}

def normalize_merchant(raw: str) -> str:
    """Return a clean, canonical merchant name."""
    s = raw.lower().strip()
    for pattern, canonical in MERCHANT_ALIASES.items():
        if re.search(pattern, s, re.IGNORECASE):
            return canonical
    # Pass 2 — strip noise tokens (card numbers, references, country codes)
    cleaned = re.sub(
        r'\b(www|com|co\.uk|ltd|limited|uk|us|gb|inc|gmbh)\b'
        r'|\d{4,}'      # card / ref numbers
        r'|\*+\S*'      # asterisk-prefixed tokens
        r'|[^a-z0-9 ]', # special chars
        '', s
    ).strip().title()
    return cleaned or raw.title()

Pass 2 — Fuzzy deduplication across the transaction list

After normalisation, cluster remaining merchant names by token-set similarity (difflib.SequenceMatcher ratio > 0.82). The most-frequent variant in each cluster becomes the canonical name.

Step 3 — Detect recurring charges

def detect_cadence(dates: list[datetime]) -> str | None:
    """
    Return 'monthly', 'quarterly', 'annual', or None.
    Requires ≥ 2 transactions for monthly/quarterly, ≥ 1 for annual hinting.
    """
    if len(dates) \x3C 2:
        # Single occurrence — flag as potential annual if amount > 30
        return 'annual_candidate'
    gaps = sorted([(dates[i+1] - dates[i]).days for i in range(len(dates)-1)])
    median_gap = gaps[len(gaps)//2]
    if 25 \x3C= median_gap \x3C= 35:   return 'monthly'
    if 85 \x3C= median_gap \x3C= 100:  return 'quarterly'
    if 340 \x3C= median_gap \x3C= 380: return 'annual'
    return None

def amounts_consistent(amounts: list[float], tolerance: float = 0.02) -> bool:
    """True if all amounts are within ±2 % of the median."""
    if not amounts:
        return False
    median = sorted(amounts)[len(amounts)//2]
    return all(abs(a - median) / median \x3C= tolerance for a in amounts)

Step 4 — Confidence scoring (0–100)

Factor Max points Logic
Cadence regularity 40 All gaps within ±3 days of median
Amount consistency 30 All amounts within ±2 %
Known merchant match 20 Exact alias dictionary hit
Transaction count 10 log₂(count) × 3, capped at 10

Confidence ≥ 70 → Confirmed subscription Confidence 40–69 → Probable subscription Confidence \x3C 40 → Possible / flag for review

Step 5 — Priority ranking

Each confirmed/probable subscription gets a composite priority score:

priority = monthly_cost
         × price_creep_multiplier   # max(amounts) / min(amounts); 1.0 = stable
         × duplication_penalty       # 1.5 if same category has 2+ subscriptions
         × inactivity_risk           # 1.3 if last charge > 45 days ago

Higher priority score = stronger cancellation candidate.

Special flags:

  • Trial trap: single charge in the last 7 days from a previously-unseen merchant, followed by a recurring pattern. Flag as "check for auto-renew".
  • Price creep: amount has increased > 5 % over the observation window. Flag with the exact amount delta.
  • Annual renewal due: annual-cadence subscription whose last charge was 300–365 days ago. Flag with estimated renewal date.
  • Unknown merchant: low-confidence recurring charge with no alias match. Flag for manual review.

Output schema

The skill returns a structured report. Print to stdout as JSON for downstream agent consumption, or render the human-readable summary below.

{
  "summary": {
    "observation_window_days": 90,
    "total_subscriptions": 12,
    "confirmed": 9,
    "probable": 2,
    "flagged_for_review": 1,
    "monthly_spend_gbp": 187.43,
    "annual_spend_gbp": 2249.16,
    "potential_monthly_saving_gbp": 54.20
  },
  "subscriptions": [
    {
      "merchant": "Adobe Creative Cloud",
      "cadence": "monthly",
      "median_amount": 54.99,
      "last_charge": "2025-04-01",
      "confidence": 95,
      "priority_score": 89.2,
      "flags": ["price_creep"],
      "price_creep_delta": 5.00,
      "cancel_url": "https://account.adobe.com/plans",
      "actions": ["cancel", "downgrade"]
    }
  ],
  "top_cancellation_targets": ["Adobe Creative Cloud", "..."],
  "unknown_merchants": ["TRSF 48291 REF77"],
  "annual_renewals_due": []
}

Human-readable summary (terminal / Telegram)

╔══════════════════════════════════════════╗
║        SUBSCRIPTION KILLER  v1.0         ║
╠══════════════════════════════════════════╣
║  12 subscriptions  •  £187/mo  •  £2,249/yr
╚══════════════════════════════════════════╝

TOP CANCELLATION TARGETS
1. Adobe Creative Cloud   £54.99/mo  ↑ price crept +£5.00
2. Calm                   £39.99/yr  last used 3 months ago
3. LinkedIn Premium       £29.99/mo  duplicate: 2 job-search tools

POTENTIAL SAVING:  £54/mo  •  £648/yr

UNKNOWN MERCHANTS (review manually)
• TRSF 48291 REF77  — £12.99/mo  (confidence 38%)

Cancel guidance (top 50 services)

The skill bundles a cancel_urls.json file mapping canonical merchant names to their self-serve cancellation pages. This removes friction for the user.

{
  "Netflix":                "https://www.netflix.com/cancelplan",
  "Spotify":                "https://www.spotify.com/account/subscription/cancel",
  "Adobe Creative Cloud":   "https://account.adobe.com/plans",
  "Amazon Prime":           "https://www.amazon.co.uk/mc/pipeline/cancelEndBenefit",
  "Disney+":                "https://www.disneyplus.com/account/subscription",
  "Microsoft 365":          "https://account.microsoft.com/services",
  "GitHub":                 "https://github.com/settings/billing/subscriptions",
  "Dropbox":                "https://www.dropbox.com/account/plan",
  "Zoom":                   "https://zoom.us/billing",
  "LinkedIn Premium":       "https://www.linkedin.com/premium/products",
  "Google One/Workspace":   "https://myaccount.google.com/payments-and-subscriptions",
  "OpenAI":                 "https://platform.openai.com/account/billing"
}

Invocation

# Analyse a CSV file
python3 subscription_killer.py --file transactions.csv

# Override currency display
SUBSCRIPTION_KILLER_CURRENCY=EUR python3 subscription_killer.py --file transactions.csv

# Output raw JSON for piping to another skill
python3 subscription_killer.py --file transactions.csv --json

The script reads SUBSCRIPTION_KILLER_CURRENCY from the environment for display only; all internal calculations use the amounts as-found in the CSV.


Roadmap (future skills in this suite)

This skill is intentionally scoped to subscription detection. Planned companion skills that share the CSV parsing layer:

Skill Description
savings-rate-analyser Track income vs spend, compute savings rate, compare to benchmarks
deposit-rate-scout Pull live UK/EU savings account rates via open banking APIs
emergency-fund-checker Assess months of runway based on average monthly spend
investment-nudger Identify investable surplus after subscriptions + essentials

The bank-csv-parser module within this skill will be extracted into a shared utility skill once the suite matures.


Security & privacy

  • All processing is local. No transaction data leaves the machine.
  • The skill does not write any files outside the working directory.
  • No API keys required for the core subscription detection flow.
  • The cancel_urls.json is a static lookup table; no network calls are made unless the user explicitly requests live rate data (future skill).
Usage Guidance
This skill looks purpose-aligned and locally scoped, but it works on private bank transaction data. Before installing or running it, inspect the packaged files if provenance matters to you, provide only the CSV data needed for the analysis, and manually review any cancellation or downgrade recommendation before taking action.
Capability Analysis
Type: OpenClaw Skill Name: subscription-killer Version: 1.2.1 The subscription-killer skill is a local utility designed to analyze bank transaction CSV files for recurring charges. The Python script (subscription_killer.py) implements heuristic column sniffing, regex-based merchant normalization, and cadence detection using only the Python standard library. Analysis of the code and SKILL.md confirms that all processing is performed locally, with no evidence of network communication, data exfiltration, or unauthorized file system access. The logic is transparent and aligns perfectly with the stated purpose of identifying and ranking subscriptions for potential savings.
Capability Tags
cryptocan-make-purchases
Capability Assessment
Purpose & Capability
The purpose and behavior are coherent: it analyzes bank transaction CSVs for recurring subscriptions. Because the input is sensitive financial data and the output can guide subscription changes, users should review what they provide and what actions they take.
Instruction Scope
The documented workflow is user-invoked and scoped to a CSV file supplied with --file. The artifacts do not show instructions to override user intent, run automatically, or cancel accounts without user action.
Install Mechanism
There is no install spec and only python3 is required, but the registry lists the source as unknown and homepage as none. The included code was static-scanned clean, but provenance is limited.
Credentials
The environment needs are modest: python3 and an optional currency display variable. The registry's can-make-purchases capability appears related to billing/cancellation links rather than code that performs purchases or cancellations.
Persistence & Privilege
The provided artifacts show no background process, persistence mechanism, credential use, browser session access, or storage of bank data beyond processing the user-supplied CSV.
How to Use
  1. Make sure OpenClaw is installed (local or Docker)
  2. Run the install command in chat: /install subscription-killer
  3. After installation, invoke the skill by name or use /subscription-killer
  4. Provide required inputs per the skill's parameter spec and get structured output
Version History
v1.2.1
Fix date parsing priority — full datetime formats now precede date-only formats, preventing silent midnight truncation of Wise/ISO timestamps
v1.2.0
Universal column sniffing — token-scoring replaces exact header matching, works with Wise, Revolut, HSBC and any generic bank export
v1.1.0
Added subscription_killer.py — full terminal output with colour, confidence bars, cancel URLs, price creep detection, and renewal alerts
v1.0.0
Initial release — CSV-based subscription detection with confidence scoring and cancel guidance
Metadata
Slug subscription-killer
Version 1.2.1
License MIT-0
All-time Installs 0
Active Installs 0
Total Versions 4
Frequently Asked Questions

What is Subscription Killer?

Analyse a bank transactions CSV to detect recurring subscriptions, score cancellation priority, and surface actionable savings recommendations. Designed as t... It is an AI Agent Skill for Claude Code / OpenClaw, with 80 downloads so far.

How do I install Subscription Killer?

Run "/install subscription-killer" in the OpenClaw or Claude Code chat to install it in one step — no extra setup required.

Is Subscription Killer free?

Yes, Subscription Killer is completely free, licensed under MIT-0. You can download, install and use it at no cost.

Which platforms does Subscription Killer support?

Subscription Killer is cross-platform and runs anywhere OpenClaw / Claude Code is available (cross-platform).

Who created Subscription Killer?

It is built and maintained by alexwong27 (@alexwong27); the current version is v1.2.1.

💬 Comments