Skill: etopro/email-otp (version 1.0.0)

--- name: email-otp description: Create temporary email addresses and monitor for registration OTP codes or validation links version: 1.0.0 metadata: author: etopro --- # Email OTP Skill A skill for creating temporary email addresses and automatically extracting OTP codes and validation links from incoming emails. Uses the free mail.tm API (no API key required). ## When to Use This Skill Invoke this skill when the user asks to: - Create a temporary email address - Get a disposable email for signup/verification - Check for OTP (one-time password) codes - Wait for email verification links - Monitor an inbox for authentication codes ## Quick Start ```bash # Create a new temporary email python3 scripts/tempmail_otp.py create # Monitor for OTP codes (5 minute timeout) python3 scripts/tempmail_otp.py check # List current account and messages python3 scripts/tempmail_otp.py list ``` ## Commands ### Create Account ```bash python3 scripts/tempmail_otp.py create [OPTIONS] ``` **Options:** - `-e, --email ADDRESS` - Custom full email address - `-d, --domain DOMAIN` - Specific domain to use - `-p, --password PASSWORD` - Account password (auto-generated if not specified) - `--json` - Output as JSON **Example:** ```bash python3 scripts/tempmail_otp.py create --domain "marcilzo.com" ``` ### Check for OTP/Links ```bash python3 scripts/tempmail_otp.py check [OPTIONS] ``` **Options:** - `--timeout SECONDS` - Max seconds to wait (default: 300) - `--poll SECONDS` - Poll interval in seconds (default: 3) - `--sender EMAIL` - Only accept emails from this sender - `--subject TEXT` - Only accept emails with this in subject - `--pattern REGEX` - Custom regex pattern for OTP extraction - `--once` - Exit after first OTP found - `--json` - Output messages as JSON **Examples:** ```bash # Wait up to 2 minutes for OTP python3 scripts/tempmail_otp.py check --timeout 120 # Only accept emails from [REDACTED_SECRET] python3 scripts/tempmail_otp.py check --sender "[REDACTED_SECRET]" # Exit immediately after finding OTP python3 scripts/tempmail_otp.py check --once ``` ### List Account and Messages ```bash python3 scripts/tempmail_otp.py list ``` Shows the current account details and all messages in the inbox with extracted links. ### List Available Domains ```bash python3 scripts/tempmail_otp.py domains [--json] ``` ## Output Files When an OTP or link is found, the script automatically saves them to the unified state directory: - `~/.tempmail_otp/last_otp` - Contains the last extracted OTP code - `~/.tempmail_otp/last_link` - Contains the first interesting validation link found - `~/.tempmail_otp/account.json` - Account credentials (JWT token, email, password) All state files are stored in `~/.tempmail_otp/` with restricted permissions (0600). ## OTP Detection Patterns The script automatically detects OTP codes using these patterns: - 6-8 digit numbers (most common) - 4 digit numbers - "code: XXXXXX" format - "verification: XXXXXX" format - "otp: XXXXXX" format ## Link Extraction The script extracts all HTTP/HTTPS links from email HTML, filtering out: - Unsubscribe links - Tracking links - Image files (.png, .jpg, .gif) ## State Management All state is stored in a unified directory: `~/.tempmail_otp/` - `account.json` - Account credentials and JWT token (created by `create` command) - `last_otp` - Most recent OTP code extracted (created by `check` command) - `last_link` - First validation link extracted (created by `check` command) Files have restricted permissions (0600) for security. The `check` and `list` commands automatically use stored credentials. ### Design Rationale The unified state directory follows best practices for CLI tools: 1. **No project pollution** - No temporary files are created in your working directory 2. **Predictable location** - All state is in one place, easy to find and clean up 3. **Cross-session persistence** - Works from any directory on your system 4. **Permission safety** - Sensitive credentials have proper file permissions To reset all state: `rm -rf ~/.tempmail_otp/` ## Typical Workflow 1. **Create account** - Generate a new temporary email address 2. **Use email** - Provide the email during service signup 3. **Monitor inbox** - Run the check command to wait for OTP/link 4. **Extract code** - OTP is automatically displayed and saved to `~/.tempmail_otp/last_otp` 5. **Verify** - Use the OTP or link to complete verification ## Example Session ```bash # Create a temp email $ python3 scripts/tempmail_otp.py create Email: [REDACTED_SECRET] [REDACTED_SECRET] Domain: marcilzo.com Account saved to /home/user/.tempmail_otp/account.json # In another terminal, wait for OTP $ python3 scripts/tempmail_otp.py check --once Monitoring: [REDACTED_SECRET] Timeout: 300s | Poll interval: 3s -------------------------------------------------- 📧 New email from: [REDACTED_SECRET] Subject: Your verification code ✅ OTP FOUND: 842197 OTP saved to /home/user/.tempmail_otp/last_otp -------------------------------------------------- ``` ## Error Handling - If email address is already taken, the script automatically retries with a new username - Network errors are logged and the script continues polling - Invalid account state prompts to recreate the account ## API This skill uses the mail.tm REST API: - Base URL: `https://api.mail.tm` - Authentication: JWT Bearer token - No API key required ## Notes - Temporary emails may expire after inactivity periods - Some services may block temporary email domains - The script automatically handles account creation and JWT token management - OTP patterns cover most common formats, but custom regex can be provided via `--pattern`

