Skill: biguntroll/tesla-tessie (version 1.0.0)

--- name: tesla description: Control and monitor Tesla vehicles via the Tessie API. Use when you need to check Tesla status (battery, location, charging), control climate (heat/cool), lock/unlock doors, start/stop charging, honk/flash lights, open charge port or trunks, or any other Tesla vehicle command. Requires TESSIE_API_KEY environment variable. --- # Tesla Control via Tessie Control Tesla vehicles using the Tessie API through Python scripts. ## Prerequisites **Python 3 with requests library:** ```bash pip install requests ``` **Set the `TESSIE_API_KEY` environment variable** with your Tessie API key from https://my.tessie.com/settings/api ```bash # Linux/macOS export TESSIE_API_KEY="your-api-key-here" # Windows (PowerShell) $env:TESSIE_API_KEY = "your-api-key-here" # Windows (cmd) set TESSIE_API_KEY=your-api-key-here ``` For persistent storage, add to your shell profile (.bashrc, .zshrc, PowerShell profile, etc.). ## Common Commands All commands use the `scripts/tessie.py` script. Most commands require a VIN (Vehicle Identification Number). ### Get Vehicle List ```bash python scripts/tessie.py vehicles ``` Returns all vehicles associated with your Tessie account with their VINs. ### Check Status ```bash python scripts/tessie.py status --vin <VIN> ``` Returns comprehensive vehicle status including battery, location, climate, charging state, and more. ### Battery Info ```bash python scripts/tessie.py battery --vin <VIN> ``` Returns battery level, range, and charging information. ### Location ```bash python scripts/tessie.py location --vin <VIN> ``` Returns current vehicle location (latitude, longitude, heading). ### Lock & Unlock ```bash python scripts/tessie.py lock --vin <VIN> python scripts/tessie.py unlock --vin <VIN> ``` ### Climate Control ```bash # Start climate python scripts/tessie.py start_climate --vin <VIN> # Stop climate python scripts/tessie.py stop_climate --vin <VIN> # Set temperature (Celsius) python scripts/tessie.py set_temperature --vin <VIN> --value 22 ``` ### Charging ```bash # Start charging python scripts/tessie.py start_charging --vin <VIN> # Stop charging python scripts/tessie.py stop_charging --vin <VIN> # Set charge limit (0-100) python scripts/tessie.py set_charge_limit --vin <VIN> --value 80 # Open/close charge port python scripts/tessie.py open_charge_port --vin <VIN> python scripts/tessie.py close_charge_port --vin <VIN> ``` ### Honk, Flash & Fart ```bash python scripts/tessie.py honk --vin <VIN> python scripts/tessie.py flash --vin <VIN> python scripts/tessie.py fart --vin <VIN> ``` Note: Fart requires firmware 2022.40.25 or newer. ### Trunks ```bash python scripts/tessie.py open_frunk --vin <VIN> python scripts/tessie.py open_trunk --vin <VIN> ``` ### Software Updates ```bash # Schedule update immediately python scripts/tessie.py schedule_update --vin <VIN> # Schedule update in 2 hours (7200 seconds) python scripts/tessie.py schedule_update --vin <VIN> --value 7200 # Cancel scheduled update python scripts/tessie.py cancel_update --vin <VIN> # Check for available updates python scripts/check-updates.py --vin <VIN> ``` The check-updates script returns one of: - `UPDATE_AVAILABLE: Software update X.X.X is ready to install!` - `UPDATE_DOWNLOADING: Downloading update X.X.X (XX%)` - `UPDATE_INSTALLING: Installing update X.X.X (XX%)` - `UPDATE_SCHEDULED: Update X.X.X is scheduled` - `NO_UPDATE` ### Wake Vehicle If the vehicle is asleep, wake it first: ```bash python scripts/tessie.py wake --vin <VIN> ``` ## Automatic Update Notifications To get notified when software updates are available, set up a cron job: ```bash # Check for updates every 6 hours and notify if available cron add \ --schedule "0 */6 * * *" \ --text "Check my Tesla for software updates and notify me if one is available" \ --description "Tesla software update check" ``` When an update is available, you'll get a notification with the version number. ## Workflow 1. First time: Get VIN with `vehicles` action 2. For most commands: Use the VIN to target specific vehicle 3. If vehicle is asleep: Use `wake` first, then retry command 4. Check status with `status`, `battery`, or `location` as needed ## Reference For complete API documentation, see `references/api.md`.

