Skip to content
Back to skills

Groceries

ASecurity

> "Tea. Earl Grey. Hot. And add milk to the replicator queue." Dutch supermarket integration with smart shopping automation. Sister scripts for Albert Heijn API, meal planning, and list management.

  • 56 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 6, 2026
toolspythongobashnodespringgitapidocumentation

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 11 files and shows the line behind each finding

Scanned October 6, 2026

npx -y skills add SimHacker/moollm --skill groceries --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Groceries?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Groceries
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/simhacker-groceries/badge)](https://www.skillsdirectory.com/skills/simhacker-groceries)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
# 🛒 GROCERIES

> "Tea. Earl Grey. Hot. And add milk to the replicator queue."

Dutch supermarket integration with smart shopping automation.
Sister scripts for Albert Heijn API, meal planning, and list management.

## Quick Start

```bash
# Anonymous (no login needed)
python scripts/ah.py search "hagelslag"
python scripts/ah.py bonus --week current
python scripts/ah.py stores --near "Amsterdam"

# Authenticated (requires 1Password setup)
python scripts/ah.py login
python scripts/ah.py receipts --limit 10
python scripts/ah.py orders
python scripts/ah.py list add "melk" "brood" "kaas"
```

## Architecture

**PUBLIC** — The tools (shareable):
```
moollm/skills/groceries/        # THIS REPO — Share freely
├── CARD.yml                    # Quick reference
├── SKILL.md                    # Protocol docs
├── scripts/
│   └── ah.py                   # Albert Heijn API client
├── templates/                  # Config templates
│   └── config.yml.tmpl
└── examples/                   # 🖖 Trekified examples
    ├── enterprise-galley.yml   # TNG: Ship's galley
    ├── ds9-replimat.yml        # DS9: Supply crisis
    ├── voyager-neelix.yml      # VOY: Neelix's kitchen
    └── klingon-kitchen.yml     # Warrior's guide to cooking
```

**PRIVATE** — Your data (your repo or local):
```
YourHome/household/shopping/    # YOUR REPO — Personal data
├── INDEX.yml                   # Master index
├── current-list.yml            # Today's shopping list
├── grocery/
│   ├── ah-cache.yml            # Product cache, favorites, deals
│   ├── meal-planning.yml       # Your recipes (English + Dutch terms)
│   ├── albert-heijn.yml        # Order history (if scraping)
│   └── delivery-apps.yml       # What you order (for recipe ideas)
└── zooplus.yml                 # Cat supplies (separate tracking OK)
```

**The Split:**
| PUBLIC (skill) | PRIVATE (your repo) |
|----------------|---------------------|
| ah.py script | Your product cache |
| API documentation | Your price tracking |
| Trek examples | Your deal alerts |
| Generic templates | Your shopping lists |
| How-to guides | Your order history |
| Recipe structures | Your actual recipes |

## Private Cache Usage

Your private repo stores the actual data. The public skill provides tools.

### ah-cache.yml (Your Repo)

Track products you buy, prices, and deals:

```yaml
# YourHome/household/shopping/grocery/ah-cache.yml

regulars:
  mexican_essentials:
    items:
      - product: "AH Rundergehakt"
        webshop_id: 123456  # From: ah.py search "rundergehakt" --json
        usual_qty: "750g"
        frequency: "weekly"

bonus_tracking:
  watch_list:
    - "Rundergehakt"
    - "Tortilla wraps"
    - "Avocado's"
  current_deals:
    week: 5
    relevant:
      - "Rundergehakt 500g: €4.49 (was €5.49)"

price_reference:
  rundergehakt_500g:
    normal: "~€5.50"
    good_deal: "<€4.50"
```

### Workflow

```bash
# 1. Check this week's bonus deals
python ah.py bonus --week current

# 2. Search for products you need
python ah.py search "rundergehakt"

# 3. Update your ah-cache.yml with:
#    - Product IDs (for list sync)
#    - Current bonus prices
#    - Price history

# 4. Generate shopping list based on:
#    - Weekly meal plan
#    - What's on bonus
#    - What's running low
```

---

## Credentials — 1Password Integration

Store your supermarket credentials in 1Password, retrieve via `op`:

```bash
# Setup (one time)
op signin

# The script uses:
op read "op://Personal/Albert Heijn/email"
op read "op://Personal/Albert Heijn/password"
```

### Config Template

Copy `templates/config.yml.tmpl` to `~/.moollm/skills/groceries/config.yml`:

```yaml
# ~/.moollm/skills/groceries/config.yml
# GITIGNORED — contains personal data

credentials:
  method: "1password"  # or "env" or "direct"
  
  # 1Password paths
  op:
    ah:
      email: "op://Personal/Albert Heijn/email"
      password: "op://Personal/Albert Heijn/password"
    jumbo:
      email: "op://Personal/Jumbo/email"
      password: "op://Personal/Jumbo/password"
      
  # Or environment variables
  # env:
  #   ah_email: "AH_EMAIL"
  #   ah_password: "AH_PASSWORD"

preferences:
  default_store: "ah"
  delivery_address: "Your address here"
  
  # Dietary preferences for suggestions
  diet:
    vegetarian: false
    vegan: false
    gluten_free: false
    lactose_free: false
    
  # Favorite brands (for SUGGEST)
  brands:
    coffee: ["Douwe Egberts", "Lavazza"]
    cheese: ["Old Amsterdam", "Beemster"]
```

## Albert Heijn API

### Endpoints

| Endpoint | Auth | Description |
|----------|------|-------------|
| `/mobile-auth/v1/auth/token/anonymous` | No | Get anonymous token |
| `/mobile-auth/v1/auth/token` | Login | Get user token |
| `/mobile-services/product/search/v2` | Anon | Search products |
| `/mobile-services/v1/receipts` | User | Get receipts |
| `/mobile-services/v2/receipts/{id}` | User | Get receipt details |
| `/mobile-services/shoppinglist/v2/items` | User | Manage shopping list |
| `/gql` | Varies | GraphQL (bonus, categories) |

### Headers Required

```
User-Agent: Appie/8.22.3
Content-Type: application/json
Authorization: Bearer {access_token}  # for authenticated requests
```

### Authentication Flow

```
1. Visit: https://login.ah.nl/secure/oauth/authorize?client_id=appie&redirect_uri=appie://login-exit&response_type=code
2. Login with credentials
3. Get redirected to: appie://login-exit?code=CODE
4. Exchange code for token:
   POST https://api.ah.nl/mobile-auth/v1/auth/token
   {"clientId": "appie", "code": "CODE"}
5. Receive: {"access_token": "...", "refresh_token": "...", "expires_in": 7199}
```

## Sister Script: ah.py

### Structure (follows sister-script pattern)

```python
#!/usr/bin/env python3
"""Albert Heijn API client — groceries skill sister script."""

import argparse
import json
import subprocess
from pathlib import Path
from dataclasses import dataclass
from enum import Enum
import requests

# CONFIGURATION
CONFIG_PATH = Path.home() / ".moollm/skills/groceries/config.yml"
TOKEN_CACHE = Path.home() / ".moollm/skills/groceries/.tokens.json"
API_BASE = "https://api.ah.nl"
USER_AGENT = "Appie/8.22.3"

# STATE
@dataclass
class AuthState:
    access_token: str | None = None
    refresh_token: str | None = None
    expires_at: float = 0

# CLI DEFINITION
def create_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        description="Albert Heijn API client",
        epilog="See 'ah.py COMMAND --help' for details."
    )
    sub = parser.add_subparsers(dest="command")
    
    # SEARCH — Find products
    search = sub.add_parser("search", help="Search for products")
    search.add_argument("query", help="Search term")
    search.add_argument("--limit", type=int, default=10)
    search.add_argument("--bonus", action="store_true", help="Only bonus items")
    
    # BONUS — Get current deals
    bonus = sub.add_parser("bonus", help="Get bonus/sale items")
    bonus.add_argument("--week", default="current")
    bonus.add_argument("--category", help="Filter by category")
    
    # STORES — Find stores
    stores = sub.add_parser("stores", help="Find stores")
    stores.add_argument("--near", help="Location (city or coordinates)")
    stores.add_argument("--limit", type=int, default=5)
    
    # LOGIN — Authenticate
    sub.add_parser("login", help="Login to AH account")
    
    # RECEIPTS — Get purchase history
    receipts = sub.add_parser("receipts", help="Get receipts")
    receipts.add_argument("--limit", type=int, default=20)
    receipts.add_argument("--detail", help="Get specific receipt ID")
    
    # ORDERS — Get online orders
    orders = sub.add_parser("orders", help="Get online order history")
    orders.add_argument("--limit", type=int, default=10)
    
    # LIST — Manage shopping list
    list_cmd = sub.add_parser("list", help="Manage shopping list")
    list_sub = list_cmd.add_subparsers(dest="list_action")
    list_sub.add_parser("show", help="Show current list")
    list_add = list_sub.add_parser("add", help="Add items")
    list_add.add_argument("items", nargs="+", help="Items to add")
    list_clear = list_sub.add_parser("clear", help="Clear list")
    
    return parser

# IMPLEMENTATION
# (See full script in scripts/ah.py)
```

## Methods

### SEARCH — Find Products

```yaml
invoke: python scripts/ah.py search "kaas" --limit 5

example_output:
  - title: "AH Jong belegen kaas plakken"
    price: 2.49
    unit: "200g"
    bonus: true
    bonus_price: 1.99
```

### BONUS — Current Deals

```yaml
invoke: python scripts/ah.py bonus --week current

# Or via GraphQL for full details:
query: |
  query bonusCategories($input: PromotionSearchInput) {
    bonusCategories(filterSet: WEB_CATEGORIES, input: $input) {
      id
      title
      promotions {
        title
        price { now { amount } was { amount } }
      }
    }
  }
```

### LIST-SYNC — Sync to AH App

```yaml
invoke: python scripts/ah.py list add "melk" "brood" "eieren"

# Adds to AH shopping list via API:
# PATCH /mobile-services/shoppinglist/v2/items
# {"items": [{"productId": 123, "quantity": 1, "type": "SHOPPABLE"}]}
```

### RECEIPTS — Purchase History

```yaml
invoke: python scripts/ah.py receipts --limit 5

example_output:
  - transaction_id: "ABC123"
    date: "2026-01-28"
    store: "AH Amsterdam Centrum"
    total: 45.67
    items: 23
```

### ANALYZE — Pattern Analysis

```yaml
# After fetching receipts, analyze patterns:

patterns:
  most_bought:
    - "AH Halfvolle melk": 47 times
    - "AH Volkoren brood": 43 times
    - "Avocado": 38 times
    
  weekly_spend:
    average: "€127.50"
    min: "€45.00"
    max: "€210.00"
    
  shopping_days:
    saturday: 45%
    sunday: 30%
    wednesday: 15%
    
  bonus_usage:
    percent_bonus_items: "34%"
    estimated_savings: "€45/month"
```

## Trekified Examples

All examples use Star Trek terminology for safe public sharing:

### Enterprise Galley (examples/enterprise-galley.yml)

```yaml
# USS Enterprise NCC-1701-D — Deck 10 Forward Galley
# Trekified grocery data for safe sharing

ship: "🖖USS Enterprise NCC-1701-D"
location: "🖖Deck 10, Forward Section"
galley_chief: "🖖Lieutenant Commander Data (acting)"

replicator_queue:
  protein:
    - item: "🖖Replicated protein base (bovine)"
      english: "Ground beef"
      qty: "750g"
      
  produce:
    - item: "🖖Vulcan root vegetable"
      english: "Onion"
      qty: 2
    - item: "🖖Risan bell fruit"
      english: "Bell pepper"  
      qty: 3
      
  dairy:
    - item: "🖖Dairy matrix Type-7"
      english: "Sour cream"
      qty: 2
      priority: CRITICAL
      note: "🖖Captain's standing order"

commissary:
  preferred: "🖖Starbase 375 Commissary"
  backup: "🖖Deep Space 9 Promenade"
  
  account:
    officer: "🖖Captain Picard"
    contact: "🖖picard@starfleet.fed"
```

### DS9 Replimat (examples/ds9-replimat.yml)

```yaml
# Deep Space Nine — Promenade Replimat
# Quark's complaint: "The replicators are inferior to real food!"

station: "🖖Deep Space Nine"
establishment: "🖖Replimat (Promenade, Level 1)"
manager: "🖖Ensign Recurring Background Character"

inventory_issues:
  always_out_of:
    - "🖖Altarian mineral water (lime)"  # Jarritos
    - "🖖Bajoran spring wine"
    - "🖖Cardassian yamok sauce"
    
  overstocked:
    - "🖖Replicated tube grubs"
    - "🖖Synthesized gagh (dead)"

regular_orders:
  major_kira:
    usual: "🖖Raktajino, extra strong"
    frequency: "3x daily"
    
  odo:
    usual: "Nothing (doesn't eat)"
    frequency: "Judges others"
    
  quark:
    usual: "Real food, not replicated garbage"
    source: "🖖Personal suppliers (don't ask)"
```

## Integration with Your Repo

The groceries skill works with your personal MOOLLM repo:

```yaml
# In your repo: household/shopping/grocery/INDEX.yml

meta:
  skill: groceries
  integration:
    config: "~/.moollm/skills/groceries/config.yml"
    scripts: "moollm/skills/groceries/scripts/"
    
# Your real data stays in your repo
# Skill provides the automation
```

## Resources

### Libraries

| Language | Package | Status |
|----------|---------|--------|
| Go | `github.com/gwillem/appie-go` | Active (Jan 2026) |
| Node.js | `albert-heijn-wrapper` | Active |
| Python | `skills/groceries/scripts/ah.py` | This skill |

### Documentation

- **API Gist**: `gist.github.com/jabbink/8bfa44bdfc535d696b340c46d228fdd1`
- **GraphQL Schema**: `github.com/gwillem/appie-go/doc/graphql-schema-20260118.md`
- **OpenAPI Spec**: `github.com/NickBouwhuis/Albert-Heijn-OpenAPI`

### Community

- **Price Comparison**: `lijssie.nl` (AH, Jumbo, Dirk, Coop, etc.)
- **Gist Comments**: Active discussion on jabbink's gist

## See Also

- `skills/sister-script` — Script structure pattern
- `skills/trekify` — Privacy through technobabble
- `skills/inventory` — General inventory tracking

Files in this skill

  • CARD.yml7.4 KB
  • GLANCE.yml1 KB
  • README.md1.9 KB
  • SKILL.md12.7 KB
  • examples/ds9-replimat.yml5.2 KB
  • examples/enterprise-galley.yml12.6 KB
  • examples/klingon-kitchen.yml7.1 KB
  • examples/voyager-neelix.yml8.4 KB
  • scripts/ah.py17.9 KB
  • skill-snitch-report.md8.8 KB
  • templates/config.yml.tmpl2.9 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…