path: README.md
content: # Email OTP Skill

A Python CLI tool and OpenClaw/Claude skill for creating temporary email addresses and automatically extracting OTP codes and validation links from incoming emails.

## Features

- **Free temporary email** - Uses mail.tm API (no API key required)
- **Automatic OTP extraction** - Detects 4-8 digit verification codes
- **Validation link extraction** - Extracts and saves verification URLs
- **Real-time monitoring** - Polls inbox for new messages with configurable timeout
- **Sender/subject filtering** - Only process emails from specific senders or with specific subjects
- **Unified state management** - All credentials and outputs stored in `~/.tempmail_otp/`

## Requirements

- Python 3.6+
- Standard library only (no external dependencies)
- Internet connection for mail.tm API

## Installation

1. Clone this repository:
```bash
git clone https://github.com/yourusername/skill-email-otp.git
cd skill-email-otp
```

2. Make the script executable (optional):
```bash
chmod +x scripts/tempmail_otp.py
```

3. (Optional) Install as a skill for OpenClaw/Claude:
   - Copy to `~/.openclaw/skills/email-otp/` or `~/.claude/skills/email-otp/`

## Quick Start

```bash
# Create a new temporary email
python3 scripts/tempmail_otp.py create

# Use the displayed email for signup, then monitor for OTP
python3 scripts/tempmail_otp.py check --once

# OTP is automatically saved to ~/.tempmail_otp/last_otp
cat ~/.tempmail_otp/last_otp
```

## Commands

### `create` - Create a temporary email account

```bash
python3 scripts/tempmail_otp.py create [OPTIONS]
```

| Option | Description |
|--------|-------------|
| `-e, --email ADDRESS` | Custom full email address |
| `-d, --domain DOMAIN` | Specific domain to use (check with `domains` command) |
| `-p, --password PASSWORD` | Account password (auto-generated if not specified) |
| `--json` | Output as JSON |

**Example:**
```bash
python3 scripts/tempmail_otp.py create --domain "marcilzo.com"
```

### `check` - Monitor inbox for OTP/links

```bash
python3 scripts/tempmail_otp.py check [OPTIONS]
```

| Option | Description |
|--------|-------------|
| `--timeout SECONDS` | Max seconds to wait (default: 300) |
| `--poll SECONDS` | Poll interval in seconds (default: 3) |
| `--sender EMAIL` | Only accept emails from this sender |
| `--subject TEXT` | Only accept emails with this in subject |
| `--pattern REGEX` | Custom regex pattern for OTP extraction |
| `--once` | Exit after first OTP found |
| `--json` | Output messages as JSON |

**Examples:**
```bash
# Wait up to 2 minutes for OTP
python3 scripts/tempmail_otp.py check --timeout 120

# Only accept emails from [REDACTED_SECRET]
python3 scripts/tempmail_otp.py check --sender "[REDACTED_SECRET]"

# Exit immediately after finding OTP
python3 scripts/tempmail_otp.py check --once
```

### `list` - Show account and messages

```bash
python3 scripts/tempmail_otp.py list
```

Displays the current account details and all messages in the inbox with extracted links.

### `domains` - List available domains

```bash
python3 scripts/tempmail_otp.py domains [--json]
```

Shows all available mail.tm domains with their status.

## State Management

All state is stored in a unified directory: `~/.tempmail_otp/`

| File | Purpose |
|------|---------|
| `account.json` | Account credentials and JWT token |
| `last_otp` | Most recent OTP code extracted |
| `last_link` | First validation link extracted |

Files have restricted permissions (0600) for security.

**Reset all state:**
```bash
rm -rf ~/.tempmail_otp/
```

## OTP Detection Patterns

