Skill: xenofex7/milkee (version 2.0.0)

--- name: milkee description: "Complete MILKEE accounting integration for Swiss businesses. Manage projects, customers, time tracking, tasks, and products. Use when: (1) tracking billable time with start/stop timers, (2) creating/managing projects and customers, (3) recording work entries with descriptions, (4) viewing daily time summaries. Features smart fuzzy project matching." metadata: openclaw: requires: env: - MILKEE_API_TOKEN - MILKEE_COMPANY_ID --- # MILKEE Skill Complete integration for MILKEE Swiss accounting software. Manage projects, customers, time tracking, tasks, and products. ## Features - ⏱️ **Time Tracking** – Start/stop timers with fuzzy project matching - 👥 **Customers** – Full CRUD operations - 📋 **Projects** – Create, update, manage budgets - ✅ **Tasks** – Track project tasks - 📦 **Products** – Manage billable items ## Quick Start ### Time Tracking (Main Feature) ```bash # Start timer (smart fuzzy match) python3 scripts/milkee.py start_timer "Website" "Building authentication" # Stop timer (auto-logs to MILKEE) python3 scripts/milkee.py stop_timer # Show today's times python3 scripts/milkee.py list_times_today ``` ### Projects ```bash python3 scripts/milkee.py list_projects python3 scripts/milkee.py create_project "My Project" --customer-id 123 --budget 5000 python3 scripts/milkee.py update_project 456 --name "Updated" --budget 6000 ``` ### Customers ```bash python3 scripts/milkee.py list_customers # Create with all fields python3 scripts/milkee.py create_customer "Example AG" \ --street "Musterstrasse 1" \ --zip "8000" \ --city "Zürich" \ --phone "+41 44 123 45 67" \ --email "[REDACTED_SECRET]" \ --website "https://example.ch" # Update specific fields python3 scripts/milkee.py update_customer 123 --name "New Name" --phone "+41 44 999 88 77" ``` ### Tasks & Products ```bash python3 scripts/milkee.py list_tasks python3 scripts/milkee.py create_task "Implement feature" --project-id 456 python3 scripts/milkee.py list_products python3 scripts/milkee.py create_product "Consulting Hour" --price 150 ``` ## Configuration Set environment variables: ```bash export MILKEE_API_TOKEN="USER_ID|API_KEY" export MILKEE_COMPANY_ID="YOUR_COMPANY_ID" ``` Or configure via your gateway config under `skills.entries.milkee.env`. ### Get Your Credentials 1. Log in to MILKEE → **Settings** → **API** 2. Copy your User ID and API Key 3. Format: `USER_ID|API_KEY` 4. Company ID is shown in Settings ## Special Features ### Fuzzy Project Matching When you say "Website", the skill: 1. Fetches all projects from MILKEE 2. Fuzzy-matches using Levenshtein distance 3. Auto-selects the closest match 4. Starts timer on that project ### Timer Persistence - Timer state saved to `~/.milkee_timer` - Survives between sessions - Auto-calculates elapsed time on stop ### Daily Summary `list_times_today` shows: - All time entries for today - Duration per entry - Total hours worked ## Technical Details - **Language**: Python 3.8+ - **Dependencies**: None (stdlib only) - **Timer File**: `~/.milkee_timer` (JSON) - **API Docs**: https://apidocs.milkee.ch/api --- **Author**: xenofex7 | **Version**: 2.0.0

path: INSTALLATION_GUIDE.md
content: # MILKEE Skill - Installation Guide

---

## Prerequisites

- Python 3.8+
- MILKEE account with API access
- Clawdbot installed

---

## Step 1: Get Your Credentials

### API Token