path: references/api.md
content: # Tessie API Reference

## Base URL

```
https://api.tessie.com
```

## Authentication

All requests require a Bearer token in the Authorization header:

```
[REDACTED_SECRET]
```

Get your API key from: https://my.tessie.com/settings/api

## Common Endpoints

### Vehicle Data

- **GET /vehicles** - List all vehicles
- **GET /{vin}/status** - Full vehicle status
- **GET /{vin}/battery** - Battery level and range
- **GET /{vin}/location** - GPS coordinates and heading
- **GET /{vin}/battery_health** - Battery degradation info

### Vehicle Commands

All commands use POST method to `/{vin}/{command}`:

**Climate:**
- `start_climate` - Start HVAC
- `stop_climate` - Stop HVAC
- `set_temperature` - Set temp (requires `{"temperature": <celsius>}` body)
- `start_defrost` - Max defrost mode
- `stop_defrost` - Stop defrost

**Charging:**
- `start_charging` - Begin charging
- `stop_charging` - Stop charging
- `set_charge_limit` - Set limit (requires `{"percent": <0-100>}` body)
- `set_charging_amps` - Set charge current (requires `{"amps": <number>}` body)
- `open_charge_port` - Open charge port
- `close_charge_port` - Close charge port

**Locks & Security:**
- `lock` - Lock doors
- `unlock` - Unlock doors
- `enable_sentry_mode` - Enable sentry
- `disable_sentry_mode` - Disable sentry
- `enable_valet_mode` - Enable valet mode
- `disable_valet_mode` - Disable valet mode

**Alerts:**
- `honk` - Honk horn
- `flash_lights` - Flash lights
- `remote_boombox` - Generate fart sound (requires 2022.40.25+)

**Access:**
- `front_trunk` - Open frunk (front trunk)
- `rear_trunk` - Open trunk
- `vent_windows` - Vent windows
- `close_windows` - Close windows

**Software Updates:**
- `schedule_software_update` - Schedule update (requires `{"in_seconds": <number>}` body, 0 for immediate)
- `cancel_software_update` - Cancel scheduled update

**Other:**
- `wake` - Wake sleeping vehicle
- `trigger_homelink` - Trigger HomeLink

## Response Format

Successful responses return JSON with vehicle state or command confirmation.

Error responses include HTTP status codes:
- 401: Invalid API key
- 404: Vehicle not found
- 408: Vehicle offline/asleep (try `wake` first)
- 429: Rate limit exceeded

## Rate Limits

Tessie enforces rate limits. If you hit limits, responses will include retry-after headers.

## Full Documentation

https://developer.tessie.com/reference


path: scripts/check-updates.py
content: #!/usr/bin/env python3
"""Check for Tesla software updates and notify if available"""

import argparse
import os
import sys
import requests


def get_api_key():
    """Get API key from environment"""
    [REDACTED_SECRET]'TESSIE_API_KEY')
    if not [REDACTED_SECRET]"Error: TESSIE_API_KEY environment variable not set", file=sys.stderr)
        sys.exit(1)
    return api_key


def main():
    parser = argparse.ArgumentParser(description='Check Tesla software updates')
    parser.add_argument('--vin', help='Vehicle VIN (optional, uses first vehicle if not specified)')
    
    args = parser.parse_args()
    [REDACTED_SECRET])
    
    headers = {'Authorization': f'Bearer {api_key}'}
    
    try:
        # Get vehicle data
        response = requests.get('https://api.tessie.com/vehicles', headers=headers)
        response.raise_for_status()
        data = response.json()
        
        if args.vin:
            # Find specific vehicle
            vehicle = None
            for v in data.get('results', []):
                if v.get('vin') == args.vin:
                    vehicle = v
                    break
            
            if not vehicle:
                print(f"Error: Vehicle with VIN {args.vin} not found", file=sys.stderr)
                sys.exit(1)
        else:
            # Use first vehicle if no VIN specified
            results = data.get('results', [])
            if not results:
                print("Error: No vehicles found in account", file=sys.stderr)
                sys.exit(1)
            vehicle = results[0]
        
        # Check software update status
        update = vehicle.get('last_state', {}).get('vehicle_state', {}).get('software_update')
        
        if update and update.get('status'):
            version = update.get('version', 'unknown')
            status = update.get('status')
            
            if status == 'available':
                print(f"UPDATE_AVAILABLE: Software update {version} is ready to install!")
            elif status == 'downloading':
                download_perc = update.get('download_perc', 0)
                print(f"UPDATE_DOWNLOADING: Downloading update {version} ({download_perc}%)")
            elif status == 'installing':
                install_perc = update.get('install_perc', 0)
                print(f"UPDATE_INSTALLING: Installing update {version} ({install_perc}%)")
            elif status == 'scheduled':
                print(f"UPDATE_SCHEDULED: Update {version} is scheduled")
            else:
                print("NO_UPDATE")
        else:
            print("NO_UPDATE")
    
    except requests.exceptions.RequestException as e:
        print(f"Error: Failed to check for updates: {e}", file=sys.stderr)
        sys.exit(1)
    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)