The script automatically detects OTP codes using these patterns:
- 6-8 digit numbers (most common)
- 4 digit numbers
- `code: XXXXXX` format
- `verification: XXXXXX` format
- `otp: XXXXXX` format

For custom patterns, use the `--pattern` option with a regex containing a capture group.

## Link Extraction

Extracts all HTTP/HTTPS links from email HTML, filtering out:
- Unsubscribe links
- Tracking links
- Image files (.png, .jpg, .gif)

## Example Session

```bash
$ python3 scripts/tempmail_otp.py create
Email: [REDACTED_SECRET]
[REDACTED_SECRET])
Domain: marcilzo.com

Account saved to /home/user/.tempmail_otp/account.json

# Use email for signup on a website, then:

$ python3 scripts/tempmail_otp.py check --once
Monitoring: [REDACTED_SECRET]
Timeout: 300s | Poll interval: 3s
--------------------------------------------------

📧 New email from: [REDACTED_SECRET]
   Subject: Your verification code

✅ OTP FOUND: 842197
OTP saved to /home/user/.tempmail_otp/last_otp
--------------------------------------------------

$ cat ~/.tempmail_otp/last_otp
842197
```

## API

This tool uses the mail.tm REST API:
- Base URL: `https://api.mail.tm`
- Authentication: JWT Bearer token
- No API key required

## Usage as a Skill

When installed as a skill in OpenClaw or Claude Code, the AI assistant can:
- Create temporary email addresses on demand
- Automatically wait for and extract OTP codes
- Parse validation links from emails

See `SKILL.md` for full skill documentation.

## License

MIT

## Notes

- Temporary emails may expire after inactivity periods
- Some services may block temporary email domains
- The script automatically handles account creation and JWT token management
- OTP patterns cover most common formats, but custom regex can be provided via `--pattern`


path: scripts/tempmail_otp.py
content: #!/usr/bin/env python3
"""
Temporary Email OTP Extractor

Uses mail.tm API (free, no API key required).
Supports custom domains, creates accounts, and extracts OTP codes.

API: https://api.mail.tm

Usage:
    tempmail_otp.py create [-d DOMAIN] [-e EMAIL] [-p PASSWORD]
    tempmail_otp.py check [--timeout SECONDS] [--poll SECONDS] [--sender SENDER] [--subject SUBJECT]
    tempmail_otp.py list
    tempmail_otp.py domains
"""

import argparse
import json
import os
import re
import sys
import time
import uuid
from typing import Optional

try:
    import urllib.request
    import urllib.error
except ImportError:
    print("Error: urllib required (Python 3 standard library)")
    sys.exit(1)


# Unified state directory for all temp files
# Following XDG-like pattern: ~/.tempmail_otp/
STATE_DIR = os.path.expanduser("~/.tempmail_otp")
STATE_FILE = os.path.join(STATE_DIR, "account.json")
LAST_OTP_FILE = os.path.join(STATE_DIR, "last_otp")
LAST_LINK_FILE = os.path.join(STATE_DIR, "last_link")


def ensure_state_dir():
    """Create state directory if it doesn't exist."""
    os.makedirs(STATE_DIR, exist_ok=True)


def save_state(data: dict):
    """Save account state to file."""
    ensure_state_dir()
    with open(STATE_FILE, "w") as f:
        json.dump(data, f, indent=2)
    os.chmod(STATE_FILE, 0o600)  # Restrictive permissions


def load_state() -> Optional[dict]:
    """Load account state from file."""
    if not os.path.exists(STATE_FILE):
        return None
    with open(STATE_FILE, "r") as f:
        return json.load(f)


def delete_state():
    """Delete account state file."""
    if os.path.exists(STATE_FILE):
        os.remove(STATE_FILE)


def fetch_json(url: str, data: Optional[dict] = None, method: str = "GET", [REDACTED_SECRET] = None) -> dict:
    """Fetch JSON data from URL."""
    headers = {
        "User-Agent": "Mozilla/5.0",
        "Accept": "application/json",
        "Content-Type": "application/json",
    }

    if [REDACTED_SECRET]"Authorization"] = f"Bearer {token}"

    body = None
    if data:
        body = json.dumps(data).encode("utf-8")

    req = urllib.request.Request(url, data=body, headers=headers, method=method)

    try:
        with urllib.request.urlopen(req, timeout=30) as response:
            response_text = response.read().decode()
            if not response_text:
                return {}
            return json.loads(response_text)
    except urllib.error.HTTPError as e:
        body = e.read().decode()
        try:
            err_json = json.loads(body)
            detail = err_json.get("detail", err_json.get("hydra:description", body))
            raise RuntimeError(f"HTTP {e.code}: {detail}")
        except json.JSONDecodeError:
            raise RuntimeError(f"HTTP {e.code}: {body}")