1. Log in to MILKEE (https://app.milkee.ch)
2. Go to Settings → API Settings
3. Copy your **User ID** and **API Key**
4. Combine as: `USER_ID|API_KEY`

**Example**: `28014|MqF8I04nbuFd4aT4A5Y6hBqaDDd9T2sjVT5Xu2ZAc799c9d2`

### Company ID

1. In MILKEE, check the URL: `https://app.milkee.ch/companies/XXXX/`
2. XXXX is your **Company ID**

**Example**: `4086`

---

## Step 2: Choose Installation Method

### Method A: Upload to Skill Hub (Recommended)

1. Extract `milkee.skill` from this package
2. Go to your Skill Hub (https://hub.clawdbot.com or your instance)
3. Click "Upload New Skill"
4. Select `milkee.skill`
5. Fill in metadata:
   - Name: `milkee`
   - Category: `Accounting / Time Tracking`
   - Tags: `time-tracking, accounting, projects, swiss`
   - Description: See README.md
6. Review and publish
7. Install via Clawdbot: `clawdbot skill install milkee`

### Method B: Manual Installation

1. Extract the ZIP package
2. Unzip `milkee.skill` to `~/.clawdbot/skills/milkee/`
3. Configure Clawdbot (see Step 3 below)

---

## Step 3: Configure Clawdbot

Edit `~/.clawdbot/clawdbot.json` and add:

```json
{
  "skills": {
    "milkee": {
      "env": {
        "MILKEE_API_TOKEN": "USER_ID|API_KEY",
        "MILKEE_COMPANY_ID": "YOUR_COMPANY_ID"
      }
    }
  }
}
```

**Replace**:
- `USER_ID|API_KEY` with your actual credentials
- `YOUR_COMPANY_ID` with your actual company ID

**Example**:
```json
{
  "skills": {
    "milkee": {
      "env": {
        "MILKEE_API_TOKEN": "28014|MqF8I04nbuFd4aT4A5Y6hBqaDDd9T2sjVT5Xu2ZAc799c9d2",
        "MILKEE_COMPANY_ID": "4086"
      }
    }
  }
}
```

---

## Step 4: Test Installation

Run this command to verify everything works:

```bash
export MILKEE_API_TOKEN="USER_ID|API_KEY"
export MILKEE_COMPANY_ID="YOUR_COMPANY_ID"

python3 ~/.clawdbot/skills/milkee/scripts/milkee.py list_projects
```

**Expected output**:
```
📋 X Projects:

  • Project Name (ID: 123)
  • Another Project (ID: 456)
```

---

## Step 5: Start Using

### Basic Commands

**Start a timer**:
```bash
python3 scripts/milkee.py start_timer "ProjectName" "What you're working on"
```

**Stop the timer**:
```bash
python3 scripts/milkee.py stop_timer
```

**View today's times**:
```bash
python3 scripts/milkee.py list_times_today
```

**List all projects**:
```bash
python3 scripts/milkee.py list_projects
```

---

## Troubleshooting

### "HTTP 401: Unauthorized"
- Check API token format: `USER_ID|API_KEY` (with pipe)
- Verify credentials in MILKEE Settings
- Token might be expired, regenerate in MILKEE

### "HTTP 403: Forbidden"
- Wrong Company ID
- Check URL: `https://app.milkee.ch/companies/XXXX/`
- Use correct XXXX value

### "HTTP 404: Not Found"
- Project/customer ID doesn't exist
- Try listing: `python3 scripts/milkee.py list_projects`

### "Command not found: milkee.py"
- Make sure path is correct
- Try full path: `python3 ~/.clawdbot/skills/milkee/scripts/milkee.py`

---

## Security Notes

⚠️ **Important**:
- Never commit API tokens to git
- Use `.gitignore` to protect config files
- Rotate keys regularly
- Use environment variables, not hardcoded credentials

---

## Next Steps

1. ✅ Installation complete
2. 📖 Read SKILL.md (inside milkee.skill) for full documentation
3. ⏱️ Start tracking time!
4. 📚 Check references/ folder for API details

---

## Support

For issues:
1. Check this guide
2. Review SKILL.md in milkee.skill
3. Check official MILKEE docs: https://apidocs.milkee.ch/api

---

**Happy time tracking!** ⏱️🦭


path: README.md
content: # MILKEE Skill - Complete Package

**Version**: 1.0  
**Status**: ✅ Production Ready  
**Date**: 2026-01-17  

---

## 📦 Package Contents

This ZIP contains everything you need to use the MILKEE skill with Clawdbot.

```
├── milkee.skill              ← The skill (upload to Skill Hub)
├── README.md                 ← This file
└── INSTALLATION_GUIDE.md     ← Setup instructions
```

---

## 🚀 Quick Start

### Option A: Upload to Skill Hub (Recommended)

1. Extract `milkee.skill` from this ZIP
2. Go to your Skill Hub
3. Click "Upload New Skill"
4. Select `milkee.skill`
5. Fill metadata:
   - **Name**: `milkee`
   - **Category**: `Accounting / Time Tracking`
   - **Tags**: `time-tracking, accounting, projects, swiss`
6. Publish!

### Option B: Manual Installation

1. Extract to `~/.clawdbot/skills/milkee/`
2. Configure in `~/.clawdbot/clawdbot.json`:
   ```json
   {
     "skills": {
       "milkee": {
         "env": {
           "MILKEE_API_TOKEN": "USER_ID|API_KEY",
           "MILKEE_COMPANY_ID": "YOUR_COMPANY_ID"
         }
       }
     }
   }
   ```
3. Test: `python3 scripts/milkee.py list_projects`

See `INSTALLATION_GUIDE.md` for details.

---

## ✨ Features

- ⏱️ Time tracking with smart fuzzy project matching
- 👥 Customer management
- 📋 Project management
- ✅ Task management
- 📦 Product management
- 📊 Daily time summaries
- 🚀 No external dependencies

---

## 📖 Documentation

Inside `milkee.skill` you'll find:
- **SKILL.md** - Complete documentation
- **scripts/milkee.py** - CLI tool
- **references/configuration.md** - Setup guide
- **references/api-endpoints.md** - API reference

---

## 🔧 Configuration

Get credentials from MILKEE:
1. Log in to MILKEE (https://app.milkee.ch)
2. Settings → API Settings
3. Copy: User ID | API Key
4. Check URL for Company ID

See `INSTALLATION_GUIDE.md` for step-by-step instructions.

---

## 💡 Usage

```bash
# Start timer
python3 scripts/milkee.py start_timer "ProjectName" "What you're working on"

# Stop timer
python3 scripts/milkee.py stop_timer

# View today's times
python3 scripts/milkee.py list_times_today
```

See inside `milkee.skill` for complete command reference.

---

## 📚 Official Documentation

- MILKEE API: https://apidocs.milkee.ch/api
- Authentication: https://apidocs.milkee.ch/api/authentifizierung.html

---

## 👤 Created By

**Seal** 🦭 | 2026-01-17

**Happy time tracking!** ⏱️🚀


path: references/api-endpoints.md
content: # MILKEE API v2 Endpoints Reference

**Official Documentation**: https://apidocs.milkee.ch/api

## Base URL
```
https://app.milkee.ch/api/v2/companies/{company_id}/{resource}
```

## Authentication
Header: `Authorization: Bearer USER_ID|API_KEY`

Example:
```
Authorization: Bearer 28014|MqF8I04nb2ZAc799c9d2
```

---

## Projects

### List Projects
```
GET /projects
```

### Create Project
```
POST /projects
{
  "name": "Website Redesign",
  "customer_id": 42,
  "project_type": "byHour",
  "budget": 8500,
  "hourly_rate": 150
}
```

### Update Project
```
PUT /projects/{id}
{
  "name": "string",
  "budget": decimal,
  "hourly_rate": decimal
}
```

---

## Customers

### List Customers
```
GET /customers
GET /customers?filter[name]=searchterm
```

### Create Customer
```
POST /customers
{
  "name": "Example Corp AG",
  "city": "Zurich",
  "address": "Main Street 123",
  "zip": "8000",
  "country": "Switzerland",
  "email": "[REDACTED_SECRET]"
}
```

### Update Customer
```
PUT /customers/{id}
{
  "name": "string",
  "city": "string",
  "address": "string"
}
```

---

## Time Entries

### List Times
```
GET /times
GET /times?filter[date]=2024-01-26
GET /times?filter[project_id]=123
```

### Create Time Entry
```
POST /times
{
  "project_id": 456,
  "date": "2024-01-26",
  "hours": 4,
  "minutes": 30,
  "description": "Implementation of database schema",
  "billable": true,
  "start": "09:00",
  "end": "13:30"
}
```

---

## Tasks

### List Tasks
```
GET /tasks
GET /tasks?filter[project_id]=123
```

### Create Task
```
POST /tasks
{
  "name": "Set up CI/CD pipeline",
  "project_id": 456,
  "description": "GitHub Actions workflow for automated testing",
  "status": "open"
}
```

### Update Task
```
PUT /tasks/{id}
{
  "name": "string",
  "status": "open|done"
}
```

---

## Products

### List Products
```
GET /products
```

### Create Product
```
POST /products
{
  "name": "Technical Consulting - Hourly",
  "price": 150,
  "description": "Professional software development consulting"
}
```

### Update Product
```
PUT /products/{id}
{
  "name": "string",
  "price": decimal
}
```

---

## Error Handling

### HTTP Status Codes
- `200`: Success
- `400`: Bad request
- `401`: Unauthorized (check token)
- `403`: Forbidden (check company_id)
- `404`: Not found (check resource id)
- `500`: Server error

### Error Response Format
```json
{
  "message": "Error description"
}
```

---

## Rate Limiting

- No documented rate limit in MILKEE API
- Recommended: Max 100 requests/minute
- Retry with exponential backoff on 500 errors

---

## Response Format

All successful responses follow:
```json
{
  "data": {
    "id": integer,
    "name": "string",
    ...
  }
}
```

For list endpoints:
```json
{
  "data": [
    {...},
    {...}
  ]
}
```


path: references/configuration.md
content: # MILKEE Configuration Guide

**Official MILKEE Docs**: https://apidocs.milkee.ch/api/authentifizierung.html

## Environment Variables

The skill requires these environment variables:

### MILKEE_API_TOKEN (required)
**Format**: `USER_ID|API_KEY`

Get from MILKEE Settings → API:
1. Log in to MILKEE
2. Settings → API Settings
3. Copy your User ID and API Key
4. Combine as: `USER_ID|API_KEY`

**Example**:
```
28014|MqF8I04nbuFd4aT5Xu2ZAc799c9d2
```

### MILKEE_COMPANY_ID (required)
**Format**: String (numeric ID)

Get from MILKEE URL or Settings:
1. Log in to MILKEE
2. URL will show: `https://app.milkee.ch/companies/XXXX/...`
3. XXXX is your Company ID

**Example**:
```
4086
```

---

## Clawdbot Configuration

Add to `~/.clawdbot/clawdbot.json`:

```json
{
  "skills": {
    "milkee": {
      "env": {
        "MILKEE_API_TOKEN": "USER_ID|API_KEY",
        "MILKEE_COMPANY_ID": "4226"
      }
    }
  }
}
```

---

## Verification

Test configuration:

```bash
export MILKEE_API_TOKEN="28014|MqF8I04nbuFd4aT4A5Y6hBqaDDd9T2sjVT5Xu2ZAc799c9d2"
export MILKEE_COMPANY_ID="4186"

python3 scripts/milkee.py list_projects
```

Expected output:
```
📋 X Projects:

  • Project Name (ID: 123)
  • ...
```

---

## Troubleshooting

### "No credentials configured"
- Check MILKEE_API_TOKEN and MILKEE_COMPANY_ID are set
- Verify format: `USER_ID|API_KEY` (with pipe separator)

### "HTTP 401: Unauthorized"
- Check API token format
- Verify credentials in MILKEE Settings

### "HTTP 403: Forbidden"
- Wrong COMPANY_ID
- Check URL: `https://app.milkee.ch/companies/XXXX/`
- Use correct XXXX value

### "HTTP 404: Not found"
- Resource ID doesn't exist
- Check project_id, customer_id, task_id

---

## Security Notes

- **Never commit credentials** to git
- Use environment variables or config files
- Rotate API keys regularly
- Use least-privilege approach (only needed permissions)
- Keep API tokens confidential

---

## Getting Help

1. Check [api-endpoints.md](api-endpoints.md) for endpoint reference
2. Review error messages in output
3. Verify credentials in MILKEE Settings
4. Check network connectivity to `app.milkee.ch`


path: scripts/milkee.py
content: #!/usr/bin/env python3
"""
MILKEE Accounting API Integration
Complete project, customer, time tracking, task & product management
"""

import argparse
import os
import sys
import json
import urllib.request
import urllib.error
from datetime import datetime, timedelta
from difflib import SequenceMatcher
from pathlib import Path

# Configuration
API_TOKEN = os.getenv('MILKEE_API_TOKEN', '')
COMPANY_ID = os.getenv('MILKEE_COMPANY_ID', '')
API_BASE = "https://app.milkee.ch/api/v2"

# Timer state file
TIMER_FILE = Path.home() / ".milkee_timer"

def api_call(method, endpoint, data=None):
    """Make API call to MILKEE"""
    if not API_TOKEN or not COMPANY_ID:
        print("❌ Error: MILKEE_API_TOKEN and MILKEE_COMPANY_ID required!")
        sys.exit(1)
    
    url = f"{API_BASE}/companies/{COMPANY_ID}/{endpoint}"
    
    try:
        req = urllib.request.Request(
            url,
            headers={
                "Authorization": f"Bearer {API_TOKEN}",
                "Content-Type": "application/json",
                "Accept": "application/json"
            },
            method=method
        )
        
        if data:
            # Remove None values
            data = {k: v for k, v in data.items() if v is not None}
            req.data = json.dumps(data).encode()
        
        with urllib.request.urlopen(req, timeout=10) as response:
            return json.loads(response.read().decode())
    
    except urllib.error.HTTPError as e:
        try:
            error_data = json.loads(e.read().decode())
            print(f"❌ HTTP {e.code}: {error_data.get('message', 'Unknown error')}")
        except:
            print(f"❌ HTTP {e.code}: {e.reason}")
        sys.exit(1)
    except Exception as e:
        print(f"❌ Error: {e}")
        sys.exit(1)

def fuzzy_match(search_term, items, key='name'):
    """Fuzzy match search term to items"""
    search_term = search_term.lower()
    
    matches = []
    for item in items:
        name = item.get(key, '').lower()
        ratio = SequenceMatcher(None, search_term, name).ratio()
        if ratio > 0.4:
            matches.append((ratio, item))
    
    if matches:
        matches.sort(reverse=True)
        return matches[0][1]
    return None

# ============ PROJECTS ============

def list_projects(args):
    """List all projects"""
    result = api_call("GET", "projects")
    projects = result.get('data', [])
    print(f"\n📋 {len(projects)} Projects:\n")
    
    for p in projects:
        print(f"  • {p.get('name')} (ID: {p.get('id')})")
        if p.get('budget'):
            print(f"    Budget: CHF {p.get('budget')}")
        if p.get('customer_id'):
            print(f"    Customer ID: {p.get('customer_id')}")

def create_project(args):
    """Create new project"""
    data = {
        "name": args.name,
        "customer_id": args.customer_id,
        "budget": args.budget,
        "project_type": "byHour"
    }
    
    result = api_call("POST", "projects", data)
    project = result.get('data', {})
    print(f"✅ Project created: {project.get('name')} (ID: {project.get('id')})")

def update_project(args):
    """Update project"""
    data = {
        "name": args.name,
        "budget": args.budget,
        "customer_id": args.customer_id
    }
    
    result = api_call("PUT", f"projects/{args.id}", data)
    print(f"✅ Project updated")

# ============ CUSTOMERS ============

def list_customers(args):
    """List all customers"""
    result = api_call("GET", "customers")
    customers = result.get('data', [])
    print(f"\n👥 {len(customers)} Customers:\n")
    
    for c in customers:
        print(f"  • {c.get('name')} (ID: {c.get('id')})")
        addr_parts = []
        if c.get('street'):
            addr_parts.append(c['street'])
        if c.get('zip') or c.get('city'):
            addr_parts.append(f"{c.get('zip', '')} {c.get('city', '')}".strip())
        if addr_parts:
            print(f"    📍 {', '.join(addr_parts)}")
        if c.get('phone'):
            print(f"    📞 {c['phone']}")
        if c.get('email'):
            print(f"    ✉️  {c['email']}")

def create_customer(args):
    """Create new customer"""
    data = {
        "name": args.name,
        "street": args.street,
        "zip": args.zip,
        "city": args.city,
        "country": args.country,
        "phone": args.phone,
        "email": args.email,
        "website": args.website
    }
    
    result = api_call("POST", "customers", data)
    customer = result.get('data', {})
    print(f"✅ Customer created: {customer.get('name')} (ID: {customer.get('id')})")

def update_customer(args):
    """Update customer"""
    data = {
        "name": args.name,
        "street": args.street,
        "zip": args.zip,
        "city": args.city,
        "country": args.country,
        "phone": args.phone,
        "email": args.email,
        "website": args.website
    }
    
    result = api_call("PUT", f"customers/{args.id}", data)
    print(f"✅ Customer updated")

# ============ TIME TRACKING ============

def start_timer(args):
    """Start timer (smart project matching)"""
    result = api_call("GET", "projects")
    projects = result.get('data', [])
    project = fuzzy_match(args.project, projects)
    
    if not project:
        print(f"❌ No project found matching '{args.project}'")
        sys.exit(1)
    
    timer_data = {
        "project_id": project['id'],
        "project_name": project['name'],
        "description": args.description or "",
        "start_time": datetime.now().isoformat()
    }
    
    with open(TIMER_FILE, 'w') as f:
        json.dump(timer_data, f)
    
    print(f"✅ Timer started: {project['name']}")
    if args.description:
        print(f"   Description: {args.description}")

def stop_timer(args):
    """Stop timer and log to MILKEE"""
    if not TIMER_FILE.exists():
        print("❌ No timer running")
        sys.exit(1)
    
    with open(TIMER_FILE, 'r') as f:
        timer_data = json.load(f)
    
    start_time = datetime.fromisoformat(timer_data['start_time'])
    end_time = datetime.now()
    duration = end_time - start_time
    
    hours = int(duration.total_seconds() // 3600)
    minutes = int((duration.total_seconds() % 3600) // 60)
    
    data = {
        "project_id": timer_data['project_id'],
        "date": end_time.strftime("%Y-%m-%d"),
        "hours": hours,
        "minutes": minutes,
        "description": timer_data['description'],
        "billable": True
    }
    
    result = api_call("POST", "times", data)
    print(f"✅ Time logged: {hours}h {minutes}min on {timer_data['project_name']}")
    TIMER_FILE.unlink()

def list_times_today(args):
    """Show today's time entries"""
    today = datetime.now().strftime("%Y-%m-%d")
    result = api_call("GET", f"times?filter[date]={today}")
    times = result.get('data', [])
    
    print(f"\n⏱️  Time entries for {today}:\n")
    
    total_minutes = 0
    for t in times:
        hours = t.get('hours', 0)
        minutes = t.get('minutes', 0)
        total_minutes += hours * 60 + minutes
        
        print(f"  • {t.get('description', 'No desc')} - {hours}h {minutes}min")
        print(f"    Project: {t.get('project', {}).get('name', 'N/A')}")
    
    total_hours = total_minutes // 60
    remaining_minutes = total_minutes % 60
    print(f"\n📊 Total: {total_hours}h {remaining_minutes}min\n")

# ============ TASKS ============

def list_tasks(args):
    """List tasks"""
    endpoint = "tasks"
    if args.project_id:
        endpoint += f"?filter[project_id]={args.project_id}"
    
    result = api_call("GET", endpoint)
    tasks = result.get('data', [])
    print(f"\n✅ {len(tasks)} Tasks:\n")
    
    for t in tasks:
        print(f"  • {t.get('name')} (ID: {t.get('id')})")
        if t.get('status'):
            print(f"    Status: {t.get('status')}")

def create_task(args):
    """Create task"""
    data = {
        "name": args.name,
        "project_id": args.project_id,
        "description": args.description
    }
    
    result = api_call("POST", "tasks", data)
    task = result.get('data', {})
    print(f"✅ Task created: {task.get('name')} (ID: {task.get('id')})")

def update_task(args):
    """Update task"""
    data = {
        "name": args.name,
        "status": args.status
    }
    
    result = api_call("PUT", f"tasks/{args.id}", data)
    print(f"✅ Task updated")

# ============ PRODUCTS ============

def list_products(args):
    """List products"""
    result = api_call("GET", "products")
    products = result.get('data', [])
    print(f"\n📦 {len(products)} Products:\n")
    
    for p in products:
        print(f"  • {p.get('name')} (ID: {p.get('id')})")
        if p.get('price'):
            print(f"    Price: CHF {p.get('price')}")

def create_product(args):
    """Create product"""
    data = {
        "name": args.name,
        "price": args.price,
        "description": args.description
    }
    
    result = api_call("POST", "products", data)
    product = result.get('data', {})
    print(f"✅ Product created: {product.get('name')} (ID: {product.get('id')})")

def update_product(args):
    """Update product"""
    data = {
        "name": args.name,
        "price": args.price
    }
    
    result = api_call("PUT", f"products/{args.id}", data)
    print(f"✅ Product updated")

# ============ CLI ============

def main():
    parser = argparse.ArgumentParser(description="MILKEE Accounting CLI")
    subparsers = parser.add_subparsers(dest="command", required=True)
    
    # === Projects ===
    subparsers.add_parser("list_projects", help="List all projects")
    
    p = subparsers.add_parser("create_project", help="Create a project")
    p.add_argument("name", help="Project name")
    p.add_argument("--customer-id", type=int, help="Customer ID")
    p.add_argument("--budget", type=float, help="Budget in CHF")
    
    p = subparsers.add_parser("update_project", help="Update a project")
    p.add_argument("id", help="Project ID")
    p.add_argument("--name", help="New name")
    p.add_argument("--customer-id", type=int, help="Customer ID")
    p.add_argument("--budget", type=float, help="Budget in CHF")
    
    # === Customers ===
    subparsers.add_parser("list_customers", help="List all customers")
    
    p = subparsers.add_parser("create_customer", help="Create a customer")
    p.add_argument("name", help="Customer name")
    p.add_argument("--street", help="Street address")
    p.add_argument("--zip", help="ZIP/postal code")
    p.add_argument("--city", help="City")
    p.add_argument("--country", default="CH", help="Country code (default: CH)")
    p.add_argument("--phone", help="Phone number")
    p.add_argument("--email", help="Email address")
    p.add_argument("--website", help="Website URL")
    
    p = subparsers.add_parser("update_customer", help="Update a customer")
    p.add_argument("id", help="Customer ID")
    p.add_argument("--name", help="New name")
    p.add_argument("--street", help="Street address")
    p.add_argument("--zip", help="ZIP/postal code")
    p.add_argument("--city", help="City")
    p.add_argument("--country", help="Country code")
    p.add_argument("--phone", help="Phone number")
    p.add_argument("--email", help="Email address")
    p.add_argument("--website", help="Website URL")
    
    # === Time Tracking ===
    p = subparsers.add_parser("start_timer", help="Start a timer")
    p.add_argument("project", help="Project name (fuzzy match)")
    p.add_argument("description", nargs="?", help="Work description")
    
    subparsers.add_parser("stop_timer", help="Stop timer and log time")
    subparsers.add_parser("list_times_today", help="Show today's time entries")
    
    # === Tasks ===
    p = subparsers.add_parser("list_tasks", help="List tasks")
    p.add_argument("--project-id", type=int, help="Filter by project ID")
    
    p = subparsers.add_parser("create_task", help="Create a task")
    p.add_argument("name", help="Task name")
    p.add_argument("--project-id", type=int, required=True, help="Project ID")
    p.add_argument("--description", help="Task description")
    
    p = subparsers.add_parser("update_task", help="Update a task")
    p.add_argument("id", help="Task ID")
    p.add_argument("--name", help="New name")
    p.add_argument("--status", help="Status")
    
    # === Products ===
    subparsers.add_parser("list_products", help="List all products")
    
    p = subparsers.add_parser("create_product", help="Create a product")
    p.add_argument("name", help="Product name")
    p.add_argument("--price", type=float, help="Price in CHF")
    p.add_argument("--description", help="Product description")
    
    p = subparsers.add_parser("update_product", help="Update a product")
    p.add_argument("id", help="Product ID")
    p.add_argument("--name", help="New name")
    p.add_argument("--price", type=float, help="Price in CHF")
    
    args = parser.parse_args()
    
    # Dispatch commands
    commands = {
        "list_projects": list_projects,
        "create_project": create_project,
        "update_project": update_project,
        "list_customers": list_customers,
        "create_customer": create_customer,
        "update_customer": update_customer,
        "start_timer": start_timer,
        "stop_timer": stop_timer,
        "list_times_today": list_times_today,
        "list_tasks": list_tasks,
        "create_task": create_task,
        "update_task": update_task,
        "list_products": list_products,
        "create_product": create_product,
        "update_product": update_product,
    }
    
    commands[args.command](args)

if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)