if __name__ == '__main__':
    main()


path: scripts/tessie.py
content: #!/usr/bin/env python3
"""Tessie API interaction script for Tesla vehicle control"""

import argparse
import json
import os
import sys
import requests


def get_api_key():
    """Get API key from environment"""
    [REDACTED_SECRET]'TESSIE_API_KEY')
    if not [REDACTED_SECRET]"Error: TESSIE_API_KEY environment variable not set", file=sys.stderr)
        sys.exit(1)
    return api_key


def make_request(method, url, headers, body=None):
    """Make HTTP request and return JSON response"""
    try:
        if method == 'GET':
            response = requests.get(url, headers=headers)
        elif method == 'POST':
            if body:
                response = requests.post(url, headers=headers, json=body)
            else:
                response = requests.post(url, headers=headers)
        else:
            raise ValueError(f"Unsupported HTTP method: {method}")
        
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)


def get_vehicles(api_key):
    """Get list of vehicles"""
    headers = {'Authorization': f'Bearer {api_key}'}
    return make_request('GET', 'https://api.tessie.com/vehicles', headers)


def get_status(api_key, vin):
    """Get vehicle status"""
    headers = {'Authorization': f'Bearer {api_key}'}
    return make_request('GET', f'https://api.tessie.com/{vin}/status', headers)


def get_battery(api_key, vin):
    """Get battery info"""
    headers = {'Authorization': f'Bearer {api_key}'}
    return make_request('GET', f'https://api.tessie.com/{vin}/battery', headers)


def get_location(api_key, vin):
    """Get vehicle location"""
    headers = {'Authorization': f'Bearer {api_key}'}
    return make_request('GET', f'https://api.tessie.com/{vin}/location', headers)


def invoke_command(api_key, vin, command, body=None):
    """Invoke a vehicle command"""
    headers = {
        'Authorization': f'Bearer {api_key}',
        'Content-Type': 'application/json'
    }
    url = f'https://api.tessie.com/{vin}/{command}'
    return make_request('POST', url, headers, body)