def get_domains() -> list:
    """Get available domains from mail.tm."""
    url = "https://api.mail.tm/domains"
    data = fetch_json(url)
    if "hydra:member" in data:
        return data["hydra:member"]
    return data if isinstance(data, list) else [data]


def create_account(address: Optional[str] = None, domain: Optional[str] = None, [REDACTED_SECRET] = None) -> dict:
    """Create a new mail.tm account."""
    import random

    domains = get_domains()
    active_domains = [d["domain"] for d in domains if d.get("isActive")]

    if not active_domains:
        raise RuntimeError("No active domains available")

    if domain and domain not in active_domains:
        print(f"Warning: Domain {domain} not available, using random")

    target_domain = domain if domain in active_domains else random.choice(active_domains)

    # Generate random username if address not provided
    if not address:
        username = str(uuid.uuid4())[:8]
        address = f"{username}@{target_domain}"

    if not [REDACTED_SECRET] = str(uuid.uuid4())

    # Create account
    url = "https://api.mail.tm/accounts"
    data = {"address": address, "password": password}

    try:
        result = fetch_json(url, data=data, method="POST")
    except RuntimeError as e:
        if "already used" in str(e):
            # Try with different username
            username = str(uuid.uuid4())[:12]
            address = f"{username}@{target_domain}"
            data["address"] = address
            result = fetch_json(url, data=data, method="POST")
        else:
            raise

    account_id = result.get("id")
    address = result.get("address", address)

    # Login to get token
    token_url = "https://api.mail.tm/token"
    token_data = {"address": address, "password": password}
    token_result = fetch_json(token_url, data=token_data, method="POST")
    [REDACTED_SECRET]"token")

    if not token:
        raise RuntimeError("Failed to get authentication token")

    return {
        "address": address,
        "password": password,
        "token": token,
        "accountId": account_id,
        "domain": target_domain,
        "createdAt": time.strftime("%Y-%m-%dT%H:%M:%SZ"),
    }


def check_messages(token: str) -> list:
    """Fetch all messages in the inbox."""
    url = "https://api.mail.tm/messages"
    data = fetch_json(url, token=token)

    if "hydra:member" in data:
        return data["hydra:member"]
    return data if isinstance(data, list) else []


def get_message(token: str, msg_id: str) -> dict:
    """Fetch full message content by ID."""
    url = f"https://api.mail.tm/messages/{msg_id}"
    return fetch_json(url, token=token)


def extract_otp(text: str, pattern: Optional[str] = None) -> Optional[str]:
    """Extract OTP code from text."""
    if pattern:
        match = re.search(pattern, text)
        return match.group(1) if match else None

    # Remove HTML tags for cleaner matching
    text_clean = re.sub(r'<[^>]+>', ' ', text)

    # Common OTP patterns: 4-8 digits
    patterns = [
        r'\b(\d{6,8})\b',  # 6-8 digits (most common)
        r'\b(\d{4})\b',     # 4 digits
        r'code[:\s]+["\']?(\d{4,8})',  # "code: 123456"
        r'verification[:\s]+["\']?(\d{4,8})',  # "verification: 123456"
        r'otp[:\s]+["\']?(\d{4,8})',  # "otp: 123456"
        r'["\']?(\d{4,8})["\']?\s+is\s+(?:your|verification)',
    ]
    for p in patterns:
        match = re.search(p, text_clean, re.IGNORECASE)
        if match:
            return match.group(1)
    return None


def extract_urls(text: str) -> list:
    """Extract all URLs from text (handles HTML href attributes and plain URLs)."""
    urls = []

    # First, extract from HTML href attributes
    href_pattern = r'href=["\']([^"\']+)["\']'
    for match in re.finditer(href_pattern, text, re.IGNORECASE):
        url = match.group(1)
        # Clean up common URL issues
        if url and not url.startswith(('http://', 'https://', 'mailto:')):
            # Skip relative URLs and non-http links
            if not url.startswith('#') and not url.startswith('mailto:'):
                continue
        if url and not url.startswith('mailto:'):
            urls.append(url)

    # Also extract plain URLs that might not be in href tags
    url_pattern = r'https?://[^\s<>"\']+[^\s<>"\'.]'
    for match in re.finditer(url_pattern, text):
        url = match.group(0)
        # Remove trailing punctuation that's not part of URL
        url = url.rstrip('.,;:)')
        if url not in urls:
            urls.append(url)

    return urls


def cmd_create(args):
    """Create a new temporary email account."""
    try:
        account = create_account(
            address=args.email,
            domain=args.domain,
            [REDACTED_SECRET]
        )
        save_state(account)

        if args.json:
            print(json.dumps(account, indent=2))
        else:
            print(f"Email: {account['address']}")
            print(f"[REDACTED_SECRET]'password']}")
            print(f"Domain: {account['domain']}")
            print(f"\nAccount saved to {STATE_FILE}")
            print(f"Use 'tempmail_otp.py check' to wait for OTP")

        return 0
    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        return 1


def cmd_check(args):
    """Check for OTP emails."""
    state = load_state()
    if not state:
        print("Error: No account found. Use 'create' command first.", file=sys.stderr)
        return 1

    [REDACTED_SECRET]"token")
    address = state.get("address")

    if not [REDACTED_SECRET]"Error: Invalid account state (no token). Recreate account.", file=sys.stderr)
        return 1

    start_time = time.time()
    seen_ids = set()

    print(f"Monitoring: {address}")
    print(f"Timeout: {args.timeout}s | Poll interval: {args.poll}s")
    print("-" * 50)

    while time.time() - start_time < args.timeout:
        try:
            messages = check_messages(token)
        except Exception as e:
            print(f"Warning: Failed to check messages: {e}")
            time.sleep(args.poll)
            continue

        for msg in messages:
            msg_id = msg.get("id")

            if msg_id in seen_ids:
                continue
            seen_ids.add(msg_id)

            from_addr = msg.get("from", {}).get("address", "")
            subject = msg.get("subject", "")

            # Apply filters
            if args.sender and args.sender.lower() not in from_addr.lower():
                continue
            if args.subject and args.subject.lower() not in subject.lower():
                continue

            print(f"\n📧 New email from: {from_addr}")
            print(f"   Subject: {subject}")

            # Get full message and extract OTP/Links
            try:
                full_msg = get_message(token, msg_id)

                text_content = ""
                html_content = ""
                if "text" in full_msg:
                    text_content = full_msg["text"] if isinstance(full_msg["text"], str) else ""
                if "html" in full_msg:
                    html_val = full_msg["html"]
                    if isinstance(html_val, list):
                        html_content = " ".join(html_val) if html_val else ""
                    elif isinstance(html_val, str):
                        html_content = html_val
                    else:
                        html_content = ""

                full_content = text_content + " " + html_content

                # Extract OTP
                otp = extract_otp(full_content, args.pattern)
                if otp:
                    print(f"\n✅ OTP FOUND: {otp}")

                    # Save OTP to file
                    ensure_state_dir()
                    with open(LAST_OTP_FILE, "w") as f:
                        f.write(otp)
                    print(f"OTP saved to {LAST_OTP_FILE}")
                    print("-" * 50)

                    if args.once:
                        return 0

                # Extract URLs
                urls = extract_urls(html_content or text_content)
                if urls:
                    print(f"\n🔗 Links found ({len(urls)}):")
                    # Filter out common tracking/utility URLs
                    interesting_urls = [
                        u for u in urls
                        if 'unsubscribe' not in u.lower()
                        and 'tracking' not in u.lower()
                        and not u.endswith('.png')
                        and not u.endswith('.jpg')
                        and not u.endswith('.gif')
                    ]
                    for i, url in enumerate(interesting_urls[:10], 1):  # Max 10 links
                        print(f"   {i}. {url}")
                    if len(interesting_urls) > 10:
                        print(f"   ... and {len(interesting_urls) - 10} more")

                    # Save first interesting link
                    if interesting_urls:
                        ensure_state_dir()
                        with open(LAST_LINK_FILE, "w") as f:
                            f.write(interesting_urls[0])
                        print(f"\nFirst link saved to {LAST_LINK_FILE}")

                if not otp and not urls:
                    print("   No OTP or links detected in message")
                    # Show preview of message
                    preview = re.sub(r'<[^>]+>', ' ', full_content)[:300]
                    print(f"   Preview: {preview}...")
                else:
                    print("-" * 50)

            except Exception as e:
                print(f"   Error reading message: {e}")

        # Progress indicator
        elapsed = int(time.time() - start_time)
        remaining = args.timeout - elapsed
        sys.stdout.write(f"\r⏳ Waiting... ({elapsed}s elapsed, {remaining}s remaining) | Messages seen: {len(seen_ids)}")
        sys.stdout.flush()
        time.sleep(args.poll)

    print(f"\n\n⏱️ Timeout waiting for OTP")
    return 1