def main():
    parser = argparse.ArgumentParser(description='Control Tesla via Tessie API')
    parser.add_argument('action', help='Action to perform')
    parser.add_argument('--vin', help='Vehicle VIN')
    parser.add_argument('--value', help='Value parameter for certain actions')
    
    args = parser.parse_args()
    [REDACTED_SECRET])
    
    action = args.action.lower()
    
    try:
        if action == 'vehicles':
            result = get_vehicles(api_key)
        elif action == 'status':
            if not args.vin:
                print("Error: --vin required for status", file=sys.stderr)
                sys.exit(1)
            result = get_status(api_key, args.vin)
        elif action == 'battery':
            if not args.vin:
                print("Error: --vin required for battery", file=sys.stderr)
                sys.exit(1)
            result = get_battery(api_key, args.vin)
        elif action == 'location':
            if not args.vin:
                print("Error: --vin required for location", file=sys.stderr)
                sys.exit(1)
            result = get_location(api_key, args.vin)
        elif action == 'wake':
            if not args.vin:
                print("Error: --vin required", file=sys.stderr)
                sys.exit(1)
            result = invoke_command(api_key, args.vin, 'wake')
        elif action == 'lock':
            if not args.vin:
                print("Error: --vin required", file=sys.stderr)
                sys.exit(1)
            result = invoke_command(api_key, args.vin, 'lock')
        elif action == 'unlock':
            if not args.vin:
                print("Error: --vin required", file=sys.stderr)
                sys.exit(1)
            result = invoke_command(api_key, args.vin, 'unlock')
        elif action == 'honk':
            if not args.vin:
                print("Error: --vin required", file=sys.stderr)
                sys.exit(1)
            result = invoke_command(api_key, args.vin, 'honk')
        elif action == 'flash':
            if not args.vin:
                print("Error: --vin required", file=sys.stderr)
                sys.exit(1)
            result = invoke_command(api_key, args.vin, 'flash_lights')
        elif action == 'start_climate':
            if not args.vin:
                print("Error: --vin required", file=sys.stderr)
                sys.exit(1)
            result = invoke_command(api_key, args.vin, 'start_climate')
        elif action == 'stop_climate':
            if not args.vin:
                print("Error: --vin required", file=sys.stderr)
                sys.exit(1)
            result = invoke_command(api_key, args.vin, 'stop_climate')
        elif action == 'set_temperature':
            if not args.vin:
                print("Error: --vin required", file=sys.stderr)
                sys.exit(1)
            if not args.value:
                print("Error: --value required (temperature in Celsius)", file=sys.stderr)
                sys.exit(1)
            result = invoke_command(api_key, args.vin, 'set_temperature', 
                                   {'temperature': int(args.value)})
        elif action == 'start_charging':
            if not args.vin:
                print("Error: --vin required", file=sys.stderr)
                sys.exit(1)
            result = invoke_command(api_key, args.vin, 'start_charging')
        elif action == 'stop_charging':
            if not args.vin:
                print("Error: --vin required", file=sys.stderr)
                sys.exit(1)
            result = invoke_command(api_key, args.vin, 'stop_charging')
        elif action == 'set_charge_limit':
            if not args.vin:
                print("Error: --vin required", file=sys.stderr)
                sys.exit(1)
            if not args.value:
                print("Error: --value required (charge limit 0-100)", file=sys.stderr)
                sys.exit(1)
            result = invoke_command(api_key, args.vin, 'set_charge_limit', 
                                   {'percent': int(args.value)})
        elif action == 'open_charge_port':
            if not args.vin:
                print("Error: --vin required", file=sys.stderr)
                sys.exit(1)
            result = invoke_command(api_key, args.vin, 'open_charge_port')
        elif action == 'close_charge_port':
            if not args.vin:
                print("Error: --vin required", file=sys.stderr)
                sys.exit(1)
            result = invoke_command(api_key, args.vin, 'close_charge_port')
        elif action == 'open_frunk':
            if not args.vin:
                print("Error: --vin required", file=sys.stderr)
                sys.exit(1)
            result = invoke_command(api_key, args.vin, 'front_trunk')
        elif action == 'open_trunk':
            if not args.vin:
                print("Error: --vin required", file=sys.stderr)
                sys.exit(1)
            result = invoke_command(api_key, args.vin, 'rear_trunk')
        elif action == 'fart':
            if not args.vin:
                print("Error: --vin required", file=sys.stderr)
                sys.exit(1)
            result = invoke_command(api_key, args.vin, 'command/remote_boombox')
        elif action == 'schedule_update':
            if not args.vin:
                print("Error: --vin required", file=sys.stderr)
                sys.exit(1)
            seconds = int(args.value) if args.value else 0
            result = invoke_command(api_key, args.vin, 'command/schedule_software_update',
                                   {'in_seconds': seconds})
        elif action == 'cancel_update':
            if not args.vin:
                print("Error: --vin required", file=sys.stderr)
                sys.exit(1)
            result = invoke_command(api_key, args.vin, 'command/cancel_software_update')
        else:
            print(f"Error: Unknown action: {action}", file=sys.stderr)
            print("Available actions: vehicles, status, battery, location, wake, lock, unlock, "
                  "honk, flash, start_climate, stop_climate, set_temperature, start_charging, "
                  "stop_charging, set_charge_limit, open_charge_port, close_charge_port, "
                  "open_frunk, open_trunk, fart, schedule_update, cancel_update", file=sys.stderr)
            sys.exit(1)
        
        print(json.dumps(result, indent=2))
    
    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)


if __name__ == '__main__':
    main()


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