def cmd_list(_args):
    """List saved account and current messages."""
    state = load_state()
    if not state:
        print("No account found. Use 'create' command first.")
        return 1

    [REDACTED_SECRET]"token")
    address = state.get("address")

    print(f"📧 Email: {address}")
    print(f"🔐 [REDACTED_SECRET]'password')}")
    print(f"🌐 Domain: {state.get('domain')}")
    print(f"📅 Created: {state.get('createdAt')}")

    if token:
        try:
            messages = check_messages(token)
            total = len(messages)
            print(f"\n📬 Messages in inbox: {total}")

            for i, msg in enumerate(messages, 1):
                from_addr = msg.get("from", {}).get("address", "unknown")
                subject = msg.get("subject", "no subject")
                msg_date = msg.get("createdAt", "")[:16]
                msg_id = msg.get("id")

                print(f"   {i}. {msg_date} | {from_addr}")
                print(f"      └─ {subject}")

                # Fetch full message to extract links
                try:
                    full_msg = get_message(token, msg_id)
                    html_val = full_msg.get("html", "")
                    if isinstance(html_val, list):
                        html_content = " ".join(html_val) if html_val else ""
                    else:
                        html_content = html_val or ""
                    text_content = full_msg.get("text", "") or ""
                    urls = extract_urls(html_content or text_content)

                    interesting_urls = [
                        u for u in urls
                        if 'unsubscribe' not in u.lower()
                        and 'tracking' not in u.lower()
                        and not u.endswith('.png')
                        and not u.endswith('.jpg')
                        and not u.endswith('.gif')
                    ]

                    if interesting_urls:
                        for url in interesting_urls[:3]:  # Show up to 3 links
                            print(f"      🔗 {url}")
                        if len(interesting_urls) > 3:
                            print(f"      ... and {len(interesting_urls) - 3} more links")

                except Exception as e:
                    pass  # Skip link extraction on error
        except Exception as e:
            print(f"Error fetching messages: {e}")

    return 0


def cmd_domains(args):
    """List available domains."""
    try:
        domains = get_domains()
        if args.json:
            print(json.dumps({"domains": domains}, indent=2))
        else:
            print("Available domains:")
            for d in domains:
                status = "✓" if d.get("isActive") else "✗"
                private = " [private]" if d.get("isPrivate") else ""
                print(f"  {status} {d.get('domain')}{private}")
        return 0
    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        return 1


def main():
    parser = argparse.ArgumentParser(
        description="Temporary Email OTP Extractor",
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    subparsers = parser.add_subparsers(dest="command", help="Command to run")

    # Create command
    create_parser = subparsers.add_parser("create", help="Create a new temporary email account")
    create_parser.add_argument("-e", "--email", help="Custom full email address")
    create_parser.add_argument("-d", "--domain", help="Specific domain to use")
    create_parser.add_argument("-p", "--password", help="Account password (auto-generated if not specified)")
    create_parser.add_argument("--json", action="store_true", help="Output as JSON")

    # Check command
    check_parser = subparsers.add_parser("check", help="Check for OTP emails")
    check_parser.add_argument("--timeout", type=int, default=300, help="Max seconds to wait (default: 300)")
    check_parser.add_argument("--poll", type=int, default=3, help="Poll interval in seconds (default: 3)")
    check_parser.add_argument("--sender", help="Only accept emails from this sender")
    check_parser.add_argument("--subject", help="Only accept emails with this in subject")
    check_parser.add_argument("--pattern", help="Custom regex pattern for OTP extraction")
    check_parser.add_argument("--once", action="store_true", help="Exit after first OTP found")
    check_parser.add_argument("--json", action="store_true", help="Output messages as JSON")

    # List command
    list_parser = subparsers.add_parser("list", help="List saved account and messages")

    # Domains command
    domains_parser = subparsers.add_parser("domains", help="List available domains")
    domains_parser.add_argument("--json", action="store_true", help="Output as JSON")

    args = parser.parse_args()

    if not args.command:
        parser.print_help()
        return 1

    commands = {
        "create": cmd_create,
        "check": cmd_check,
        "list": cmd_list,
        "domains": cmd_domains,
    }

    return commands[args.command](args)


if __name__ == "__main__":
    sys.exit(main())


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