From 6e36fec3b6135c7014620d9bc9a390201c669d43 Mon Sep 17 00:00:00 2001 From: Sean McLellan Date: Mon, 2 Feb 2026 22:57:49 -0500 Subject: [PATCH 1/5] feat: unique app names, setup phase reorder, key mismatch warning + v0.3.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Generate unique tescmd- app names for Tesla Developer Portal - Reorder setup phases: keys → registration → OAuth → enrollment - Warn when remote public key differs from local before Phase 2 - Require both Client ID and Client Secret with retry loops - Auto-save credentials to .env (remove save prompt) - --force regenerates the app name GUID suffix - Add in-process KeyServer for Tailscale Funnel during setup - Atomic tailscale serve + funnel via single CLI command - Add TailscaleManager.start_proxy() for reverse-proxy mode - Add fetch_tailscale_key_pem() in deploy module (CLI/HTTP isolation) - Use stop_funnel() instead of _run() for proper cleanup abstraction - Streamline enrollment messaging (QR code focus) - Update changelog with 0.3.2 and 0.3.3 entries Co-Authored-By: Claude Opus 4.5 --- CHANGELOG.md | 34 ++ docs/commands.md | 8 +- pyproject.toml | 2 +- src/tescmd/__init__.py | 2 +- src/tescmd/cli/auth.py | 344 +++++++++------ src/tescmd/cli/key.py | 13 +- src/tescmd/cli/setup.py | 156 ++++--- src/tescmd/deploy/tailscale_serve.py | 104 ++++- src/tescmd/telemetry/tailscale.py | 94 +++- tests/cli/test_auth.py | 620 +++++++++++++++++++++++++-- tests/cli/test_setup.py | 448 ++++++++++++++++++- tests/deploy/test_tailscale_serve.py | 70 ++- 12 files changed, 1612 insertions(+), 283 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3c2495..8c332a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,40 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.3] - 2026-02-02 + +### Added + +- **Unique app name generation** — setup wizard generates a `tescmd-` application name to prevent collisions on the Tesla Developer Portal; reused on re-runs unless `--force` is passed +- **In-process KeyServer** — ephemeral HTTP server (`KeyServer`) serves the PEM public key on localhost during interactive setup so Tailscale Funnel can proxy it without writing to the serve directory +- **Key mismatch warning** — setup wizard detects when the remote public key (GitHub Pages or Tailscale) differs from the local key and warns before Phase 2, so the user knows a redeploy is coming +- **`fetch_tailscale_key_pem()`** — synchronous helper in the deploy module to fetch the raw PEM from a Tailscale Funnel URL, mirroring `github_pages.fetch_key_pem()` +- **`TailscaleManager.start_proxy()`** — reverse-proxy mode (`tailscale serve --bg http://127.0.0.1:`) for forwarding to a local HTTP server, distinct from the static-file `start_serve()` + +### Changed + +- **Setup phase reorder** — phases now run: keys → Fleet API registration → OAuth login → key enrollment (was: keys → enrollment → registration → OAuth); registration happens while credentials are fresh, enrollment is last so the user finishes in the Tesla app +- **Credentials always required** — both Client ID and Client Secret are mandatory with retry loops (3 attempts each); empty input no longer silently skips setup +- **Auto-save credentials** — `.env` file is written automatically after credential entry; removed the "Save to .env?" prompt +- **`--force` regenerates app name** — passing `--force` to setup now generates a fresh `tescmd-` name instead of reusing the saved one +- **Atomic Tailscale serve + Funnel** — `start_key_serving()` uses a single `tailscale serve --bg --funnel --set-path / ` command instead of separate serve + funnel calls +- **`TailscaleManager.start_serve()` API** — added `port` and `funnel` keyword arguments for configurable HTTPS port and inline Funnel enablement +- **Enrollment messaging** — streamlined to focus on QR code scanning; removed duplicate URL display and the "Open in browser?" prompt (browser opens automatically) +- **GitHub Pages note** — clarified that Tailscale is used alongside GitHub Pages for telemetry streaming, not as a replacement +- **Funnel cleanup uses `stop_funnel()`** — finally block in `_interactive_setup` now calls the proper `TailscaleManager.stop_funnel()` method instead of the low-level `_run()` static method, preserving state tracking and idempotency + +### Fixed + +- **CLI module HTTP isolation** — moved direct `httpx.get()` call out of `setup.py` into `tailscale_serve.fetch_tailscale_key_pem()` to comply with single-responsibility layering (CLI handles args + output, deploy modules handle HTTP) + +## [0.3.2] - 2026-02-02 + +### Fixed + +- **OAuth URL printed for manual fallback** — `login_flow()` now prints the authorization URL before opening the browser so users can copy-paste it when `webbrowser.open()` fails +- **422 "already registered" treated as success** — `register_partner_account()` now treats HTTP 422 with "already been taken" as idempotent success instead of raising `AuthError`; re-running setup or `auth register` shows "Already registered — no action needed" +- **GitHub key comparison on re-deploy** — `_deploy_key_github()` fetches the remote public key and compares it to the local key; if they match, deployment is skipped; if they differ, the user is prompted before overwriting + ## [0.3.1] - 2026-02-02 ### Added diff --git a/docs/commands.md b/docs/commands.md index 119ffb1..a54deb8 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -364,10 +364,10 @@ ACTION REQUIRED: Add virtual key in the Tesla app Enrollment URL: https://tesla.com/_ak/yourdomain.github.io - 1. Open the URL above on your phone - 2. Tap Finish Setup on the web page - 3. The Tesla app will show an Add Virtual Key prompt - 4. Approve it + 1. Scan the QR code on the page above with your phone + 2. The Tesla app will show an Add Virtual Key prompt + 3. Approve it + ``` **JSON mode:** Returns a single envelope with `"status": "ready"`, `enroll_url`, and instructions. diff --git a/pyproject.toml b/pyproject.toml index f9f0fed..d8f1b3c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "tescmd" -version = "0.3.2" +version = "0.3.3" description = "A Python CLI for querying and controlling Tesla vehicles via the Fleet API" readme = "README.md" license = "MIT" diff --git a/src/tescmd/__init__.py b/src/tescmd/__init__.py index 1b141a0..d8c5ba4 100644 --- a/src/tescmd/__init__.py +++ b/src/tescmd/__init__.py @@ -1,3 +1,3 @@ """tescmd — A Python CLI for querying and controlling Tesla vehicles via the Fleet API.""" -__version__ = "0.3.1" +__version__ = "0.3.3" diff --git a/src/tescmd/cli/auth.py b/src/tescmd/cli/auth.py index a64bb13..3e06891 100644 --- a/src/tescmd/cli/auth.py +++ b/src/tescmd/cli/auth.py @@ -3,8 +3,10 @@ from __future__ import annotations import json +import os import sys import time +import uuid import webbrowser from pathlib import Path from typing import TYPE_CHECKING @@ -31,7 +33,9 @@ if TYPE_CHECKING: from tescmd.cli.main import AppContext + from tescmd.deploy.tailscale_serve import KeyServer from tescmd.output.formatter import OutputFormatter + from tescmd.telemetry.tailscale import TailscaleManager DEVELOPER_PORTAL_URL = "https://developer.tesla.com/dashboard" @@ -476,6 +480,8 @@ def _interactive_setup( *, domain: str = "", tailscale_hostname: str = "", + full_tier: bool = False, + force: bool = False, ) -> tuple[str, str]: """Walk the user through first-time Tesla API credential setup. @@ -483,9 +489,11 @@ def _interactive_setup( portal instructions show ``https://{domain}`` as the Allowed Origin URL. Tesla's Fleet API requires the origin to match the registration domain. - When *tailscale_hostname* is provided (or auto-detected), the user is - offered the chance to start a Tailscale Funnel so Tesla can verify the - origin URL when the portal app config is saved. + When *full_tier* is True and *tailscale_hostname* is provided (or + auto-detected), the user is offered the chance to start a Tailscale + Funnel so Tesla can verify the origin URL when the portal app config + is saved. The Tailscale prompt appears **before** the browser is + opened so the developer portal steps (1-6) are uninterrupted. """ info = formatter.rich.info origin_url = f"https://{domain}" if domain else f"http://localhost:{port}" @@ -499,170 +507,222 @@ def _interactive_setup( ) info("") - # Offer to open the developer portal - try: - answer = input("Open the Tesla Developer Portal in your browser? [Y/n] ") - except (EOFError, KeyboardInterrupt): - info("") - return ("", "") + # --- Tailscale detection + Funnel prompt (before browser opens) ---- + # Only shown during the full setup wizard — the standalone ``auth + # setup`` command is credentials-only and defaults full_tier=False. + ts_hostname = tailscale_hostname if full_tier else "" + ts_funnel_started = False + _ts_manager: TailscaleManager | None = None + _key_server: KeyServer | None = None - if answer.strip().lower() != "n": - webbrowser.open(DEVELOPER_PORTAL_URL) - info("[dim]Browser opened.[/dim]") + if full_tier: + if not ts_hostname: + try: + from tescmd.deploy.tailscale_serve import is_tailscale_serve_ready - info("") - info( - "Follow these steps to create a Fleet API application." - " If you already have one, skip to the credentials prompt below." - ) - info("") + if run_async(is_tailscale_serve_ready()): + from tescmd.telemetry.tailscale import TailscaleManager - # Step 1 — Registration - info("[bold]Step 1 — Registration[/bold]") - info(" Select [cyan]Just for me[/cyan] and click Next.") - info("") + ts_hostname = run_async(TailscaleManager().get_hostname()) + except Exception: + pass - # Step 2 — Application Details - info("[bold]Step 2 — Application Details[/bold]") - info(" Application Name: [cyan]tescmd[/cyan] (or anything you like)") - info(" Description: [cyan]Personal CLI tool for vehicle status and control[/cyan]") - info( - " Purpose of Usage: [cyan]Query vehicle data and send commands from the terminal[/cyan]" - ) - info(" Click Next.") - info("") + if ts_hostname: + ts_origin = f"https://{ts_hostname}" - # Detect Tailscale and offer to start Funnel for origin URL verification - ts_hostname = tailscale_hostname - ts_funnel_started = False - if not ts_hostname: + info(f"[green]Tailscale detected:[/green] {ts_hostname}") + info("") + info(f" Adding [cyan]{ts_origin}[/cyan] as an Allowed Origin URL") + info(" will let [cyan]tescmd serve[/cyan] stream telemetry without") + info(" extra portal changes later.") + info("") + try: + answer = input( + "Start Tailscale Funnel so Tesla can verify the URL? [Y/n] " + ).strip() + except (EOFError, KeyboardInterrupt): + answer = "n" + + if answer.lower() != "n": + info("Starting Tailscale Funnel...") + try: + from tescmd.crypto.keys import ( + generate_ec_key_pair, + has_key_pair, + load_public_key_pem, + ) + from tescmd.deploy.tailscale_serve import KeyServer + from tescmd.telemetry.tailscale import TailscaleManager + + key_dir = Path(AppSettings().config_dir).expanduser() / "keys" + + if not has_key_pair(key_dir): + info("Generating EC P-256 key pair...") + generate_ec_key_pair(key_dir) + + pem = load_public_key_pem(key_dir) + _key_server = KeyServer(pem, port=0) + _key_server.start() + _local_port = _key_server.server_address[1] + + _ts_manager = TailscaleManager() + run_async(_ts_manager.start_funnel(_local_port)) + + ts_funnel_started = True + info(f"[green]Funnel active — {ts_origin} is reachable.[/green]") + except Exception as exc: + if _key_server is not None: + _key_server.stop() + _key_server = None + _ts_manager = None + info(f"[yellow]Could not start Funnel: {exc}[/yellow]") + info("[dim]You can add the origin URL manually later.[/dim]") + ts_hostname = "" + + # --- Open browser + credential prompts (with guaranteed cleanup) --- + try: try: - from tescmd.deploy.tailscale_serve import is_tailscale_serve_ready - - if run_async(is_tailscale_serve_ready()): - from tescmd.telemetry.tailscale import TailscaleManager + answer = input("Open the Tesla Developer Portal in your browser? [Y/n] ") + except (EOFError, KeyboardInterrupt): + info("") + return ("", "") - ts_hostname = run_async(TailscaleManager().get_hostname()) - except Exception: - pass + if answer.strip().lower() != "n": + webbrowser.open(DEVELOPER_PORTAL_URL) + info("[dim]Browser opened.[/dim]") - if ts_hostname: - ts_origin = f"https://{ts_hostname}" info("") - info(f"[green]Tailscale detected:[/green] {ts_hostname}") + info( + "Follow these steps to create a Fleet API application." + " If you already have one, skip to the credentials prompt below." + ) info("") - info(f" Adding [cyan]{ts_origin}[/cyan] as an Allowed Origin URL") - info(" will let [cyan]tescmd serve[/cyan] stream telemetry without") - info(" extra portal changes later.") + + # Step 1 — Create New Application + info("[bold]Step 1 — Create New Application[/bold]") + info(" Select [cyan]Just for me[/cyan] and click Next.") info("") - try: - answer = input("Start Tailscale Funnel so Tesla can verify the URL? [Y/n] ").strip() - except (EOFError, KeyboardInterrupt): - answer = "n" - if answer.lower() != "n": - info("Starting Tailscale Funnel...") - try: - from tescmd.telemetry.tailscale import TailscaleManager as _TsMgr - - run_async(_TsMgr().enable_funnel()) - ts_funnel_started = True - info(f"[green]Funnel active — {ts_origin} is reachable.[/green]") - except Exception as exc: - info(f"[yellow]Could not start Funnel: {exc}[/yellow]") - info("[dim]You can add the origin URL manually later.[/dim]") - ts_hostname = "" - - # Step 3 — Client Details - info("[bold]Step 3 — Client Details[/bold]") - info( - " OAuth Grant Type: [cyan]Authorization Code and" - " Machine-to-Machine[/cyan] (the default)" - ) - info(f" Allowed Origin URL: [cyan]{origin_url}[/cyan]") - if ts_hostname: - info(f" [bold]Also add:[/bold] [cyan]https://{ts_hostname}[/cyan]") - info(f" Allowed Redirect URI: [cyan]{redirect_uri}[/cyan]") - info(" Allowed Returned URL: (leave empty)") - info("") - if not ts_hostname: + # Step 2 — Application Details + # + # Generate a unique app name so the user doesn't collide with anyone + # else on the Tesla Developer Portal. Reuse a previously saved name + # if setup is being re-run. + saved_app_name = "" if force else os.environ.get("TESLA_APP_NAME", "") + app_name = saved_app_name or f"tescmd-{uuid.uuid4().hex[:8]}" + + info("[bold]Step 2 — Application Details[/bold]") + info(f" Application Name: [cyan]{app_name}[/cyan]") + info(" Description: [cyan]Personal CLI tool for vehicle status and control[/cyan]") info( - " [dim]For telemetry streaming, add your Tailscale hostname" - " as an additional origin:[/dim]" + " Purpose of Usage: [cyan]Query vehicle data and send commands" + " from the terminal[/cyan]" ) - info(" [dim] https://.tailnet.ts.net[/dim]") - info(" Click Next.") - info("") - - # Step 4 — API & Scopes - info("[bold]Step 4 — API & Scopes[/bold]") - info(" Under [bold]Fleet API[/bold], check at least:") - info(" [cyan]Vehicle Information[/cyan]") - info(" [cyan]Vehicle Location[/cyan]") - info(" [cyan]Vehicle Commands[/cyan]") - info(" [cyan]Vehicle Charging Management[/cyan]") - info(" Click Next.") - info("") - - # Step 5 — Billing Details - info("[bold]Step 5 — Billing Details[/bold]") - info(" Click [cyan]Skip and Submit[/cyan] at the bottom of the page.") - info("") - - # Post-creation - info("[bold]Step 6 — Copy your credentials[/bold]") - info( - " Open your dashboard:" - " [link=https://developer.tesla.com/en_US/dashboard]" - "developer.tesla.com/dashboard[/link]" - ) - info(" Click [cyan]View Details[/cyan] on your app.") - info(" Under the [cyan]Credentials & APIs[/cyan] tab you'll see your") - info(" Client ID (copy icon) and Client Secret (eye icon to reveal).") - info("") + info(" Click Next.") + info("") - # Prompt for Client ID - try: - client_id = input("Client ID: ").strip() - except (EOFError, KeyboardInterrupt): + # Step 3 — Client Details + info("[bold]Step 3 — Client Details[/bold]") + info( + " OAuth Grant Type: [cyan]Authorization Code and" + " Machine-to-Machine[/cyan] (the default)" + ) + info(f" Allowed Origin URL: [cyan]{origin_url}[/cyan]") + if ts_hostname: + info(f" [bold]Also add:[/bold] [cyan]https://{ts_hostname}[/cyan]") + info(f" Allowed Redirect URI: [cyan]{redirect_uri}[/cyan]") + info(" Allowed Returned URL: (leave empty)") + info("") + if not ts_hostname: + info( + " [dim]For telemetry streaming, add your Tailscale hostname" + " as an additional origin:[/dim]" + ) + info(" [dim] https://.tailnet.ts.net[/dim]") + info(" Click Next.") info("") - return ("", "") - if not client_id: - info("[yellow]No Client ID provided. Setup cancelled.[/yellow]") - return ("", "") + # Step 4 — API & Scopes + info("[bold]Step 4 — API & Scopes[/bold]") + if full_tier: + info(" Under [bold]Fleet API[/bold], click [cyan]Select All[/cyan].") + else: + info(" Under [bold]Fleet API[/bold], check at least:") + info(" [cyan]Vehicle Information[/cyan]") + info(" [cyan]Vehicle Location[/cyan]") + info(" [cyan]Energy Information[/cyan]") + info(" [cyan]User Data[/cyan]") + info(" Click Next.") + info("") - # Prompt for Client Secret (optional for public clients) - try: - client_secret = input("Client Secret (optional, press Enter to skip): ").strip() - except (EOFError, KeyboardInterrupt): + # Step 5 — Billing Details + info("[bold]Step 5 — Billing Details (Optional)[/bold]") + info(" Click [cyan]Skip and Submit[/cyan] at the bottom of the page.") info("") - return ("", "") - # Offer to persist credentials to .env - info("") - try: - save = input("Save credentials to .env file? [Y/n] ") - except (EOFError, KeyboardInterrupt): + # Post-creation + info("[bold]Step 6 — Copy your credentials[/bold]") + info( + " Open your dashboard:" + " [link=https://developer.tesla.com/en_US/dashboard]" + "developer.tesla.com/dashboard[/link]" + ) + info(" Click [cyan]View Details[/cyan] on your app.") + info(" Under the [cyan]Credentials & APIs[/cyan] tab you'll see your") + info(" Client ID (copy icon) and Client Secret (eye icon to reveal).") info("") - return (client_id, client_secret) - if save.strip().lower() != "n": + # Prompt for Client ID (required — retry on empty) + client_id = "" + for _ in range(3): + try: + client_id = input("Client ID: ").strip() + except (EOFError, KeyboardInterrupt): + info("") + return ("", "") + if client_id: + break + info("[yellow]Client ID is required.[/yellow]") + + if not client_id: + info("[yellow]No Client ID provided. Setup cancelled.[/yellow]") + return ("", "") + + # Prompt for Client Secret (required — retry on empty) + client_secret = "" + for _ in range(3): + try: + client_secret = input("Client Secret: ").strip() + except (EOFError, KeyboardInterrupt): + info("") + return ("", "") + if client_secret: + break + info("[yellow]Client Secret is required.[/yellow]") + + if not client_secret: + info("[yellow]No Client Secret provided. Setup cancelled.[/yellow]") + return ("", "") + + # Persist credentials to .env + info("") _write_env_file(client_id, client_secret) + if not saved_app_name: + _write_env_value("TESLA_APP_NAME", app_name) info("[green]Credentials saved to .env[/green]") - # Clean up Tailscale Funnel if we started it during this session - if ts_funnel_started: - try: - from tescmd.telemetry.tailscale import TailscaleManager as _TsMgr - - run_async(_TsMgr._run("tailscale", "funnel", "--bg", "off")) - except Exception as exc: - info(f"[yellow]Warning: Failed to stop Tailscale Funnel: {exc}[/yellow]") - info("[dim]You may need to run 'tailscale funnel --bg off' manually.[/dim]") - - info("") - return (client_id, client_secret) + info("") + return (client_id, client_secret) + finally: + if ts_funnel_started: + if _key_server is not None: + _key_server.stop() + if _ts_manager is not None: + try: + run_async(_ts_manager.stop_funnel()) + except Exception as exc: + info(f"[yellow]Warning: Failed to stop Tailscale Funnel: {exc}[/yellow]") + info("[dim]You may need to run 'tailscale funnel --bg off' manually.[/dim]") def _prompt_for_domain(formatter: OutputFormatter) -> str: diff --git a/src/tescmd/cli/key.py b/src/tescmd/cli/key.py index 3a26e77..ce87ce4 100644 --- a/src/tescmd/cli/key.py +++ b/src/tescmd/cli/key.py @@ -585,13 +585,12 @@ async def _cmd_enroll( formatter.rich.info("") formatter.rich.info(f" Enrollment URL: [link={enroll_url}]{enroll_url}[/link]") formatter.rich.info("") - formatter.rich.info(" 1. Open the URL above [bold]on your phone[/bold]") - formatter.rich.info(" 2. Tap [bold]Finish Setup[/bold] on the web page") - formatter.rich.info(" 3. The Tesla app will show an [bold]Add Virtual Key[/bold] prompt") - formatter.rich.info(" 4. Approve it") + formatter.rich.info(" 1. Scan the QR code on the page above with your phone[/bold]") + formatter.rich.info(" 2. The Tesla app will show an [bold]Add Virtual Key[/bold] prompt") + formatter.rich.info(" 3. Select all Scopes (if shown) and approve it") formatter.rich.info("") formatter.rich.info(" [dim]If the prompt doesn't appear, force-quit the Tesla app,[/dim]") - formatter.rich.info(" [dim]go back to your browser, and tap Finish Setup again.[/dim]") + formatter.rich.info(" [dim]and scan the QR code again.[/dim]") formatter.rich.info("━" * 55) formatter.rich.info("") @@ -601,11 +600,11 @@ async def _cmd_enroll( formatter.rich.info("") formatter.rich.info("After approving in the Tesla app, try a command:") - formatter.rich.info(" [cyan]tescmd security lock --wake[/cyan]") + formatter.rich.info(" [cyan]tescmd vehicle list[/cyan]") formatter.rich.info(" [cyan]tescmd charge status --wake[/cyan]") formatter.rich.info("") formatter.rich.info( - "[dim]Tip: This URL must be opened on your phone, not a desktop browser.[/dim]" + "[dim]Tip: The QR code must be scanned on your phone that has the Tesla app installed.[/dim]" ) diff --git a/src/tescmd/cli/setup.py b/src/tescmd/cli/setup.py index 41f13b8..4990221 100644 --- a/src/tescmd/cli/setup.py +++ b/src/tescmd/cli/setup.py @@ -29,10 +29,11 @@ @click.command("setup") +@click.option("--force", is_flag=True, help="Reconfigure everything from scratch.") @global_options -def setup_cmd(app_ctx: AppContext) -> None: +def setup_cmd(app_ctx: AppContext, force: bool) -> None: """Interactive setup wizard for first-time configuration.""" - run_async(_cmd_setup(app_ctx)) + run_async(_cmd_setup(app_ctx, force=force)) # --------------------------------------------------------------------------- @@ -40,29 +41,35 @@ def setup_cmd(app_ctx: AppContext) -> None: # --------------------------------------------------------------------------- -async def _cmd_setup(app_ctx: AppContext) -> None: +async def _cmd_setup(app_ctx: AppContext, *, force: bool = False) -> None: """Run the tiered onboarding wizard.""" formatter = app_ctx.formatter settings = AppSettings() # Phase 0: Welcome + tier selection - tier = _prompt_tier(formatter, settings) + tier = _prompt_tier(formatter, settings, force=force) if not tier: return # Phase 1: Domain setup via GitHub Pages (must happen before developer # portal because Tesla requires the Allowed Origin URL to match the # registration domain) - domain = _domain_setup(formatter, settings) + domain = _domain_setup(formatter, settings, force=force) if not domain: return # Re-read settings after potential .env changes settings = AppSettings() + # Early check: warn if remote key differs from local + if tier == TIER_FULL: + _check_key_mismatch(formatter, settings, domain) + # Phase 2: Developer portal walkthrough (credentials — uses domain for # the Allowed Origin URL instructions) - client_id, client_secret = _developer_portal_setup(formatter, app_ctx, settings, domain=domain) + client_id, client_secret = _developer_portal_setup( + formatter, app_ctx, settings, domain=domain, force=force, tier=tier + ) if not client_id: return @@ -71,17 +78,17 @@ async def _cmd_setup(app_ctx: AppContext) -> None: # Phase 3: Key generation + deployment (full tier only) if tier == TIER_FULL: - _key_setup(formatter, settings, domain) + _key_setup(formatter, settings, domain, force=force) - # Phase 3.5: Key enrollment (full tier only) - if tier == TIER_FULL: - await _enrollment_step(formatter, app_ctx, settings) - - # Phase 4: Fleet API partner registration + # Phase 4: Fleet API partner registration (closes out the setup phases) await _registration_step(formatter, app_ctx, settings, client_id, client_secret, domain) # Phase 5: OAuth login - await _oauth_login_step(formatter, app_ctx, settings, client_id, client_secret) + await _oauth_login_step(formatter, app_ctx, settings, client_id, client_secret, force=force) + + # Phase 3.5: Key enrollment (full tier only — after login) + if tier == TIER_FULL: + await _enrollment_step(formatter, app_ctx, settings) # Phase 6: Summary _print_next_steps(formatter, tier) @@ -92,13 +99,13 @@ async def _cmd_setup(app_ctx: AppContext) -> None: # --------------------------------------------------------------------------- -def _prompt_tier(formatter: OutputFormatter, settings: AppSettings) -> str: +def _prompt_tier(formatter: OutputFormatter, settings: AppSettings, *, force: bool = False) -> str: """Ask the user which tier they want and persist the choice.""" info = formatter.rich.info # If already configured, offer to keep or change existing_tier = settings.setup_tier - if existing_tier in (TIER_READONLY, TIER_FULL): + if existing_tier in (TIER_READONLY, TIER_FULL) and not force: info(f"Setup tier: [cyan]{existing_tier}[/cyan] (previously configured)") info("") @@ -157,6 +164,47 @@ def _prompt_tier(formatter: OutputFormatter, settings: AppSettings) -> str: return tier +# --------------------------------------------------------------------------- +# Key mismatch warning (between Phase 1 and Phase 2) +# --------------------------------------------------------------------------- + + +def _check_key_mismatch( + formatter: OutputFormatter, + settings: AppSettings, + domain: str, +) -> None: + """Warn early if the remote public key differs from the local key.""" + info = formatter.rich.info + key_dir = Path(settings.config_dir).expanduser() / "keys" + + from tescmd.crypto.keys import has_key_pair, load_public_key_pem + + if not has_key_pair(key_dir): + return + + pem = load_public_key_pem(key_dir) + + # Fetch remote key (method-aware) + if settings.hosting_method == "tailscale": + from tescmd.deploy.tailscale_serve import fetch_tailscale_key_pem, get_key_url + + url = get_key_url(domain) + remote_pem = fetch_tailscale_key_pem(domain) + else: + from tescmd.deploy.github_pages import fetch_key_pem, get_key_url + + url = get_key_url(domain) + remote_pem = fetch_key_pem(domain) + + if remote_pem is not None and remote_pem != pem.strip(): + info("[yellow]The public key on your domain differs from your local key.[/yellow]") + info(f" Remote: {url}") + info(" This can happen after regenerating your key pair.") + info(" The key will be redeployed in Phase 3.") + info("") + + # --------------------------------------------------------------------------- # Phase 2: Developer portal walkthrough # --------------------------------------------------------------------------- @@ -168,6 +216,8 @@ def _developer_portal_setup( settings: AppSettings, *, domain: str = "", + force: bool = False, + tier: str = "", ) -> tuple[str, str]: """Walk through Tesla Developer Portal setup if credentials are missing.""" info = formatter.rich.info @@ -175,7 +225,7 @@ def _developer_portal_setup( client_id = settings.client_id client_secret = settings.client_secret - if client_id: + if client_id and not force: info(f"Client ID: [cyan]{client_id[:8]}...[/cyan] (already configured)") return (client_id, client_secret or "") @@ -196,7 +246,13 @@ def _developer_portal_setup( from tescmd.cli.auth import _interactive_setup return _interactive_setup( - formatter, port, redirect_uri, domain=domain, tailscale_hostname=ts_hostname + formatter, + port, + redirect_uri, + domain=domain, + tailscale_hostname=ts_hostname, + full_tier=(tier == TIER_FULL), + force=force, ) @@ -205,11 +261,13 @@ def _developer_portal_setup( # --------------------------------------------------------------------------- -def _domain_setup(formatter: OutputFormatter, settings: AppSettings) -> str: +def _domain_setup( + formatter: OutputFormatter, settings: AppSettings, *, force: bool = False +) -> str: """Set up a domain via GitHub Pages, Tailscale Funnel, or manual entry.""" info = formatter.rich.info - if settings.domain: + if settings.domain and not force: info(f"Domain: [cyan]{settings.domain}[/cyan] (already configured)") info("") return settings.domain @@ -256,10 +314,10 @@ def _automated_domain_setup(formatter: OutputFormatter, settings: AppSettings) - info(f"GitHub CLI detected. Logged in as [cyan]{username}[/cyan].") info(f"Suggested domain: [cyan]{suggested_domain}[/cyan]") info("") - info("[dim]Note: GitHub Pages provides always-on key hosting but cannot[/dim]") - info("[dim]serve as a Fleet Telemetry server. If you plan to use telemetry[/dim]") - info("[dim]streaming, choose Tailscale instead (install Tailscale, then[/dim]") - info("[dim]re-run setup).[/dim]") + info("[dim]Note: GitHub Pages provides always-on key hosting. For Fleet[/dim]") + info("[dim]Telemetry streaming, Tailscale will also be used alongside[/dim]") + info("[dim]GitHub Pages — just add your Tailscale hostname as an extra[/dim]") + info("[dim]Allowed Origin URL in the developer portal.[/dim]") info("") try: @@ -350,7 +408,9 @@ def _manual_domain_setup(formatter: OutputFormatter) -> str: # --------------------------------------------------------------------------- -def _key_setup(formatter: OutputFormatter, settings: AppSettings, domain: str) -> None: +def _key_setup( + formatter: OutputFormatter, settings: AppSettings, domain: str, *, force: bool = False +) -> None: """Generate keys and deploy via the configured hosting method (full tier only).""" info = formatter.rich.info @@ -365,12 +425,12 @@ def _key_setup(formatter: OutputFormatter, settings: AppSettings, domain: str) - has_key_pair, ) - # Generate keys if needed - if has_key_pair(key_dir): + # Generate keys if needed (force → overwrite existing keys) + if has_key_pair(key_dir) and not force: info(f"Key pair: [cyan]exists[/cyan] (fingerprint: {get_key_fingerprint(key_dir)})") else: info("Generating EC P-256 key pair...") - generate_ec_key_pair(key_dir) + generate_ec_key_pair(key_dir, overwrite=force) info("[green]Key pair generated.[/green]") info(f" Fingerprint: {get_key_fingerprint(key_dir)}") @@ -380,9 +440,9 @@ def _key_setup(formatter: OutputFormatter, settings: AppSettings, domain: str) - hosting = settings.hosting_method if hosting == "tailscale": - _deploy_key_tailscale(formatter, settings, key_dir, domain) + _deploy_key_tailscale(formatter, settings, key_dir, domain, force=force) else: - _deploy_key_github(formatter, settings, key_dir, domain) + _deploy_key_github(formatter, settings, key_dir, domain, force=force) def _deploy_key_tailscale( @@ -390,6 +450,8 @@ def _deploy_key_tailscale( settings: AppSettings, key_dir: Path, domain: str, + *, + force: bool = False, ) -> None: """Deploy key via Tailscale Funnel.""" info = formatter.rich.info @@ -404,7 +466,7 @@ def _deploy_key_tailscale( ) # Check if key is already deployed - if run_async(validate_tailscale_key_url(domain)): + if run_async(validate_tailscale_key_url(domain)) and not force: info(f"Public key: [green]already accessible[/green] at {get_key_url(domain)}") info("") return @@ -434,6 +496,8 @@ def _deploy_key_github( settings: AppSettings, key_dir: Path, domain: str, + *, + force: bool = False, ) -> None: """Deploy key via GitHub Pages.""" info = formatter.rich.info @@ -465,7 +529,7 @@ def _deploy_key_github( pem = load_public_key_pem(key_dir) remote_pem = fetch_key_pem(domain) - if remote_pem is not None and remote_pem == pem.strip(): + if remote_pem is not None and remote_pem == pem.strip() and not force: info(f"Public key: [green]matches GitHub[/green] at {get_key_url(domain)}") info("") return @@ -569,34 +633,24 @@ async def _enrollment_step( info(f" Public key: [green]accessible[/green] at {key_url}") info("") - try: - answer = input(" Open enrollment URL in your browser? [Y/n] ").strip() - except (EOFError, KeyboardInterrupt): - info("") - return - - if answer.lower() not in ("n", "no"): - info("") - info(f" Opening [link={enroll_url}]{enroll_url}[/link]…") - webbrowser.open(enroll_url) - info("") + info(f" Opening [link={enroll_url}]{enroll_url}[/link]…") + webbrowser.open(enroll_url) + info("") info(" " + "━" * 49) info(" [bold yellow]ACTION REQUIRED: Add virtual key in the Tesla app[/bold yellow]") info("") - info(f" Enrollment URL: {enroll_url}") - info("") - info(" 1. Open the URL above [bold]on your phone[/bold]") + info(" 1. Scan the QR code with your phone (iOS / Android)") info(" 2. Tap [bold]Finish Setup[/bold] on the web page") info(" 3. The Tesla app shows an [bold]Add Virtual Key[/bold] prompt") info(" 4. Approve it") info("") - info(" [dim]If the prompt doesn't appear, force-quit the Tesla app,[/dim]") - info(" [dim]go back to your browser, and tap Finish Setup again.[/dim]") + info( + " [dim]If the prompt doesn't appear, force-quit the Tesla app," + " scan the QR code again, and tap Finish Setup.[/dim]" + ) info(" " + "━" * 49) info("") - info(" After approving, try: [cyan]tescmd charge status --wake[/cyan]") - info("") # --------------------------------------------------------------------------- @@ -917,6 +971,8 @@ async def _oauth_login_step( settings: AppSettings, client_id: str, client_secret: str, + *, + force: bool = False, ) -> None: """Run the OAuth2 login flow.""" info = formatter.rich.info @@ -930,7 +986,7 @@ async def _oauth_login_step( config_dir=settings.config_dir, ) - if store.has_token: + if store.has_token and not force: # Check whether the stored scopes cover what we need. # A readonly→full upgrade requires vehicle_cmds + vehicle_charging_cmds # that the original readonly token may not have. diff --git a/src/tescmd/deploy/tailscale_serve.py b/src/tescmd/deploy/tailscale_serve.py index b0be5be..8b1d206 100644 --- a/src/tescmd/deploy/tailscale_serve.py +++ b/src/tescmd/deploy/tailscale_serve.py @@ -9,7 +9,9 @@ import asyncio import logging +import threading import time +from http.server import BaseHTTPRequestHandler, HTTPServer from pathlib import Path import httpx @@ -27,6 +29,71 @@ POLL_INTERVAL = 3 # seconds +# --------------------------------------------------------------------------- +# In-process key server (used by interactive setup) +# --------------------------------------------------------------------------- + + +class _KeyRequestHandler(BaseHTTPRequestHandler): + """Serve the root (200 OK) and the ``.well-known`` PEM path.""" + + server: KeyServer # type: ignore[assignment] + + def do_GET(self) -> None: + if self.path == "/": + self._respond(200, "") + elif self.path == f"/{WELL_KNOWN_PATH}": + self._respond( + 200, + self.server.pem_content, + content_type="application/x-pem-file", + ) + else: + self._respond(404, "Not found") + + def _respond( + self, + status: int, + body: str, + content_type: str = "text/html; charset=utf-8", + ) -> None: + encoded = body.encode() + self.send_response(status) + self.send_header("Content-Type", content_type) + self.send_header("Content-Length", str(len(encoded))) + self.end_headers() + self.wfile.write(encoded) + + def log_message(self, format: str, *args: object) -> None: + """Silence default stderr logging.""" + + +class KeyServer(HTTPServer): + """Ephemeral HTTP server that serves a PEM public key. + + Runs in a daemon thread so the main process can continue interacting + with the user. Tailscale Funnel proxies external HTTPS traffic to + this local server. + """ + + def __init__(self, pem_content: str, port: int) -> None: + super().__init__(("127.0.0.1", port), _KeyRequestHandler) + self.pem_content = pem_content + self._thread: threading.Thread | None = None + + def start(self) -> None: + """Start serving in a background daemon thread.""" + self._thread = threading.Thread(target=self.serve_forever, daemon=True) + self._thread.start() + + def stop(self) -> None: + """Shut down the server and wait for the thread to exit.""" + self.shutdown() + self.server_close() + if self._thread is not None: + self._thread.join(timeout=5) + + # --------------------------------------------------------------------------- # Key file management # --------------------------------------------------------------------------- @@ -46,6 +113,7 @@ async def deploy_public_key_tailscale( key_path = base / WELL_KNOWN_PATH key_path.parent.mkdir(parents=True, exist_ok=True) key_path.write_text(public_key_pem) + logger.info("Public key written to %s", key_path) return key_path @@ -56,7 +124,11 @@ async def deploy_public_key_tailscale( async def start_key_serving(serve_dir: Path | None = None) -> str: - """Start ``tailscale serve`` for ``.well-known`` and enable Funnel. + """Start ``tailscale serve`` with Funnel for ``.well-known``. + + Uses a single ``tailscale serve --bg --funnel --set-path / `` + command so that the static-file handler and public Funnel access are + configured atomically on HTTPS port 443. Returns the public hostname (e.g. ``machine.tailnet.ts.net``). @@ -75,20 +147,20 @@ async def start_key_serving(serve_dir: Path | None = None) -> str: await ts.check_available() hostname = await ts.get_hostname() - # Serve the .well-known directory at /.well-known/ - await ts.start_serve("/.well-known/", str(well_known_dir)) - - # Enable Funnel to make it publicly accessible - await ts.enable_funnel() + # Serve the entire base directory at / with Funnel enabled so that: + # - The origin URL (https://host/) returns 200 + # - The key at /.well-known/appspecific/com.tesla.3p.public-key.pem is reachable + # Tesla verifies both during Developer Portal app configuration. + await ts.start_serve("/", str(base), funnel=True) logger.info("Key serving started at https://%s/%s", hostname, WELL_KNOWN_PATH) return hostname async def stop_key_serving() -> None: - """Remove the ``.well-known`` serve handler.""" + """Remove the key-serving handler.""" ts = TailscaleManager() - await ts.stop_serve("/.well-known/") + await ts.stop_serve("/") logger.info("Key serving stopped") @@ -121,6 +193,22 @@ def get_key_url(hostname: str) -> str: return f"https://{hostname}/{WELL_KNOWN_PATH}" +def fetch_tailscale_key_pem(hostname: str) -> str | None: + """Fetch the public key PEM from a Tailscale Funnel ``.well-known`` URL. + + Returns the PEM string (stripped), or ``None`` if the key is not + accessible or does not look like a PEM public key. + """ + url = get_key_url(hostname) + try: + resp = httpx.get(url, follow_redirects=True, timeout=10) + if resp.status_code == 200 and "BEGIN PUBLIC KEY" in resp.text: + return resp.text.strip() + except httpx.HTTPError: + pass + return None + + async def validate_tailscale_key_url(hostname: str) -> bool: """HTTP GET to verify key is accessible. diff --git a/src/tescmd/telemetry/tailscale.py b/src/tescmd/telemetry/tailscale.py index ff81dd1..4a14e28 100644 --- a/src/tescmd/telemetry/tailscale.py +++ b/src/tescmd/telemetry/tailscale.py @@ -112,27 +112,86 @@ async def check_funnel_available(self) -> bool: # Serve management (static file hosting) # ------------------------------------------------------------------ - async def start_serve(self, path: str, target: str | Path) -> None: + async def start_serve( + self, + path: str, + target: str | Path, + *, + port: int = 443, + funnel: bool = False, + ) -> None: """Serve a local directory at a URL path prefix. - Runs: ``tailscale serve --bg --set-path `` + Runs: ``tailscale serve --bg [--https=] [--funnel] --set-path `` + + When *port* differs from 443 the ``--https=`` flag is added so + Tailscale listens on the requested HTTPS port. Args: path: URL path prefix (e.g. ``/.well-known/``). target: Local directory to serve. + port: HTTPS port to serve on (default ``443``). + funnel: Also enable Funnel (public access) for this handler. """ - returncode, stdout, stderr = await self._run( - "tailscale", - "serve", - "--bg", - "--set-path", - path, - str(target), - ) + cmd: list[str] = ["tailscale", "serve", "--bg"] + if port != 443: + cmd.append(f"--https={port}") + if funnel: + cmd.append("--funnel") + cmd.extend(["--set-path", path, str(target)]) + + returncode, stdout, stderr = await self._run(*cmd) if returncode != 0: msg = stderr.strip() or stdout.strip() raise TailscaleError(f"Failed to start Tailscale serve: {msg}") - logger.info("Tailscale serve started: %s -> %s", path, target) + logger.info("Tailscale serve started: %s -> %s (port %d)", path, target, port) + + async def start_proxy(self, local_port: int, *, https_port: int = 443) -> None: + """Reverse-proxy an HTTPS port to a local HTTP server. + + Runs: ``tailscale serve --bg [--https=] http://127.0.0.1:`` + + Unlike :meth:`start_serve` (which serves static files via + ``--set-path``), this sets up a reverse proxy so Tailscale + forwards traffic to a local HTTP server. + + Args: + local_port: Port of the local HTTP server to proxy to. + https_port: Public-facing HTTPS port (default ``443``). + """ + cmd: list[str] = ["tailscale", "serve", "--bg"] + if https_port != 443: + cmd.append(f"--https={https_port}") + cmd.append(f"http://127.0.0.1:{local_port}") + + returncode, stdout, stderr = await self._run(*cmd) + if returncode != 0: + msg = stderr.strip() or stdout.strip() + raise TailscaleError(f"Failed to start Tailscale proxy: {msg}") + logger.info( + "Tailscale proxy started: https port %d -> http://127.0.0.1:%d", + https_port, + local_port, + ) + + async def stop_proxy(self, *, https_port: int = 443) -> None: + """Remove a reverse-proxy serve configuration for an HTTPS port. + + Runs: ``tailscale serve --bg [--https=] off`` + """ + cmd: list[str] = ["tailscale", "serve", "--bg"] + if https_port != 443: + cmd.append(f"--https={https_port}") + cmd.append("off") + + returncode, stdout, stderr = await self._run(*cmd) + if returncode != 0: + logger.warning( + "Failed to stop Tailscale proxy on port %d (may already be stopped): %s", + https_port, + stderr.strip() or stdout.strip(), + ) + logger.info("Tailscale proxy stopped for HTTPS port %d", https_port) async def stop_serve(self, path: str) -> None: """Remove a serve handler for a path. @@ -155,21 +214,24 @@ async def stop_serve(self, path: str) -> None: ) logger.info("Tailscale serve stopped for path: %s", path) - async def enable_funnel(self) -> None: - """Enable Funnel on port 443 (expose all serve handlers publicly). + async def enable_funnel(self, port: int = 443) -> None: + """Enable Funnel on the given *port* (expose all serve handlers publicly). - Runs: ``tailscale funnel --bg 443`` + Runs: ``tailscale funnel --bg `` + + Args: + port: HTTPS port to expose via Funnel (default ``443``). """ returncode, stdout, stderr = await self._run( "tailscale", "funnel", "--bg", - "443", + str(port), ) if returncode != 0: msg = stderr.strip() or stdout.strip() raise TailscaleError(f"Failed to enable Tailscale Funnel: {msg}") - logger.info("Tailscale Funnel enabled on port 443") + logger.info("Tailscale Funnel enabled on port %d", port) # ------------------------------------------------------------------ # Funnel management (port proxying for telemetry) diff --git a/tests/cli/test_auth.py b/tests/cli/test_auth.py index 3e2f548..99d214c 100644 --- a/tests/cli/test_auth.py +++ b/tests/cli/test_auth.py @@ -4,7 +4,10 @@ from unittest.mock import AsyncMock, MagicMock, patch -from tescmd.cli.auth import _interactive_setup, _prompt_for_domain +from tescmd.cli.auth import ( + _interactive_setup, + _prompt_for_domain, +) def _make_formatter() -> MagicMock: @@ -82,13 +85,14 @@ def test_persists_lowercased_value(self) -> None: class TestInteractiveSetupTailscaleOrigin: """Tests for Tailscale detection + Funnel lifecycle in _interactive_setup.""" - def test_shows_concrete_tailscale_url_when_hostname_provided(self) -> None: - """When tailscale_hostname is passed, Step 3 shows 'Also add' with concrete URL.""" + def test_shows_concrete_tailscale_url(self) -> None: + """When tailscale_hostname is passed with full_tier, Step 3 shows the URL.""" formatter = _make_formatter() - # Inputs: open browser=n, funnel prompt=n, client_id, secret, save=n + # Inputs: funnel=n, browser=n, client_id, secret with ( - patch("builtins.input", side_effect=["n", "n", "test-id", "secret", "n"]), + patch("builtins.input", side_effect=["n", "n", "test-id", "secret"]), patch("tescmd.cli.auth.webbrowser"), + patch("tescmd.cli.auth._write_env_file"), ): cid, cs = _interactive_setup( formatter, @@ -96,12 +100,13 @@ def test_shows_concrete_tailscale_url_when_hostname_provided(self) -> None: "http://localhost:8085/callback", domain="user.github.io", tailscale_hostname="mybox.tail99.ts.net", + full_tier=True, ) assert cid == "test-id" assert cs == "secret" calls = [str(c) for c in formatter.rich.info.call_args_list] - # Should show the concrete Tailscale origin + # Should show the concrete Tailscale origin (port 443, no suffix) assert any("Also add" in c and "https://mybox.tail99.ts.net" in c for c in calls) # Should NOT show the generic placeholder assert not any(".tailnet.ts.net" in c for c in calls) @@ -109,10 +114,12 @@ def test_shows_concrete_tailscale_url_when_hostname_provided(self) -> None: def test_generic_placeholder_when_no_tailscale(self) -> None: """Without Tailscale, Step 3 shows the generic placeholder hint.""" formatter = _make_formatter() - # Inputs: open browser=n, client_id, secret, save=n + # full_tier=True but no tailscale available → auto-detect fails + # Inputs: browser=n, client_id, secret with ( - patch("builtins.input", side_effect=["n", "test-id", "secret", "n"]), + patch("builtins.input", side_effect=["n", "test-id", "secret"]), patch("tescmd.cli.auth.webbrowser"), + patch("tescmd.cli.auth._write_env_file"), patch( "tescmd.deploy.tailscale_serve.is_tailscale_serve_ready", new_callable=AsyncMock, @@ -124,6 +131,7 @@ def test_generic_placeholder_when_no_tailscale(self) -> None: 8085, "http://localhost:8085/callback", domain="user.github.io", + full_tier=True, ) calls = [str(c) for c in formatter.rich.info.call_args_list] @@ -133,12 +141,13 @@ def test_generic_placeholder_when_no_tailscale(self) -> None: assert not any("Also add" in c for c in calls) def test_auto_detects_tailscale_when_no_hostname_passed(self) -> None: - """When tailscale_hostname is empty, auto-detection finds Tailscale.""" + """When tailscale_hostname is empty + full_tier, auto-detection finds Tailscale.""" formatter = _make_formatter() - # Inputs: open browser=n, funnel prompt=n, client_id, secret, save=n + # Inputs: funnel=n, browser=n, client_id, secret with ( - patch("builtins.input", side_effect=["n", "n", "test-id", "secret", "n"]), + patch("builtins.input", side_effect=["n", "n", "test-id", "secret"]), patch("tescmd.cli.auth.webbrowser"), + patch("tescmd.cli.auth._write_env_file"), patch( "tescmd.deploy.tailscale_serve.is_tailscale_serve_ready", new_callable=AsyncMock, @@ -155,6 +164,7 @@ def test_auto_detects_tailscale_when_no_hostname_passed(self) -> None: 8085, "http://localhost:8085/callback", domain="user.github.io", + full_tier=True, ) calls = [str(c) for c in formatter.rich.info.call_args_list] @@ -162,21 +172,30 @@ def test_auto_detects_tailscale_when_no_hostname_passed(self) -> None: assert any("Also add" in c and "https://auto.tail99.ts.net" in c for c in calls) def test_funnel_started_and_stopped(self) -> None: - """When user accepts Funnel prompt, enable_funnel is called and cleanup runs.""" + """When user accepts Funnel prompt, KeyServer starts and cleanup runs.""" formatter = _make_formatter() - # Inputs: open browser=n, funnel=Y, client_id, secret, save=n + mock_server = MagicMock() + mock_server.server_address = ("127.0.0.1", 54321) + # Inputs: funnel=Y, browser=n, client_id, secret with ( - patch("builtins.input", side_effect=["n", "Y", "test-id", "secret", "n"]), + patch("builtins.input", side_effect=["Y", "n", "test-id", "secret"]), patch("tescmd.cli.auth.webbrowser"), + patch("tescmd.cli.auth._write_env_file"), + patch("tescmd.crypto.keys.has_key_pair", return_value=True), + patch("tescmd.crypto.keys.load_public_key_pem", return_value="PEM-DATA"), patch( - "tescmd.telemetry.tailscale.TailscaleManager.enable_funnel", + "tescmd.deploy.tailscale_serve.KeyServer", + return_value=mock_server, + ) as mock_ks_cls, + patch( + "tescmd.telemetry.tailscale.TailscaleManager.start_funnel", new_callable=AsyncMock, - ) as mock_enable, + return_value="https://mybox.tail99.ts.net", + ) as mock_start_funnel, patch( - "tescmd.telemetry.tailscale.TailscaleManager._run", + "tescmd.telemetry.tailscale.TailscaleManager.stop_funnel", new_callable=AsyncMock, - return_value=(0, "", ""), - ) as mock_run, + ) as mock_stop_funnel, ): _interactive_setup( formatter, @@ -184,27 +203,28 @@ def test_funnel_started_and_stopped(self) -> None: "http://localhost:8085/callback", domain="user.github.io", tailscale_hostname="mybox.tail99.ts.net", + full_tier=True, ) - mock_enable.assert_called_once() - # Cleanup: _run called with funnel off - mock_run.assert_called_once_with("tailscale", "funnel", "--bg", "off") + mock_ks_cls.assert_called_once_with("PEM-DATA", port=0) + mock_server.start.assert_called_once() + mock_start_funnel.assert_awaited_once_with(54321) + # Cleanup: KeyServer.stop() + stop_funnel() + mock_server.stop.assert_called_once() + mock_stop_funnel.assert_awaited_once() def test_funnel_declined_no_cleanup(self) -> None: """When user declines Funnel prompt, no Funnel start/stop occurs.""" formatter = _make_formatter() - # Inputs: open browser=n, funnel=n, client_id, secret, save=n + # Inputs: funnel=n, browser=n, client_id, secret with ( - patch("builtins.input", side_effect=["n", "n", "test-id", "secret", "n"]), + patch("builtins.input", side_effect=["n", "n", "test-id", "secret"]), patch("tescmd.cli.auth.webbrowser"), + patch("tescmd.cli.auth._write_env_file"), patch( - "tescmd.telemetry.tailscale.TailscaleManager.enable_funnel", - new_callable=AsyncMock, - ) as mock_enable, - patch( - "tescmd.telemetry.tailscale.TailscaleManager._run", + "tescmd.telemetry.tailscale.TailscaleManager.start_funnel", new_callable=AsyncMock, - ) as mock_run, + ) as mock_start_funnel, ): _interactive_setup( formatter, @@ -212,20 +232,29 @@ def test_funnel_declined_no_cleanup(self) -> None: "http://localhost:8085/callback", domain="user.github.io", tailscale_hostname="mybox.tail99.ts.net", + full_tier=True, ) - mock_enable.assert_not_called() - mock_run.assert_not_called() + mock_start_funnel.assert_not_called() def test_funnel_error_falls_back_to_generic(self) -> None: - """When Funnel start fails, falls back to generic placeholder.""" + """When start_funnel fails, falls back to generic placeholder.""" formatter = _make_formatter() - # Inputs: open browser=n, funnel=Y, client_id, secret, save=n + mock_server = MagicMock() + mock_server.server_address = ("127.0.0.1", 54321) + # Inputs: funnel=Y, browser=n, client_id, secret with ( - patch("builtins.input", side_effect=["n", "Y", "test-id", "secret", "n"]), + patch("builtins.input", side_effect=["Y", "n", "test-id", "secret"]), patch("tescmd.cli.auth.webbrowser"), + patch("tescmd.cli.auth._write_env_file"), + patch("tescmd.crypto.keys.has_key_pair", return_value=True), + patch("tescmd.crypto.keys.load_public_key_pem", return_value="PEM-DATA"), + patch( + "tescmd.deploy.tailscale_serve.KeyServer", + return_value=mock_server, + ), patch( - "tescmd.telemetry.tailscale.TailscaleManager.enable_funnel", + "tescmd.telemetry.tailscale.TailscaleManager.start_funnel", new_callable=AsyncMock, side_effect=Exception("Funnel not available"), ), @@ -236,6 +265,7 @@ def test_funnel_error_falls_back_to_generic(self) -> None: "http://localhost:8085/callback", domain="user.github.io", tailscale_hostname="mybox.tail99.ts.net", + full_tier=True, ) calls = [str(c) for c in formatter.rich.info.call_args_list] @@ -243,20 +273,32 @@ def test_funnel_error_falls_back_to_generic(self) -> None: assert any("Could not start Funnel" in c for c in calls) # Should show generic placeholder since ts_hostname was cleared assert any(".tailnet.ts.net" in c for c in calls) + # KeyServer should have been stopped on failure + mock_server.stop.assert_called_once() def test_funnel_cleanup_failure_warns_user(self) -> None: """When Funnel cleanup fails, a warning is shown to the user.""" formatter = _make_formatter() - # Inputs: open browser=n, funnel=Y, client_id, secret, save=n + mock_server = MagicMock() + mock_server.server_address = ("127.0.0.1", 54321) + # Inputs: funnel=Y, browser=n, client_id, secret with ( - patch("builtins.input", side_effect=["n", "Y", "test-id", "secret", "n"]), + patch("builtins.input", side_effect=["Y", "n", "test-id", "secret"]), patch("tescmd.cli.auth.webbrowser"), + patch("tescmd.cli.auth._write_env_file"), + patch("tescmd.crypto.keys.has_key_pair", return_value=True), + patch("tescmd.crypto.keys.load_public_key_pem", return_value="PEM-DATA"), patch( - "tescmd.telemetry.tailscale.TailscaleManager.enable_funnel", + "tescmd.deploy.tailscale_serve.KeyServer", + return_value=mock_server, + ), + patch( + "tescmd.telemetry.tailscale.TailscaleManager.start_funnel", new_callable=AsyncMock, + return_value="https://mybox.tail99.ts.net", ), patch( - "tescmd.telemetry.tailscale.TailscaleManager._run", + "tescmd.telemetry.tailscale.TailscaleManager.stop_funnel", new_callable=AsyncMock, side_effect=Exception("network timeout"), ), @@ -267,26 +309,88 @@ def test_funnel_cleanup_failure_warns_user(self) -> None: "http://localhost:8085/callback", domain="user.github.io", tailscale_hostname="mybox.tail99.ts.net", + full_tier=True, ) calls = [str(c) for c in formatter.rich.info.call_args_list] assert any("Failed to stop Tailscale Funnel" in c for c in calls) assert any("tailscale funnel --bg off" in c for c in calls) + # KeyServer.stop() should still have been called + mock_server.stop.assert_called_once() + + def test_app_name_guid_generated_on_fresh_run(self) -> None: + """Fresh run generates tescmd- app name and shows it in Step 2.""" + formatter = _make_formatter() + # full_tier=True with tailscale: funnel=n, browser=n, client_id, secret + with ( + patch("builtins.input", side_effect=["n", "n", "test-id", "secret"]), + patch("tescmd.cli.auth.webbrowser"), + patch("tescmd.cli.auth._write_env_file"), + patch("tescmd.cli.auth._write_env_value") as mock_write_val, + patch.dict("os.environ", {}, clear=False), + patch("tescmd.cli.auth.uuid") as mock_uuid_mod, + ): + # Remove TESLA_APP_NAME if present + import os + + os.environ.pop("TESLA_APP_NAME", None) + mock_uuid_mod.uuid4.return_value = MagicMock(hex="aabbccdd11223344") + _interactive_setup( + formatter, + 8085, + "http://localhost:8085/callback", + domain="user.github.io", + tailscale_hostname="mybox.tail99.ts.net", + full_tier=True, + ) + + calls = [str(c) for c in formatter.rich.info.call_args_list] + # Step 2 should show the generated name + assert any("tescmd-aabbccdd" in c for c in calls) + # Should persist the app name + mock_write_val.assert_called_once_with("TESLA_APP_NAME", "tescmd-aabbccdd") + + def test_app_name_reused_from_env_on_rerun(self) -> None: + """When TESLA_APP_NAME is already set, reuses it and does not re-save.""" + formatter = _make_formatter() + # full_tier=True with tailscale: funnel=n, browser=n, client_id, secret + with ( + patch("builtins.input", side_effect=["n", "n", "test-id", "secret"]), + patch("tescmd.cli.auth.webbrowser"), + patch("tescmd.cli.auth._write_env_file"), + patch("tescmd.cli.auth._write_env_value") as mock_write_val, + patch.dict("os.environ", {"TESLA_APP_NAME": "tescmd-existing1"}, clear=False), + ): + _interactive_setup( + formatter, + 8085, + "http://localhost:8085/callback", + domain="user.github.io", + tailscale_hostname="mybox.tail99.ts.net", + full_tier=True, + ) + + calls = [str(c) for c in formatter.rich.info.call_args_list] + # Should show the existing app name + assert any("tescmd-existing1" in c for c in calls) + # Should NOT call _write_env_value since name was already saved + mock_write_val.assert_not_called() def test_eof_on_funnel_prompt_skips_funnel(self) -> None: """EOFError on Funnel prompt skips Funnel but continues setup.""" formatter = _make_formatter() - # Inputs: open browser=n, funnel=EOFError, client_id, secret, save=n + # Inputs: funnel=EOFError, browser=n, client_id, secret with ( patch( "builtins.input", - side_effect=["n", EOFError, "test-id", "secret", "n"], + side_effect=[EOFError, "n", "test-id", "secret"], ), patch("tescmd.cli.auth.webbrowser"), + patch("tescmd.cli.auth._write_env_file"), patch( - "tescmd.telemetry.tailscale.TailscaleManager.enable_funnel", + "tescmd.telemetry.tailscale.TailscaleManager.start_funnel", new_callable=AsyncMock, - ) as mock_enable, + ) as mock_start_funnel, ): cid, _cs = _interactive_setup( formatter, @@ -294,8 +398,434 @@ def test_eof_on_funnel_prompt_skips_funnel(self) -> None: "http://localhost:8085/callback", domain="user.github.io", tailscale_hostname="mybox.tail99.ts.net", + full_tier=True, ) # Setup should continue past the Funnel prompt assert cid == "test-id" - mock_enable.assert_not_called() + mock_start_funnel.assert_not_called() + + +# --------------------------------------------------------------------------- +# _interactive_setup — full_tier gating +# --------------------------------------------------------------------------- + + +class TestInteractiveSetupFullTierGating: + """Verify Tailscale prompt only appears when full_tier=True.""" + + def test_no_tailscale_prompt_without_full_tier(self) -> None: + """When full_tier=False, no Tailscale prompt even if hostname is provided.""" + formatter = _make_formatter() + # Inputs: browser=n, client_id, secret (no funnel prompt) + with ( + patch("builtins.input", side_effect=["n", "test-id", "secret"]), + patch("tescmd.cli.auth.webbrowser"), + patch("tescmd.cli.auth._write_env_file"), + ): + cid, cs = _interactive_setup( + formatter, + 8085, + "http://localhost:8085/callback", + domain="user.github.io", + tailscale_hostname="mybox.tail99.ts.net", + full_tier=False, + ) + + assert cid == "test-id" + assert cs == "secret" + calls = [str(c) for c in formatter.rich.info.call_args_list] + # Should NOT show Tailscale detection or funnel prompt + assert not any("Tailscale detected" in c for c in calls) + # Without full_tier, tailscale_hostname is not used for "Also add" + assert not any("Also add" in c for c in calls) + + def test_no_tailscale_auto_detect_without_full_tier(self) -> None: + """When full_tier=False, auto-detection is not even attempted.""" + formatter = _make_formatter() + # Inputs: browser=n, client_id, secret + with ( + patch("builtins.input", side_effect=["n", "test-id", "secret"]), + patch("tescmd.cli.auth.webbrowser"), + patch("tescmd.cli.auth._write_env_file"), + patch( + "tescmd.deploy.tailscale_serve.is_tailscale_serve_ready", + new_callable=AsyncMock, + return_value=True, + ) as mock_ts_ready, + ): + _interactive_setup( + formatter, + 8085, + "http://localhost:8085/callback", + domain="user.github.io", + full_tier=False, + ) + + # Auto-detection should not have been called + mock_ts_ready.assert_not_called() + + def test_default_full_tier_is_false(self) -> None: + """Default full_tier=False — standalone auth command has no Tailscale prompt.""" + formatter = _make_formatter() + # Inputs: browser=n, client_id, secret (no funnel prompt) + with ( + patch("builtins.input", side_effect=["n", "test-id", "secret"]), + patch("tescmd.cli.auth.webbrowser"), + patch("tescmd.cli.auth._write_env_file"), + ): + cid, _cs = _interactive_setup( + formatter, + 8085, + "http://localhost:8085/callback", + domain="user.github.io", + tailscale_hostname="mybox.tail99.ts.net", + ) + + assert cid == "test-id" + calls = [str(c) for c in formatter.rich.info.call_args_list] + assert not any("Tailscale detected" in c for c in calls) + + +# --------------------------------------------------------------------------- +# _interactive_setup — prompt ordering +# --------------------------------------------------------------------------- + + +class TestInteractiveSetupPromptOrder: + """Verify the Tailscale prompt comes before the browser prompt.""" + + def test_tailscale_prompt_before_browser(self) -> None: + """When full_tier=True + Tailscale, funnel prompt precedes browser prompt.""" + formatter = _make_formatter() + prompts_seen: list[str] = [] + + def mock_input(prompt: str = "") -> str: + prompts_seen.append(prompt) + if "Funnel" in prompt: + return "n" + if "Developer Portal" in prompt: + return "n" + if "Client ID" in prompt: + return "test-id" + if "Client Secret" in prompt: + return "secret" + return "" + + with ( + patch("builtins.input", side_effect=mock_input), + patch("tescmd.cli.auth.webbrowser"), + patch("tescmd.cli.auth._write_env_file"), + ): + _interactive_setup( + formatter, + 8085, + "http://localhost:8085/callback", + domain="user.github.io", + tailscale_hostname="mybox.tail99.ts.net", + full_tier=True, + ) + + # Find the indices of key prompts + funnel_idx = next((i for i, p in enumerate(prompts_seen) if "Funnel" in p), None) + browser_idx = next( + (i for i, p in enumerate(prompts_seen) if "Developer Portal" in p), None + ) + assert funnel_idx is not None, "Funnel prompt not found" + assert browser_idx is not None, "Browser prompt not found" + assert funnel_idx < browser_idx, ( + f"Funnel prompt (idx={funnel_idx}) should come before " + f"browser prompt (idx={browser_idx})" + ) + + def test_steps_uninterrupted_when_full_tier(self) -> None: + """Steps 1-6 appear contiguously without Tailscale prompts between them.""" + formatter = _make_formatter() + # Inputs: funnel=n, browser=n, client_id, secret + with ( + patch("builtins.input", side_effect=["n", "n", "test-id", "secret"]), + patch("tescmd.cli.auth.webbrowser"), + patch("tescmd.cli.auth._write_env_file"), + ): + _interactive_setup( + formatter, + 8085, + "http://localhost:8085/callback", + domain="user.github.io", + tailscale_hostname="mybox.tail99.ts.net", + full_tier=True, + ) + + calls = [str(c) for c in formatter.rich.info.call_args_list] + # Find indices of Steps + step_indices = [] + for i, c in enumerate(calls): + if "Step 1" in c: + step_indices.append(("Step 1", i)) + elif "Step 2" in c: + step_indices.append(("Step 2", i)) + elif "Step 3" in c: + step_indices.append(("Step 3", i)) + elif "Step 4" in c: + step_indices.append(("Step 4", i)) + elif "Step 5" in c: + step_indices.append(("Step 5", i)) + elif "Step 6" in c: + step_indices.append(("Step 6", i)) + + assert len(step_indices) == 6, f"Expected 6 steps, found {len(step_indices)}" + + # Verify no "Tailscale detected" or "Funnel" messages between Step 1 and Step 6 + first_step_idx = step_indices[0][1] + last_step_idx = step_indices[-1][1] + between_steps = calls[first_step_idx : last_step_idx + 1] + assert not any("Tailscale detected" in c for c in between_steps) + assert not any("Start Tailscale Funnel" in c for c in between_steps) + + +# --------------------------------------------------------------------------- +# _interactive_setup — tier-aware scopes +# --------------------------------------------------------------------------- + + +class TestInteractiveSetupTierAwareScopes: + """Verify Step 4 scope list changes with full_tier flag.""" + + def test_step4_scopes_readonly_tier(self) -> None: + """When full_tier=False, Step 4 shows only readonly scopes.""" + formatter = _make_formatter() + # Inputs: browser=n, client_id, secret (no funnel prompt for readonly) + with ( + patch("builtins.input", side_effect=["n", "test-id", "secret"]), + patch("tescmd.cli.auth.webbrowser"), + patch("tescmd.cli.auth._write_env_file"), + ): + _interactive_setup( + formatter, + 8085, + "http://localhost:8085/callback", + domain="user.github.io", + full_tier=False, + ) + + calls = [str(c) for c in formatter.rich.info.call_args_list] + # Readonly scopes present + assert any("Vehicle Information" in c for c in calls) + assert any("Vehicle Location" in c for c in calls) + assert any("Energy Information" in c for c in calls) + assert any("User Data" in c for c in calls) + # Command scopes absent + assert not any("Vehicle Commands" in c for c in calls) + assert not any("Vehicle Charging Management" in c for c in calls) + assert not any("Energy Commands" in c for c in calls) + + def test_step4_scopes_full_tier(self) -> None: + """When full_tier=True, Step 4 says 'Select All' instead of listing scopes.""" + formatter = _make_formatter() + # Inputs: funnel=n, browser=n, client_id, secret + with ( + patch("builtins.input", side_effect=["n", "n", "test-id", "secret"]), + patch("tescmd.cli.auth.webbrowser"), + patch("tescmd.cli.auth._write_env_file"), + ): + _interactive_setup( + formatter, + 8085, + "http://localhost:8085/callback", + domain="user.github.io", + tailscale_hostname="mybox.tail99.ts.net", + full_tier=True, + ) + + calls = [str(c) for c in formatter.rich.info.call_args_list] + # Should show "Select All" for full tier + assert any("Select All" in c for c in calls) + # Individual scope names should NOT appear + assert not any("Vehicle Information" in c for c in calls) + assert not any("Vehicle Commands" in c for c in calls) + assert not any("Vehicle Charging Management" in c for c in calls) + assert not any("Energy Commands" in c for c in calls) + + +# --------------------------------------------------------------------------- +# _interactive_setup — key serving +# --------------------------------------------------------------------------- + + +class TestInteractiveSetupKeyServing: + """Verify key generation and serving during Funnel start.""" + + def test_key_generated_during_funnel_start(self) -> None: + """When has_key_pair=False, generate_ec_key_pair is called.""" + formatter = _make_formatter() + mock_server = MagicMock() + mock_server.server_address = ("127.0.0.1", 54321) + # Inputs: funnel=Y, browser=n, client_id, secret + with ( + patch("builtins.input", side_effect=["Y", "n", "test-id", "secret"]), + patch("tescmd.cli.auth.webbrowser"), + patch("tescmd.cli.auth._write_env_file"), + patch("tescmd.crypto.keys.has_key_pair", return_value=False), + patch("tescmd.crypto.keys.generate_ec_key_pair") as mock_gen, + patch("tescmd.crypto.keys.load_public_key_pem", return_value="PEM-DATA"), + patch( + "tescmd.deploy.tailscale_serve.KeyServer", + return_value=mock_server, + ), + patch( + "tescmd.telemetry.tailscale.TailscaleManager.start_funnel", + new_callable=AsyncMock, + return_value="https://mybox.tail99.ts.net", + ), + patch( + "tescmd.telemetry.tailscale.TailscaleManager.stop_funnel", + new_callable=AsyncMock, + ), + ): + _interactive_setup( + formatter, + 8085, + "http://localhost:8085/callback", + domain="user.github.io", + tailscale_hostname="mybox.tail99.ts.net", + full_tier=True, + ) + + mock_gen.assert_called_once() + calls = [str(c) for c in formatter.rich.info.call_args_list] + assert any("Generating" in c for c in calls) + + def test_key_not_regenerated_when_exists(self) -> None: + """When has_key_pair=True, generate_ec_key_pair is NOT called.""" + formatter = _make_formatter() + mock_server = MagicMock() + mock_server.server_address = ("127.0.0.1", 54321) + # Inputs: funnel=Y, browser=n, client_id, secret + with ( + patch("builtins.input", side_effect=["Y", "n", "test-id", "secret"]), + patch("tescmd.cli.auth.webbrowser"), + patch("tescmd.cli.auth._write_env_file"), + patch("tescmd.crypto.keys.has_key_pair", return_value=True), + patch("tescmd.crypto.keys.generate_ec_key_pair") as mock_gen, + patch("tescmd.crypto.keys.load_public_key_pem", return_value="PEM-DATA"), + patch( + "tescmd.deploy.tailscale_serve.KeyServer", + return_value=mock_server, + ), + patch( + "tescmd.telemetry.tailscale.TailscaleManager.start_funnel", + new_callable=AsyncMock, + return_value="https://mybox.tail99.ts.net", + ), + patch( + "tescmd.telemetry.tailscale.TailscaleManager.stop_funnel", + new_callable=AsyncMock, + ), + ): + _interactive_setup( + formatter, + 8085, + "http://localhost:8085/callback", + domain="user.github.io", + tailscale_hostname="mybox.tail99.ts.net", + full_tier=True, + ) + + mock_gen.assert_not_called() + + def test_cleanup_runs_on_eof_during_client_id(self) -> None: + """EOFError at Client ID prompt still triggers KeyServer.stop().""" + formatter = _make_formatter() + mock_server = MagicMock() + mock_server.server_address = ("127.0.0.1", 54321) + # Inputs: funnel=Y, browser=n, client_id=EOFError + with ( + patch("builtins.input", side_effect=["Y", "n", EOFError]), + patch("tescmd.cli.auth.webbrowser"), + patch("tescmd.crypto.keys.has_key_pair", return_value=True), + patch("tescmd.crypto.keys.load_public_key_pem", return_value="PEM-DATA"), + patch( + "tescmd.deploy.tailscale_serve.KeyServer", + return_value=mock_server, + ), + patch( + "tescmd.telemetry.tailscale.TailscaleManager.start_funnel", + new_callable=AsyncMock, + return_value="https://mybox.tail99.ts.net", + ), + patch( + "tescmd.telemetry.tailscale.TailscaleManager.stop_funnel", + new_callable=AsyncMock, + ), + ): + cid, cs = _interactive_setup( + formatter, + 8085, + "http://localhost:8085/callback", + domain="user.github.io", + tailscale_hostname="mybox.tail99.ts.net", + full_tier=True, + ) + + assert cid == "" + assert cs == "" + mock_server.stop.assert_called_once() + + def test_cleanup_runs_on_empty_client_id(self) -> None: + """Empty Client ID returns early but still triggers KeyServer.stop().""" + formatter = _make_formatter() + mock_server = MagicMock() + mock_server.server_address = ("127.0.0.1", 54321) + # Inputs: funnel=Y, browser=n, client_id="" x3 (exhausts retries) + with ( + patch("builtins.input", side_effect=["Y", "n", "", "", ""]), + patch("tescmd.cli.auth.webbrowser"), + patch("tescmd.crypto.keys.has_key_pair", return_value=True), + patch("tescmd.crypto.keys.load_public_key_pem", return_value="PEM-DATA"), + patch( + "tescmd.deploy.tailscale_serve.KeyServer", + return_value=mock_server, + ), + patch( + "tescmd.telemetry.tailscale.TailscaleManager.start_funnel", + new_callable=AsyncMock, + return_value="https://mybox.tail99.ts.net", + ), + patch( + "tescmd.telemetry.tailscale.TailscaleManager.stop_funnel", + new_callable=AsyncMock, + ), + ): + cid, cs = _interactive_setup( + formatter, + 8085, + "http://localhost:8085/callback", + domain="user.github.io", + tailscale_hostname="mybox.tail99.ts.net", + full_tier=True, + ) + + assert cid == "" + assert cs == "" + mock_server.stop.assert_called_once() + + def test_step1_shows_create_new_application(self) -> None: + """Step 1 header says 'Create New Application', not 'Registration'.""" + formatter = _make_formatter() + # Inputs: browser=n, client_id, secret + with ( + patch("builtins.input", side_effect=["n", "test-id", "secret"]), + patch("tescmd.cli.auth.webbrowser"), + patch("tescmd.cli.auth._write_env_file"), + ): + _interactive_setup( + formatter, + 8085, + "http://localhost:8085/callback", + domain="user.github.io", + ) + + calls = [str(c) for c in formatter.rich.info.call_args_list] + assert any("Create New Application" in c for c in calls) + # "Registration" should not appear as a Step 1 header + assert not any("Step 1" in c and "Registration" in c for c in calls) diff --git a/tests/cli/test_setup.py b/tests/cli/test_setup.py index 5abd654..e057b58 100644 --- a/tests/cli/test_setup.py +++ b/tests/cli/test_setup.py @@ -11,10 +11,13 @@ from tescmd.cli.setup import ( TIER_FULL, TIER_READONLY, + _check_key_mismatch, _cmd_setup, _deploy_key_github, _developer_portal_setup, _domain_setup, + _key_setup, + _oauth_login_step, _precheck_public_key, _print_next_steps, _prompt_tier, @@ -213,6 +216,129 @@ def test_lowercases_github_username_in_domain(self, monkeypatch: pytest.MonkeyPa mock_write.assert_any_call("TESLA_DOMAIN", "testuser.github.io") +# --------------------------------------------------------------------------- +# Key mismatch warning +# --------------------------------------------------------------------------- + + +class TestCheckKeyMismatch: + """Tests for _check_key_mismatch (runs between Phase 1 and Phase 2).""" + + def test_no_warning_when_no_local_key(self, monkeypatch: pytest.MonkeyPatch) -> None: + """No warning when no local key pair exists.""" + for key in list(os.environ): + if key.startswith("TESLA_"): + monkeypatch.delenv(key, raising=False) + + from tescmd.models.config import AppSettings + + settings = AppSettings(_env_file=None) # type: ignore[call-arg] + formatter = _make_formatter() + + with patch("tescmd.crypto.keys.has_key_pair", return_value=False): + _check_key_mismatch(formatter, settings, "user.github.io") + + # No warning should have been printed + calls = [str(c) for c in formatter.rich.info.call_args_list] + assert not any("differs" in c for c in calls) + + def test_no_warning_when_keys_match(self, monkeypatch: pytest.MonkeyPatch) -> None: + """No warning when remote key matches local key.""" + for key in list(os.environ): + if key.startswith("TESLA_"): + monkeypatch.delenv(key, raising=False) + + from tescmd.models.config import AppSettings + + settings = AppSettings(_env_file=None) # type: ignore[call-arg] + formatter = _make_formatter() + local_pem = "-----BEGIN PUBLIC KEY-----\nMATCH\n-----END PUBLIC KEY-----\n" + + with ( + patch("tescmd.crypto.keys.has_key_pair", return_value=True), + patch("tescmd.crypto.keys.load_public_key_pem", return_value=local_pem), + patch("tescmd.deploy.github_pages.fetch_key_pem", return_value=local_pem.strip()), + ): + _check_key_mismatch(formatter, settings, "user.github.io") + + calls = [str(c) for c in formatter.rich.info.call_args_list] + assert not any("differs" in c for c in calls) + + def test_warns_when_keys_differ_github(self, monkeypatch: pytest.MonkeyPatch) -> None: + """Warning shown when remote GitHub key differs from local.""" + for key in list(os.environ): + if key.startswith("TESLA_"): + monkeypatch.delenv(key, raising=False) + + from tescmd.models.config import AppSettings + + settings = AppSettings(_env_file=None) # type: ignore[call-arg] + formatter = _make_formatter() + local_pem = "-----BEGIN PUBLIC KEY-----\nLOCAL\n-----END PUBLIC KEY-----\n" + remote_pem = "-----BEGIN PUBLIC KEY-----\nREMOTE\n-----END PUBLIC KEY-----" + + with ( + patch("tescmd.crypto.keys.has_key_pair", return_value=True), + patch("tescmd.crypto.keys.load_public_key_pem", return_value=local_pem), + patch("tescmd.deploy.github_pages.fetch_key_pem", return_value=remote_pem), + ): + _check_key_mismatch(formatter, settings, "user.github.io") + + calls = [str(c) for c in formatter.rich.info.call_args_list] + assert any("differs" in c for c in calls) + assert any("Phase 3" in c for c in calls) + + def test_warns_when_keys_differ_tailscale(self, monkeypatch: pytest.MonkeyPatch) -> None: + """Warning shown when remote Tailscale key differs from local.""" + for key in list(os.environ): + if key.startswith("TESLA_"): + monkeypatch.delenv(key, raising=False) + monkeypatch.setenv("TESLA_HOSTING_METHOD", "tailscale") + + from tescmd.models.config import AppSettings + + settings = AppSettings(_env_file=None) # type: ignore[call-arg] + formatter = _make_formatter() + local_pem = "-----BEGIN PUBLIC KEY-----\nLOCAL\n-----END PUBLIC KEY-----\n" + + remote_pem = "-----BEGIN PUBLIC KEY-----\nREMOTE\n-----END PUBLIC KEY-----" + + with ( + patch("tescmd.crypto.keys.has_key_pair", return_value=True), + patch("tescmd.crypto.keys.load_public_key_pem", return_value=local_pem), + patch( + "tescmd.deploy.tailscale_serve.fetch_tailscale_key_pem", + return_value=remote_pem, + ), + ): + _check_key_mismatch(formatter, settings, "mybox.tail99.ts.net") + + calls = [str(c) for c in formatter.rich.info.call_args_list] + assert any("differs" in c for c in calls) + + def test_no_warning_when_remote_unreachable(self, monkeypatch: pytest.MonkeyPatch) -> None: + """No warning when remote key cannot be fetched.""" + for key in list(os.environ): + if key.startswith("TESLA_"): + monkeypatch.delenv(key, raising=False) + + from tescmd.models.config import AppSettings + + settings = AppSettings(_env_file=None) # type: ignore[call-arg] + formatter = _make_formatter() + local_pem = "-----BEGIN PUBLIC KEY-----\nLOCAL\n-----END PUBLIC KEY-----\n" + + with ( + patch("tescmd.crypto.keys.has_key_pair", return_value=True), + patch("tescmd.crypto.keys.load_public_key_pem", return_value=local_pem), + patch("tescmd.deploy.github_pages.fetch_key_pem", return_value=None), + ): + _check_key_mismatch(formatter, settings, "user.github.io") + + calls = [str(c) for c in formatter.rich.info.call_args_list] + assert not any("differs" in c for c in calls) + + # --------------------------------------------------------------------------- # Phase 6: Next steps # --------------------------------------------------------------------------- @@ -323,6 +449,7 @@ async def test_full_flow_includes_key_setup(self, monkeypatch: pytest.MonkeyPatc return_value=("test-id", "test-secret"), ), patch("tescmd.cli.setup._domain_setup", return_value="user.github.io"), + patch("tescmd.cli.setup._check_key_mismatch"), patch("tescmd.cli.setup._key_setup") as mock_key, patch( "tescmd.cli.setup._enrollment_step", @@ -340,6 +467,48 @@ async def test_full_flow_includes_key_setup(self, monkeypatch: pytest.MonkeyPatc await _cmd_setup(app_ctx) mock_key.assert_called_once() + @pytest.mark.asyncio + async def test_full_flow_phase_order(self, monkeypatch: pytest.MonkeyPatch) -> None: + """Verify phases run in order: keys → oauth → registration → enrollment.""" + for key in list(os.environ): + if key.startswith("TESLA_"): + monkeypatch.delenv(key, raising=False) + monkeypatch.setenv("TESLA_CLIENT_ID", "test-id") + monkeypatch.setenv("TESLA_CLIENT_SECRET", "test-secret") + monkeypatch.setenv("TESLA_DOMAIN", "user.github.io") + + app_ctx = _make_app_ctx() + call_order: list[str] = [] + + def track_key(*_a: object, **_k: object) -> None: + call_order.append("key_setup") + + async def track_oauth(*_a: object, **_k: object) -> None: + call_order.append("oauth") + + async def track_registration(*_a: object, **_k: object) -> None: + call_order.append("registration") + + async def track_enrollment(*_a: object, **_k: object) -> None: + call_order.append("enrollment") + + with ( + patch("tescmd.cli.setup._prompt_tier", return_value=TIER_FULL), + patch( + "tescmd.cli.setup._developer_portal_setup", + return_value=("test-id", "test-secret"), + ), + patch("tescmd.cli.setup._domain_setup", return_value="user.github.io"), + patch("tescmd.cli.setup._check_key_mismatch"), + patch("tescmd.cli.setup._key_setup", side_effect=track_key), + patch("tescmd.cli.setup._oauth_login_step", side_effect=track_oauth), + patch("tescmd.cli.setup._registration_step", side_effect=track_registration), + patch("tescmd.cli.setup._enrollment_step", side_effect=track_enrollment), + ): + await _cmd_setup(app_ctx) + + assert call_order == ["key_setup", "registration", "oauth", "enrollment"] + # --------------------------------------------------------------------------- # Phase 4: Registration — precheck and error remediation @@ -967,10 +1136,13 @@ def test_passes_tailscale_hostname_from_settings( formatter = app_ctx.formatter with patch("tescmd.cli.auth._interactive_setup", return_value=("id", "secret")) as mock_is: - _developer_portal_setup(formatter, app_ctx, settings, domain="mybox.tail99.ts.net") + _developer_portal_setup( + formatter, app_ctx, settings, domain="mybox.tail99.ts.net", tier="full" + ) _args, kwargs = mock_is.call_args assert kwargs.get("tailscale_hostname") == "mybox.tail99.ts.net" + assert kwargs.get("full_tier") is True def test_no_tailscale_hostname_when_github_hosting( self, monkeypatch: pytest.MonkeyPatch @@ -988,10 +1160,55 @@ def test_no_tailscale_hostname_when_github_hosting( formatter = app_ctx.formatter with patch("tescmd.cli.auth._interactive_setup", return_value=("id", "secret")) as mock_is: - _developer_portal_setup(formatter, app_ctx, settings, domain="user.github.io") + _developer_portal_setup( + formatter, app_ctx, settings, domain="user.github.io", tier="readonly" + ) _args, kwargs = mock_is.call_args assert kwargs.get("tailscale_hostname") == "" + assert kwargs.get("full_tier") is False + + def test_full_tier_passed_through(self, monkeypatch: pytest.MonkeyPatch) -> None: + """When tier='full', full_tier=True is forwarded to _interactive_setup.""" + for key in list(os.environ): + if key.startswith("TESLA_"): + monkeypatch.delenv(key, raising=False) + monkeypatch.setenv("TESLA_DOMAIN", "user.github.io") + + from tescmd.models.config import AppSettings + + settings = AppSettings(_env_file=None) # type: ignore[call-arg] + app_ctx = _make_app_ctx() + formatter = app_ctx.formatter + + with patch("tescmd.cli.auth._interactive_setup", return_value=("id", "secret")) as mock_is: + _developer_portal_setup( + formatter, app_ctx, settings, domain="user.github.io", tier="full" + ) + + _args, kwargs = mock_is.call_args + assert kwargs.get("full_tier") is True + + def test_readonly_tier_passed_through(self, monkeypatch: pytest.MonkeyPatch) -> None: + """When tier='readonly', full_tier=False is forwarded to _interactive_setup.""" + for key in list(os.environ): + if key.startswith("TESLA_"): + monkeypatch.delenv(key, raising=False) + monkeypatch.setenv("TESLA_DOMAIN", "user.github.io") + + from tescmd.models.config import AppSettings + + settings = AppSettings(_env_file=None) # type: ignore[call-arg] + app_ctx = _make_app_ctx() + formatter = app_ctx.formatter + + with patch("tescmd.cli.auth._interactive_setup", return_value=("id", "secret")) as mock_is: + _developer_portal_setup( + formatter, app_ctx, settings, domain="user.github.io", tier="readonly" + ) + + _args, kwargs = mock_is.call_args + assert kwargs.get("full_tier") is False def test_skips_when_already_configured(self, monkeypatch: pytest.MonkeyPatch) -> None: """When client_id is already set, returns early without calling _interactive_setup.""" @@ -1015,3 +1232,230 @@ def test_skips_when_already_configured(self, monkeypatch: pytest.MonkeyPatch) -> assert cid == "existing-id" assert cs == "existing-secret" mock_is.assert_not_called() + + +# --------------------------------------------------------------------------- +# --force flag behavior +# --------------------------------------------------------------------------- + + +class TestForceFlag: + """Verify --force bypasses all 'already configured' guards.""" + + def test_force_reconfigures_tier_when_already_full( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + """With force, a user with full tier gets re-prompted instead of auto-returning.""" + for key in list(os.environ): + if key.startswith("TESLA_"): + monkeypatch.delenv(key, raising=False) + monkeypatch.setenv("TESLA_SETUP_TIER", "full") + + from tescmd.models.config import AppSettings + + settings = AppSettings(_env_file=None) # type: ignore[call-arg] + formatter = _make_formatter() + + # Without force, full tier returns immediately + tier = _prompt_tier(formatter, settings, force=False) + assert tier == TIER_FULL + + # With force, the user is re-prompted (choose "2" for full) + with ( + patch("builtins.input", return_value="2"), + patch("tescmd.cli.auth._write_env_value"), + ): + tier = _prompt_tier(formatter, settings, force=True) + assert tier == TIER_FULL + + def test_force_reconfigures_domain_when_already_set( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + """With force, domain is re-prompted despite an existing value.""" + for key in list(os.environ): + if key.startswith("TESLA_"): + monkeypatch.delenv(key, raising=False) + monkeypatch.setenv("TESLA_DOMAIN", "existing.example.com") + + from tescmd.models.config import AppSettings + + settings = AppSettings(_env_file=None) # type: ignore[call-arg] + formatter = _make_formatter() + + # Without force, returns existing domain + domain = _domain_setup(formatter, settings, force=False) + assert domain == "existing.example.com" + + # With force, falls through to the domain selection logic + with ( + patch("tescmd.deploy.github_pages.is_gh_available", return_value=False), + patch( + "tescmd.cli.setup._is_tailscale_ready", + new_callable=AsyncMock, + return_value=False, + ), + patch( + "tescmd.cli.setup._manual_domain_setup", + return_value="new.example.com", + ) as mock_manual, + ): + domain = _domain_setup(formatter, settings, force=True) + + assert domain == "new.example.com" + mock_manual.assert_called_once() + + def test_force_reconfigures_credentials_when_already_set( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + """With force, _interactive_setup is called despite existing client_id.""" + for key in list(os.environ): + if key.startswith("TESLA_"): + monkeypatch.delenv(key, raising=False) + monkeypatch.setenv("TESLA_CLIENT_ID", "existing-id") + monkeypatch.setenv("TESLA_CLIENT_SECRET", "existing-secret") + + from tescmd.models.config import AppSettings + + settings = AppSettings(_env_file=None) # type: ignore[call-arg] + app_ctx = _make_app_ctx() + formatter = app_ctx.formatter + + with patch( + "tescmd.cli.auth._interactive_setup", + return_value=("new-id", "new-secret"), + ) as mock_is: + cid, cs = _developer_portal_setup( + formatter, app_ctx, settings, domain="user.github.io", force=True, tier="full" + ) + + assert cid == "new-id" + assert cs == "new-secret" + mock_is.assert_called_once() + + def test_force_regenerates_keys(self, monkeypatch: pytest.MonkeyPatch) -> None: + """With force, keys are regenerated with overwrite=True.""" + for key in list(os.environ): + if key.startswith("TESLA_"): + monkeypatch.delenv(key, raising=False) + monkeypatch.setenv("TESLA_CONFIG_DIR", "/tmp/test-tescmd") + + from tescmd.models.config import AppSettings + + settings = AppSettings(_env_file=None) # type: ignore[call-arg] + formatter = _make_formatter() + + with ( + patch("tescmd.crypto.keys.has_key_pair", return_value=True), + patch("tescmd.crypto.keys.get_key_fingerprint", return_value="abc123"), + patch("tescmd.crypto.keys.generate_ec_key_pair") as mock_gen, + patch("tescmd.cli.setup._deploy_key_github"), + ): + _key_setup(formatter, settings, "user.github.io", force=True) + + mock_gen.assert_called_once() + _args, kwargs = mock_gen.call_args + assert kwargs.get("overwrite") is True + + def test_force_does_not_regenerate_without_flag(self, monkeypatch: pytest.MonkeyPatch) -> None: + """Without force, existing keys are kept.""" + for key in list(os.environ): + if key.startswith("TESLA_"): + monkeypatch.delenv(key, raising=False) + monkeypatch.setenv("TESLA_CONFIG_DIR", "/tmp/test-tescmd") + + from tescmd.models.config import AppSettings + + settings = AppSettings(_env_file=None) # type: ignore[call-arg] + formatter = _make_formatter() + + with ( + patch("tescmd.crypto.keys.has_key_pair", return_value=True), + patch("tescmd.crypto.keys.get_key_fingerprint", return_value="abc123"), + patch("tescmd.crypto.keys.generate_ec_key_pair") as mock_gen, + patch("tescmd.cli.setup._deploy_key_github"), + ): + _key_setup(formatter, settings, "user.github.io", force=False) + + mock_gen.assert_not_called() + + @pytest.mark.asyncio + async def test_force_reauthenticates_oauth(self, monkeypatch: pytest.MonkeyPatch) -> None: + """With force, OAuth re-runs despite existing token with all scopes.""" + for key in list(os.environ): + if key.startswith("TESLA_"): + monkeypatch.delenv(key, raising=False) + + app_ctx = _make_app_ctx() + formatter = app_ctx.formatter + + from tescmd.models.config import AppSettings + + settings = AppSettings(_env_file=None) # type: ignore[call-arg] + + mock_store = MagicMock() + mock_store.has_token = True + mock_store.metadata = { + "scopes": [ + "openid", + "offline_access", + "user_data", + "vehicle_device_data", + "vehicle_cmds", + "vehicle_charging_cmds", + "vehicle_location", + "energy_device_data", + "energy_cmds", + ] + } + + with ( + patch("tescmd.auth.token_store.TokenStore", return_value=mock_store), + patch("tescmd.auth.oauth.login_flow", new_callable=AsyncMock) as mock_login, + ): + await _oauth_login_step( + formatter, app_ctx, settings, "test-id", "test-secret", force=True + ) + + mock_login.assert_called_once() + + @pytest.mark.asyncio + async def test_no_force_skips_oauth_when_scopes_present( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + """Without force, OAuth is skipped when token has all required scopes.""" + for key in list(os.environ): + if key.startswith("TESLA_"): + monkeypatch.delenv(key, raising=False) + + app_ctx = _make_app_ctx() + formatter = app_ctx.formatter + + from tescmd.models.config import AppSettings + + settings = AppSettings(_env_file=None) # type: ignore[call-arg] + + mock_store = MagicMock() + mock_store.has_token = True + mock_store.metadata = { + "scopes": [ + "openid", + "offline_access", + "user_data", + "vehicle_device_data", + "vehicle_cmds", + "vehicle_charging_cmds", + "vehicle_location", + "energy_device_data", + "energy_cmds", + ] + } + + with ( + patch("tescmd.auth.token_store.TokenStore", return_value=mock_store), + patch("tescmd.auth.oauth.login_flow", new_callable=AsyncMock) as mock_login, + ): + await _oauth_login_step( + formatter, app_ctx, settings, "test-id", "test-secret", force=False + ) + + mock_login.assert_not_called() diff --git a/tests/deploy/test_tailscale_serve.py b/tests/deploy/test_tailscale_serve.py index 1293b72..4f335a6 100644 --- a/tests/deploy/test_tailscale_serve.py +++ b/tests/deploy/test_tailscale_serve.py @@ -2,6 +2,7 @@ from __future__ import annotations +import urllib.request from typing import TYPE_CHECKING from unittest.mock import AsyncMock, MagicMock, patch @@ -10,6 +11,7 @@ from tescmd.api.errors import TailscaleError from tescmd.deploy import tailscale_serve as ts_serve +from tescmd.deploy.tailscale_serve import KeyServer if TYPE_CHECKING: from pathlib import Path @@ -51,7 +53,7 @@ async def test_creates_nested_parents(self, tmp_path: Path) -> None: class TestStartKeyServing: - async def test_starts_serve_and_funnel(self, tmp_path: Path) -> None: + async def test_starts_serve_with_funnel(self, tmp_path: Path) -> None: # Create the expected directory structure well_known = tmp_path / ".well-known" / "appspecific" well_known.mkdir(parents=True) @@ -68,15 +70,11 @@ async def test_starts_serve_and_funnel(self, tmp_path: Path) -> None: patch.object( ts_serve.TailscaleManager, "start_serve", new_callable=AsyncMock ) as mock_serve, - patch.object( - ts_serve.TailscaleManager, "enable_funnel", new_callable=AsyncMock - ) as mock_funnel, ): hostname = await ts_serve.start_key_serving(serve_dir=tmp_path) assert hostname == "mybox.tail99.ts.net" - mock_serve.assert_awaited_once() - mock_funnel.assert_awaited_once() + mock_serve.assert_awaited_once_with("/", str(tmp_path), funnel=True) async def test_raises_when_serve_dir_missing(self, tmp_path: Path) -> None: empty_dir = tmp_path / "empty" @@ -97,7 +95,7 @@ async def test_stops_serve(self) -> None: ts_serve.TailscaleManager, "stop_serve", new_callable=AsyncMock ) as mock_stop: await ts_serve.stop_key_serving() - mock_stop.assert_awaited_once_with("/.well-known/") + mock_stop.assert_awaited_once_with("/") # --------------------------------------------------------------------------- @@ -268,3 +266,61 @@ async def test_timeout(self) -> None: ), ): assert await ts_serve.wait_for_tailscale_deployment("bad.ts.net", timeout=10) is False + + +# --------------------------------------------------------------------------- +# KeyServer (in-process HTTP server) +# --------------------------------------------------------------------------- + +PEM_CONTENT = "-----BEGIN PUBLIC KEY-----\ntest-key-data\n-----END PUBLIC KEY-----\n" + + +class TestKeyServer: + def test_serves_root_200(self) -> None: + server = KeyServer(PEM_CONTENT, port=0) + server.start() + try: + port = server.server_address[1] + resp = urllib.request.urlopen(f"http://127.0.0.1:{port}/") + assert resp.status == 200 + finally: + server.stop() + + def test_serves_wellknown_pem(self) -> None: + server = KeyServer(PEM_CONTENT, port=0) + server.start() + try: + port = server.server_address[1] + resp = urllib.request.urlopen(f"http://127.0.0.1:{port}/{ts_serve.WELL_KNOWN_PATH}") + assert resp.status == 200 + body = resp.read().decode() + assert body == PEM_CONTENT + assert resp.headers["Content-Type"] == "application/x-pem-file" + finally: + server.stop() + + def test_returns_404_for_unknown(self) -> None: + server = KeyServer(PEM_CONTENT, port=0) + server.start() + try: + port = server.server_address[1] + with pytest.raises(urllib.error.HTTPError) as exc_info: + urllib.request.urlopen(f"http://127.0.0.1:{port}/unknown") + assert exc_info.value.code == 404 + finally: + server.stop() + + def test_start_stop_lifecycle(self) -> None: + server = KeyServer(PEM_CONTENT, port=0) + server.start() + port = server.server_address[1] + + # Server is responsive + resp = urllib.request.urlopen(f"http://127.0.0.1:{port}/") + assert resp.status == 200 + + server.stop() + + # Server is no longer responsive + with pytest.raises(urllib.error.URLError): + urllib.request.urlopen(f"http://127.0.0.1:{port}/", timeout=1) From 892f93ad69dd41ff5ef8be9179780ea05127ae39 Mon Sep 17 00:00:00 2001 From: Sean McLellan Date: Mon, 2 Feb 2026 22:59:55 -0500 Subject: [PATCH 2/5] chore: add images and gitignore .DS_Store Co-Authored-By: Claude Opus 4.5 --- .gitignore | 1 + images/tescmd_serve.png | Bin 0 -> 204171 bytes images/tescmd_waypoints.png | Bin 0 -> 8447 bytes 3 files changed, 1 insertion(+) create mode 100644 images/tescmd_serve.png create mode 100644 images/tescmd_waypoints.png diff --git a/.gitignore b/.gitignore index 1f38c45..fde996b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.DS_Store __pycache__/ *.pyc *.egg-info/ diff --git a/images/tescmd_serve.png b/images/tescmd_serve.png new file mode 100644 index 0000000000000000000000000000000000000000..bde91734ef78a318b6c53da42d6a3dd8609c452c GIT binary patch literal 204171 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

AH`Oz7bKXlcx!HxC1QhsYBgPF6AQ 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 )C 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%0A$nD4EBz8w8U?ME(s2!A}qAf$X)=j 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

Itsn}; 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ν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 literal 0 HcmV?d00001 diff --git a/images/tescmd_waypoints.png b/images/tescmd_waypoints.png new file mode 100644 index 0000000000000000000000000000000000000000..f73d1bef72e4274792ee64376492627ce7803e95 GIT binary patch literal 8447 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 Date: Mon, 2 Feb 2026 23:36:44 -0500 Subject: [PATCH 3/5] chore: bump version to 0.4.0 Co-Authored-By: Claude Opus 4.5 --- CHANGELOG.md | 2 +- pyproject.toml | 2 +- src/tescmd/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c332a6..772bfc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.3.3] - 2026-02-02 +## [0.4.0] - 2026-02-02 ### Added diff --git a/pyproject.toml b/pyproject.toml index d8f1b3c..1695ff7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "tescmd" -version = "0.3.3" +version = "0.4.0" description = "A Python CLI for querying and controlling Tesla vehicles via the Fleet API" readme = "README.md" license = "MIT" diff --git a/src/tescmd/__init__.py b/src/tescmd/__init__.py index d8c5ba4..8f02c5a 100644 --- a/src/tescmd/__init__.py +++ b/src/tescmd/__init__.py @@ -1,3 +1,3 @@ """tescmd — A Python CLI for querying and controlling Tesla vehicles via the Fleet API.""" -__version__ = "0.3.3" +__version__ = "0.4.0" From a522252f6613b2ef73ed0767ef05019fb737c31b Mon Sep 17 00:00:00 2001 From: Sean McLellan Date: Tue, 3 Feb 2026 10:38:32 -0500 Subject: [PATCH 4/5] feat: add screenshots to README, fix system.run dispatcher for bot compat - Add tescmd_serve.png, tescmd_waypoints.png, tescmd_mcp.png to README - Accept both 'method' and 'command' keys in system.run dispatcher - Normalize list-typed method values from gateway bots - Increase footer logo width Co-Authored-By: Claude Opus 4.5 --- README.md | 14 +++++++++++++- images/tescmd_mcp.png | Bin 0 -> 82639 bytes src/tescmd/openclaw/dispatcher.py | 11 +++++++++-- tests/openclaw/test_dispatcher.py | 24 ++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 images/tescmd_mcp.png diff --git a/README.md b/README.md index 0c1a31b..d5e7dc5 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,10 @@ Check which backend is active with `tescmd status` — the output includes a `To Use `tescmd --help` for detailed usage on any command group. For API endpoints not yet covered by a dedicated command, use `raw get` or `raw post` as an escape hatch. +

+ tescmd nav waypoints — multi-stop route +

+ ### Global Flags These flags can be placed at the root level or after any subcommand: @@ -356,6 +360,10 @@ tescmd serve 5YJ3... --legacy-dashboard **Requires Tailscale** with Funnel enabled. The serve command starts a local WebSocket server, exposes it via Tailscale Funnel (handles TLS + NAT traversal), configures Tesla to push data to it, and renders a full-screen TUI with live telemetry data, server info (MCP URL, tunnel, sinks), unit conversion, and connection status. Press `q` to quit. +

+ tescmd serve — live TUI dashboard +

+ By default, telemetry sessions write a wide-format CSV log to `~/.config/tescmd/logs/` with one row per frame and one column per subscribed field. Disable with `--no-log`. `tescmd vehicle telemetry stream` is an alias for `tescmd serve --no-mcp`. @@ -404,6 +412,10 @@ See [docs/vehicle-command-protocol.md](docs/vehicle-command-protocol.md) for the tescmd is designed for use by AI agents and automation platforms. Agents like [Claude Code](https://github.com/anthropics/claude-code), Claude Desktop, and other LLM-powered tools can invoke tescmd commands, parse the structured JSON output, and act on your behalf. +

+ tescmd MCP server — Claude Desktop integration +

+ **Why tescmd works well as an agent tool:** - **Structured JSON output** — piped/non-TTY mode automatically emits parseable JSON with consistent schema @@ -486,5 +498,5 @@ See [CHANGELOG.md](CHANGELOG.md) for release history. MIT

- tescmd logo + tescmd logo

diff --git a/images/tescmd_mcp.png b/images/tescmd_mcp.png new file mode 100644 index 0000000000000000000000000000000000000000..4b265c9f27a0317894fb86ebd968666ef05a68bc GIT binary patch literal 82639 zcmeEtWmH|uvMw4dc(4Qy?(P=cT^8={?i$?PU4pwiBuMb!F2P+E?r&u$`<%P?`~7~s z%a~&Tb9PsCbysy)e^nJGFDr%!{~jI;3=C01Tv!nd4AKA$4B`;>4d{&;_g(@R7<`Sn zkdVBDkPxA~gRP0Vl`$BYcvw;jjIz=qX7EJkK@?`FU*eApLJ>mHFQKzBQE`YsGy_7y z1r$^bFMESYYs3wC~7znZQU8ri4G?k6(p;r{Fq;_VeQ+gaA`s%v2^~ChURHXJCI><+~zU z3|(c+uDU+!epO4@0V;BW34p^6+9`1k!RCm1#eaNP1n$S>-`B`A6*%pMrSFb9MfmmO zG%ou{!Za@PH?y>~%veIKz((+%gheX_TreXg5?Q&uJ|gGu)0S!QEneVX7h$>L#V_&+Bv9~+F32#vypK#M)g}HJnbns-`asnK@!KKua_Jz&7|aEdvia8GI8mF zZv{m~wntyYr7x0e2nQy7qwiPJo^YbEJcGv>$!AF|qLmT?)E!5S+dZTXN$&4Q?P=Dv z1#;7Km@QS}nP!!5r2_C(T2`o?o& zuSI;5*gn>Xp2t7r@~Q1K-celk+2+hE*~mu2E3|kUcj5B+5^D55y%aTmt%XOe`94!# zqKjcny)K!7QXqap3K-j`H2s;>aXL1{(Z|Mb`z`u{4!Gl6FF%-ENVyI8fLKOcsPP(w z_qH-iS0-ADfoa4(T3+w7!d5u&vq(2mY4S5N(lx;uynRgxVq6>2JmDs7MhBbr!SvK3 z!L0dqwc&b7-+~z-jSf0Ve80T~YcO@_%fDL0_IQB&{6sAd>7ENU|$4nstURsn^Jf*kJ;YvkvwhyEQBS`TXhL9&;~4odm+)&_~$8`EA9JB$^m)m$td zC`di*4k*J7$W%W=>f?Dg*W&mI8RTRK@?l_EdsiK z*f%3RAVYtur1ZyK_%B$@k@tT62Ic_X3f4LF4`DJrRsA-D_&scT%Qd_UM8_ORAxd9y zejeMuaIn!Kri3iVa0j>hwMTsz@cx=`e(2`d!OaIx-?O(7anX2pdAEGGcLx{n_EU(S z)H%5wTv}jOU_qejSK6-(`T-PDGar}Gg+k%_WcttycrV~DP%rSpgr`UrV<9BG6xm7x zodTSqSi~QslRtil*Zw{-WB^2Rz-W!~%6W#d!WVyg`*+ zS>1QhXz`@3v2Na2Y}?ME&B4Y&t5vJ@qeIGea0x{j=4{Y#p3N8hBAq;>5yPG9ZFL|V z5E?kR)4o0UZu?y_MIdE`0)-MsF@u26q?0*~2AVbb2}xzljj{hB#4f~IfHlG)){4%# zf_+|o0hPph5T!&!bryc6yYRqB!2F>s4som$> zUty?f!t*3!hc%KuQe{zXm06VGmkG{3m!&A{mQK!O&Y;bN&y1Hxl_|~Q&DY(*GlTmTJmk{{6J-{LQRR zv3BvZKuGSlA7S&0#nXA03TkrfTCU|b^{1iP`S?=wQq{6&aeN}{3cig#K`+{n72w|p zCpY{)H4|BxnPb}mOx#9b+3J`!Kj1MOst2kY)r;4Z7$|fzE~c7i+Gv^bmh^f09Qb1Z+U>_XZ}ErkmI-R*kuG%NY=;+ zNSgSn=r@cjtQ`zqga-C~51|=x!*NgFlPR>60m{ZRcTCO3o+*g*&05*b9kxMh;jGkE z)yg_)4Zszu1C?Wy;(W%*pOcM~30A%?(4IWO7)toe1NJ|Uw>q}D>?2wVcszB8&fF`! zt@Mw@Grd=`TPEEG?>6t$ys&&IUu6PJzvczTguR5Rgegh-}+?4&ji zw1X_g413SJ=VO(vWvz>?LYh2HGdmJHVZevxC2QU*$Oo7gWbI{|rjzv>+MEpEf}V!l z7Ci@U)5j=+F&~tL&@3Bk0AK;0GR?Elft|;PB5YQ>kGp`&6PSBPzqF=vLKMm6ocMuByY# zhaHK=(DKiDuUxxHb=p7we4`=M3uh&keYeX^+sQREJ2rc|t;VX=VZ*W2(-^RMfUdmi zi{@OjMw@k;rf2d^X7`EP<0jrbx7u0V>Dux2(a8bAN&9?vhwldd?b`R}!be;DLjF{@ z)MwaBir3oFtxa+&IVvCayH5Qci;b1sBfY)pjbh{w4k7wi@n=H^j$51^^$W-AJ=TxA zA5Fzp#oBdgJQ<%9?;JK&)^h56le_19y&vd{ySWC52ZGl_*Q7gFUt|xoXCFz=mIomR za=gS}7%muJ6Q+s}Cn6{Cbia1<RF7)zjdVnQZU82)5qw zdSb%AnmAeV5(8x9359GOj0xH380Z*?`QQl&33(ihOt=(PqLzOlRw0O3%p2$w|+^M9;)T3u-~@=x*br??!9m`0+Q9zv&1YI~qEe+c}xr+7SMt zt8ZZI?8Hk<{EN|letwVB*v%ew+p4 zrQDA`2s5?cdAsI$*6Avo?j$TA0HrJdjc~38jv)g5=^()Gs%om8@K*yw+Vh?mLLg_P>Tr=r2wtAVL%GU(fMxiE@bt82>eF0f;zh zzi+}D7!n5PF@c%pCb^<*U6LR>x9yBoo{HXlheralRHuZZdh?u04vXBjZT`MZlZkB-VE7d{@n zs6u7n?{`l9XO4w2{X>umhC4lPKbCVO z3;iNTN8}eo_7w4fJ^JqMuB8yE+8}S`;|>Q}SM@a(V;{!4fIMxVI|4@kE-vRHi3oD3!kMN4{ z{CKf`e-foB;4fZ&e(kShil`BOpZaM2FN2@~nNLs<=5+kOOj7^~1*H4nC~46D*8RXB zknPv}Fwpq7numh@GF&j?`oCvfElpr7owd-T(Sp7)T%}Ao*}-^36o3PbfEmkXtsw^R zQ5B_BDvU+KTav0X8mdrwA^}f94VBzt5RJr+XEK)TB_}n21=%Zh^yuGvmX11vt;;c? z^^$0$;&kDVWjwEE6QT9Vba8Vcr6$*RcT;lU_Bbu#`x)f;3Wps@-~l=0=S<|Yu*fu~ z;n(1|$qnYyf$Y>0**s}}V)?>h0oy8hg6@<`WXC)KMpyB2firJRMMCy;Ox?xyj*6sG z;!)RqXkTrf#azyoh`5_xsXvPOkYiA*+L_mX_QW(Ch@^ys@uGo2K4o@Z?Y52adbuwS zxJ*1pCG0)F*joK#*&C`*KG^r2Jr5XwI-_x#!I&Iaucd?ArAEu4d0k}3^ivh-SXxas z@R+NEY3Nd&iR?4+lyO^V&eyUm*iyvt!}r;qSvz>z)k5|;oQ`eEm8vQ)I$c^^t_M^R zn6$~jo#CN|(r$@I7i;agtBvgbD(&{~%eJmbwT8Y)?^!KJuylQlD~2K8cx~6UiKYbb z7Dx##ApK|U=265`8S-l3v)sT%Mp;=+N=Z=ep%jZ{X?4A?gS1+`L@egYXw|B;Sd;vS zZ?4(bho_1nqKeE{9}pct=KH-`kAZZqN=1pydW~k?43Eh3_B1H~8Xopt^QX_jP>xt~ zA;Dm@26=gXEPp2B93F*2w=1&(@RFh@XbM-M#4%rIjBu2ApUGq#-(a&!)x&v=q1j;9 zh09Z;=JRM>XEd5h)N@TD5&u1vvA}rZG^@^j%Y&Kn>gr0j-g=?NpAv@M>tTp|_ikG5 zSsSL_Xvhl4>9ZN=X8s_@heMdbR>^+<^l${&`ihmCyQjN5nnrroIWM-^A7u&@;$|`) zen)6NSD|ivI78{6_o~x~OJipiWw};SI-1HSv%$pbJ_S{(P~hRm6Yx0IC}pbta+_0U z|BVah{6Y+kTD1a90^!|v`jV|Yp~syGW2r)wa(1iv$(zOc7l!p$t)`h7f=GGah63q> zDaB3?XU9mM<9>B-(EdhsveXn8Pb&4Dz=HRaufUIim*>YNQ{(8#Td8E4_=Q?Sm{v?h zU#R&v)CpMT{HG$Fy@WMID5qBIUc4*AeA0%{?4S*F1cso4-uJmxxyg2!N*9@c44dG_ zptfACx{5}pgJGf8##b)<-Ox^o?f~{UcJl^=%}oH?GtUG0V!LU|Xb!s=gA0Vz^q1<9 zbzj;9p~J82mUGgSIvoH%5^R2KLt7V*kuQPja5k%Lq!f*w@$(vMhnRE^v;ONvMY^bShuuRJCdt57HhF{n^YiRI?p|=E`~jAbr=F8H zTWf9?n`w*(Cnuk)*(7r4I`v|*Nr`61Bn`cZ1^Qe+b|pnG*@AQ>PNBdz88l}H3+YtY zXUTrr-m8ua?XL7H`Fn4;=PIx6G8wmA3x0UBI;G-Iu)yDvyF8*_?u?Kt`DPH?H)?-| zL5>@HeKLG!KoOLz*WvxT3VZLm%yxf#*}IwdSX|?$R~@<8%Img~d%WZW7OBYjtzY}!T%bs|E)W2w2komv7t0xG>KG7(85%F zV$=UK%ub!g1sMRXbphCn^_`4K@hZ8A3x}L5vqLY?;z@d@M40(&Rx}2nQI`1B< z-y0ueQ^xXl%f7gz4qX~t0EM9?nc$6T|{YTHzsjHc#t9OpX(irAvrr6!+e z4<-Bx)zn1ayUD>O;0dzeVNC22O7_ii3c-}UxHAHe>fuZ&ZPa_#nOx=*qyF$DGR<>8 z5p>L@Q{J^&3}{QE7l@z$yrZT-k6(xVP};}hj@8Qc(VXrctu~MA^zE^X!Q{7dMOp1G z5|1}{{Sy-tnf$J&nO@gBDZt0G>75EbDS5Ynm>fdLH$DbcLh8Z{(GG352<9D5sCi~_ z#7y1;Z#BE3k!tqOU7D-n2|q!!dOtQ5kbD~Vm)RPKDpaq>tn>Ny#?d>QuB8HSk)naJ zu2gizTYU0%tUffd{Gk5!r`R)TlBnGATBlfS+qv)p$kRahK>vm8&#fZ`ZJ|cLAaSb1 zGWO~I5(jz@y>X#JyL}kok4d9tlz(jv=w|G(xIS41`FTn!?WPm>UgC!=hr1==3)(90 zxtxKn%>nIdwfQ2-X%wa_gg=41-CLm-O)%-S07-OmO6)DrO9&P8u!ug_hboWLO@$r^lFjBSnhR}X6R8l1 z*d5SlHXc!>R`;90I*Cb{d;!Clrq?h1fJs27)uN~LH5Mz8l_J5ewGqqIaFy=_(h!clAP`R`MRhaL`~29YD4c^DTJMrB zEc=o>mOw7Yg7EIHk|VF`JII#;9Ta>%9T|TvY#`5BwPV@bh!yI4&mqgo$VPIL&}v|r zO7)j2yNJRHjlO}%Ygf~Hs=cGf9xj#~A0IhLuR*MqF@0ObwGQ_&5-e33alqP#nRZtK z9JVX!RdP0Y21;JXo5&swHg#3Rij$J|El}B?i&-Cl@&f2pl*{F|Q zc&hPr{i*xIRcRVv_j}s6%mC;D-&db0ag2_{QpI#IgccNjAD*LR2H)VKu2c?gG=Si8 zgE=XIH%m`T5}VD8xb|e1OX5tX=j4kU&Iz*CP|XZt-YnxdB*Hjop2tlP2d;laI#Uz0 z%ymxIHEIO#rzjNoBw{}h1Ss^>1u@2RyV51Rs|x9tf?Bx@Vobije-WR_)9pr%RD{LS z0Hj2fNRdL()dMmq@)2-};3`byk83tiG-?Nu3ib=I=NATdDzfq@k9r|r*JE!^xWqU1 zm{@L^W#mDQdjQi zQUg5+z~LJyLM6DIp`0qpL`u9c;62Rka*PdeF%08!rFTZ1Le^=9lIluSb6O$Hv z1iNj0RHx$B_0v#*p4Ly15ezETCOOP)jzX8UZeM9tG>(^Mx)Q=4GZghGRU3z34 zQGc{poX`PwrNdq6rkqK>*v1;&&anSdVj0-2CcfDhwv*-BHX@Og`Y8@RB9THtFq_Yl zN+{S%zn$H->-A;`;7-&c`g8|ZQ4P*lyKd4KY8y$7r8-d?Qwq`pne-KR2N-cn?@vB_ zYinf0+0JwgIm^xvh^6ZFCVrY5b&H*z?DZ35J<#p+3y&uBu(4p&A8)>X>M%Xu0#bX1Cq-46|jZQMp<8*b-hA4o(K-pa!2e z5Io)zwmLj$CRDWT{pg{cYy2@Y)`pw|`_^B8fHOyv#vUmS5ta4b^HqL83dP3<;+0bx zwL#l1JpGDRzz@H#fzNu45K)YP2*v@u($D>+c5z?JI-ZFZKHQxD6rxlvk&mU4$>Ng1 zXc&A?XcFHS^?|*|84!a?Gx+LQcd|rejKlFjge~xi+v_e=qS<@dWhkCCVx!aM1|mh~ zsMBbCDeN(mH$wiMT!}$=lM?Z->j>(%xYob#+J&fop2QWpc8}an`!$rVul|rGUl*EW zKBRHjE2bU=z!BVhAtd~oOsiR_KK%u_(aQZX0DN?Q?A1vZu1HrD8;_ob+(2{o7o@mxmdgsr*GwGg2T>z@s!4d`{-tcEjrJ)^@?+3k7;$;^gw ze4mZu31-CYtIffn0OESI0;%3mk`h@|NwuPRtBWY#xUr(jy5oG=!5;qf`I+c)=N$7lW$RX@q0~Cj8XG0xVx{DPu0ChPFM(GY1EQI z7Z2rhc3ZN~Fy3^`EV0r{4dzCPHZrK#-ugdIw*2o3JJ+u!(8O8^*Xe5y3MIJs^ugXF@W?eMuZOUKda`bgL+AU_`hf?UPSq5SO^fPZ0})8S?#x#xe3J&^cIFK6d2bkY z$h&+E3>(O@T3wpm)s}0Q4s1vAPk3_|Z59bw%;7d|3dVa?P3jf`?q=8y6^O-1x7l{# z=m$t_XzRlo#)P?)&snnuY+By~N9d)~LD%ro52jcWRX?YH4sbebyEZYQaN19P?FaTg zdcQ4(w{Z9GF!Q#?IqH3KM;M)7^?drMzNA~vkM!H5o4QrX*&O!I^;Pr+Qe8Fm`3;+! z+ed<VK>+K zuvyQoU=pDY8&>Oh7aH`3*J-WC1T&n6&3HU$B+N%)Anp({>8H<>8eNwq1w2IEoqE0 z|G6k&!)kS(b_d`EYvPjidlqg4hl+>Hz40Ik^i<*T9z<3hW}Ng!?S1}Ea0ZVMLzw7O zu}}-q;Y-1DAGE7;j1`bH&i6jQ{;_9tWJGEGb%!GzPjFo5zV021>3z`7L&OZ${WtHE zzKa6j3yin*-pr%wPk|Fdy!Y-lDhY>*f zxm?I>$y|3Z#XFNkVlzd%k2-@NqgJiS62SP^(eA%5N|^tyc7Gt~1nvWPU`5EX`p>WZ zP?)rmGvq%S4vuz%ez<|V%~!4F`+WZT1Hx6cZ$BM7*#B$cAMoZEa%ur#d(g#voR*Ka zdzc;C0=yu0xT5?zKVPE2yUAoCTg?Lg=E$w6JtXPB@V);lETJU9+6Aleua2;wvZ+2M<;0zhUE_!~*kuc0aeqKpZDy!L-o}^o#2x{>%P1jDB8*wt-)( z)@TZ#|1Fo97}!}`{_j2g4&Cl{U6{(i3+)fsNf;07=4NNZzjKn(G!8*V@vc4_bmx+bv&y-`7J`6ETPNsLYxj6!Y{`DYEdBk zSfnJh)1n~$`ZpRJItRtWV<5TGVxRB)Vezj}0&EzV59EdB{;43uDIdgw&u@{%$v3vP zK58LGI~~)+f`)}b=(o=I^hVL_e}pDdP(tyzF=-9yh$aLmWoVfJ99)4~CI#80w+(3&`Lut$>3 z{s?LxpIT)c&tGAJ$*o^ef_m`^=m_FpqJ@4$`6&8UrHu#r3{EK2gGw)Y5fmU84Kn== zfQu6^9#UE$1PPav;J1abN4Km#e_` zz5z*nUpgH2Zmc=o4SY9cL{Wl-%<=1lv(ef-j3Gb{b{ls$ZZxz|6qw?nZ+KgA-p z#ZYmAVA%={znx&ALN$DWPQKIoIYhs6)%c(`RH4NVTq27rsV@>$$!5LvBPj5e$Z9#) zowzrvAf(N@i#Hpz_uEAv&iR$jVB+)o;>}#E-DMC($r;R(ARLA+waUf1eRINHcTcbf zuLRLp(mM<-GMbjjD0oEF8d5@8t2=biz$)GFoCjGoHep z0=l~K`4M9#+n>CX#YPLf#*ruuH_H!%+Zj~ohRxnnlfaGoR?|dZaRLHJGmclAbzdGl zRRFfm>JLmsQj%#?q()Ev>P7-@6(T9ho#EtUb`7>9ev^p=&~?1?>Y>$QwEHT! z7VNiIAdh-3KVL8*m=qsI94N5H)4KC;edKVyq3?cop7-KO(rmMe#@s5ED37%7E?>nlgnmKbyZp)f-Ft85x}Z;6lv?%W;&8AVliKaspnO| zN3r~Z)9dK;{D62;4X>y)$f+gJ3xL?#T(mEUbyVwOlk;CM7dNt*V}#k=}m5AxW?<<#ioR;H8Kz?qgd{F zd$WI}1y|rlN=-|VWYW}JO3F1mtT!lF63vd+?(VS1aNTA1GygSP)~i?GYtqG=+-(-)A( zt@OAZ>Yf}=E=Ls-gDd3ljAv$pIaG0hKOXP!tMt>lyX<*)v!Olq2dL4b@$J zf4RdrkzH`a^E>?rr4d$mZ0D+^f5F_k7Ma{|z{vy{(U+#u+Y7ZFDhstcs!0+vQ(1X% zgh(ozxggpaKnhi7vnsk+V_(lXK+rOvQm(Oh%&Xbxk)?1BLee3Qef{E9<;hP#`{iWP z21Y!wyF*su3>*A>>GYmcK-QeJa)mVGPuVy@^2NRJw4sM1nfLFhdoo)QqNo+0J)E63 zvj<4z3d4=IM6aDl@1tq7u8Qv~R2%b?RAMKX4x3Um$n7IA=@Tk7o2B}J#?Arhi-q5e zU?hnXQKT!dXtc_9$d&0>t?%_55Q0!7PjcGcBSX{3WotDZ-N4vy4aAXE&oYM(9djZ? zWf*;X<18JIfJOh_cyg^RQSGv~@V-rUJaZO0p_Q8JAh<%aoAJ9aLBb>+w+pA0kodFz z6O}53Y2p<$LUL+Xm3&?#RZp6oL@v$1yf2D$p+eI&@=4GUu2%mKx+|%8k}#u#sqRQc zt$bkxN`;P+R;-iHbqG7=2X)<1KxmOw>OgPr+8Mm+vYV5a=a6msMhbz89*#O}wjYL( z%ZrY?M+>dFJM-*YSkU>=v6gLw{)T{o8nX>Et&lIL5;OUcPd^3kZ-GfBbkxZysyp!b4DyYE_U1pPtGDk$!opU`D zZ%ZI=f(7+^-3A<=C-|48XXRCAhj)x0qw(srv=c6lo9BXJ1`h`!y!1+I2Pkb|57OXl zaNpPvRPZf`h4sfJQO_T23V4)iwg=xM3gBQX$p>sWgqdFU^a?2oVd+j4A+}+SE9mA= zye~xyTyHe?t%}WRze(>WqLBn_?l%v&19FXvnQnJg5hmh34o0aIS)~P-%q}dAxSX!I zOT=@8X?%u1Fna7LMORHqq0_WEbk-oBPh&EED-ZKXzc-%EjP4FVA_VwPlq3jBn-Hy8 zH1@7FZ4EwmSFhJ7RPr^B2BMzOX_pmJ*>PK)W}32g-#&I2DuqjvO}y%DLM^shHY>>5 zJULGTt{6(WyzWwKfkjF|z^ltJ*)A`474Vgdr_Yjk1Le_(NTS(R6 ztZSGG%Nl^jwMXt8x+TV17w+-SSAJO4QN|{QxDaR}%VgzjQj|{ z4{cLUTTdjb_%n0aAmahm;B(*g2Jq-lDLdYW<>Yis=9d!gBhrWx@iLDO5HVf7B2tjm z*Tw>$TH8EM(w#i&D+?FmwXi-vRqF8Z>C}1kTPVS7XYgvON>pPW)VC2D((iZoc!ymi zb2-D~FuTNCSMbg5VQg8q(}@GR)rp=}^;dM;U8bLI2%jPFMlKS;L2B)X7Af+@d!iHU zDN(ZIc;c-vN3QFL8LpLy+2y=M*cL@H z-8^0`_L6S7|IuZEU#Xa`Xf*HGXEE%9WF0y>y(1&ul0_k4(K568J3~}4-=&C>zfJDZ z0KX+Lg}@rvfN~eSpeUyvAypx&Pf;#agnL)({fM0YY-%}Y!(d)-Izc9t{A3=#JGX&y zu{FqS<1yDcdF-;Ll@LTvn0@Y*y*79X{7AI|P+PHVos+8dNg`0t^Z1UE(;b+I zJCkHPgOO0b#YH)mhsj34WA1e6698q;3On#=`I!T@if%k0RsglOuM08nAm(|AfOWA@)#G;P2cG70!W7Rq3h^KYX~#+mnzXL>5LQoGF9a z)iG7zbGLiGy5Af_8?x5RVXZJ$v?U9q^(_Ez9B>mMCukmEFfF7Q?n9W8xqdJoiG%x% z)RM~A5rqD|S(qB@7ss#b(LnXNCi+c9QzN@( z3(b4&Rl%Nk@NNtyl}zl}-q}aaM&Nhue=D;Q_v1+?`hDWDZ-7U zz5_fKoo6o;_FQ&PfW&)hZPwFt%Ca<;d;y zuJ#qqoslzNBfOAhd_h~wd`pFauafHO)|=ak{xWRt@WfD;`EsR^mbkA#J-8c;PsfjA zZNV3z9#E=e`eF8DH}LFJd_Kp;?(R6{QWoc`i_oDk@S+hR>b8hp(tDh*?ZYQ;M0EiCDljWYxJh<9wk4>2?= z$1XKmEL2!VpVsoj%yU|0hkP}&O$#eW zU++n9SP2=zA-lFy`^tYFC`Cr&P_v=;Rki&~3LIO2-v{Q`R7+j0l4wJ5d1!{h51>z_iu{oJ+ru=}M*1H}mxCq>vK2QeC1m-aq4cdX_yBhL0 zswfmWfYs&!^a`VKlG@X;x)q|Iz+m1_->E_Qu3zx^W72Y~v|VROX-b07+z0J)8MZo} zTU=CcV+98dkJ)B8pB3ou3*eoiWXeLf9T#!RG#Vti0r{d|G! z$&YFVeHuSLAZ+ffJy@yfYWxt%xy$*zYPhAdXBI1*oMv#uNv^Q72LarB9VaNMhvtZv;Tc9zs8ZvuiUdEPVEJ5}b{Z zTXED*c#HR=EM>^+e&ym8XE(H9qG(#m;h^K zRZH_Apz&MuE7o@;j}%F&IDr1aZL4nsz75CNUyw>*DuT&pX^;Iy&xF9>&qWAhKo>X* zzlb(21q{wS8!t0{VzRHsjA^0*!U7?dZ6Tx_pPa_aRECf6dxgJLa5Y+57_}}RM!U+6 z09kj4>+ljo4ri&-x`IBCEC~Gwrb%N?gH517H@FV^r6{hBh77(n&1ed|`ccd6XSp&% zZ^_(SWY1O_M*@Y!UJF*SQ{4C}C`x_~Ki!YzxtnB02_1v?b&<5Rf0aQoER@YUYzuXm z6{d&^^~mP1U+M@c4}LS%>-LRUJc%l4Ccjbcaf4sVOWb&8Ze0lA%DCH$-W?=AdhU5( zC4KimEFkk1&vPx{)+vcfeTKpa6lA*ud*irE7t7PO5m4(7_KEW9797CzuZ6rz=DqZ{WJ6=|oKdqb(ES&zPb>dtU%-tXtTxI7U7! zxJI9je>7LsV**M#4(glYCpIBS$zxQ4EH6U%4{Rm;7oHWU<4qYUdbdN@*K7PW`FVic>;*WPYqUys;eQk{&jX+PcCkVgPhd7EyoRGlC21!5llA z0X4kq#Sl+<#-S9}_40&-1*B{A)P?L~sLDE^FpsANY64YhGjRHm!{Go6wgVxa#H!!e zt(Va5T#DEAt^G5M(W4UgDkTp~a(taVkkL4fV(t-=_xZl2Pu1&Tu@do4KQk5Mb@3xr zGx`x4alEKA`E1&@6(6UMX0pl&#m&byx4W=NZW^DAv|eIngwa+QU+&T~&9Pl!RA`N` zEP#`F^};36QS5^9%$OcWW7j*}BO=SO@<~pW`iq6yRR@4I#v^R^nnHD;aHB*e4px-0 z^Ykul+0|UUi?wEY?d?&^P;c~6>><9^vmO4abB#EWG)H!63Z+}CvlXIV0LnwC4G48& z-D&@B!WbD^m&MCmel60MhZBPB=T~DWvlO~hks^{vCnEQgtp9&%u&wMrOu56iIr1c+Qz;#em(0bKsdK^R^F;D9I z7SEi;C+q;eBbEIeRQj0SKEGmB0yAVNAH|}rUQgb}ELn#oH-=n8BqxBXJnW#}gd>ne zswVP?nG14ZX|gx)dtCka3C|7`B}9|{o(Wf>Uo#qHV(4Y(PJ~?Hr+HQbPlq=iGGhZ6 zS!#3_>iWPbxs2&_CN6#-e5)aTEx5K3^-Pnu*o>yAy&)Ld18rCANK(TW*hnAH4wEc1 z!=6ueH-30p!dr8!4}5sM;>XpFK6l(71T5JcJ?o?D_?cj%PP{z~^3t>SO*6zZg!Jsy zxw=K@Ml3P4Gh_nI&pJDlpuH;F!`nG#z{Afl25+(eU8qPTIGvhZ4>z~qyLQZ}GiPDm zZzTwS4q%U8RVcgcP?@$y_5_$#r616HbQ)J$9n)@gTN#7RW}Sowg$Z#w zwxR?f#$kILn%pj!E(LEA-b-hrJVe{1ijnEVX3)($hz zCT3$YSrBCPp9&cP!kiz|7Wt4RJKrQpnNFqwouX?oM&cU{Sg*JsH zK%vTfqvqPFe}xzoepTL;taw&%|1(x8kO(Rqqxt`rMNs7wA0HnYyUj|?>URDNs6;Im zlyD-M$ziXh{8_CYKu=F^zE~GDQ>ye>q{^UHmC58d;1ou{za~XX&XP75fu+XfvQG%Y z7vW`qYGg9zm1pf|H(thv-@^Yy^1$Fa(xG^C$1G}9f0D+6@{P@>+mb+8%xYfV-lXcl z?aiO4N^G`k#Gvfecu$>GzG^S5Hb~;YR5MG8y9fmph?& zO9I&}HW^Sl(}Q9E#%6Sk`9m)2OOw*v~ zl6cxwkxcTsFFzA;y@}3$)MGLdq^Xk?LX(+B5($4*1a;h9?5yf^?sVSr$GV&>Nu7a` za0KrifXj)L@gx!kDGeQ}l^TkL;&FN-YmJu8$Lrm?pnNqHtMq*zBBd{*z=036)?*zm zCv8q%^$GVrPj~jq_n~~Lj9W3G<4jujCu^O9E8VX>{%WFd2rKOVAMRcr&oY!uC$bT} z!q)$U`G1xDfq|my;C8|>^v{pC82tSFIE?iTD%C0Y;2w|q_TcO4n%oE9YB z@OUkVwZKBGHXcb)r$*oL7pe_82r77H$+&e^emy=4VtJ#^e=^LV^-QuGGpX{NE5 z*ExAy%G?9XtGCxWAKT7KqP=bw$?F{VWZf^vnx%Q+K_x_4>mE{6DrF@1K&+Z;VW81a zp>=yxJgXF@L?%gC#rzPkWRl;~v*8mJtA%okhx1W*b4+ugREDf-g%%~qFHZ`DLm#Tq z*Ok0E<}}*AT_5`SEyU=Ue#>aF&bXT>$!QLT(4W;?fQuEKxEof zzKG)%-F%J0$5Qoz;0EOpm8kcPk&mY0R&}NmbiWGpt`BFGcJh;N^a-4ssjHs5<;Y|* zX)@SsF)7Ljd=_-myVhn{N`)^B>h!nB?m-!CCmCkZ?XI4hpc)8+=omb1I)bYc)99ab zVct4wv*B}BeMFfs1J$u?pY2eOavr&Y-G{K?+h_fKc#&*q zKrm`+c6cU5hvDCtJ_C<94rbe%WjEJhU?(Xh^U?I4>yL<59(_DMsd#7rCeSfBA zs-B{rkfhJ)(|x-4UVE+8rDzMsq{fnuWj96#rb?gF`sNou7YK|<(dx(;{{FREcZ-Nc zXBmc!_uiBf>$mQF>%63g@T4%xwZ6d7`XPx5olYg0RI#`sS?c*5;@;!93~wKA^5ZK> zeNKnZ5;)AJM2k&MOue#~B1NKZKvTto8(C*N^-wD zd9?mnZ+9|A#{Tg%hg39yFNxKC5nV+icCpc6zX!yXp{RQyQszqJfKGUGb<2lIqe?{R zHZlxNcKv%je87dWx>&i4vHJmY@YsQO2K9{lS9audXt^IKaGS;Str0;SOFXhpljP;ibYiF!kvg8(BvgvK_3;e6;qt=m8g*9ld3~Q8 zvt@IoQ|Cy%8QC2645>78Dr!uxzg%yZuA7YIz<>~Nw z3!wA8HQr+egXyPo?PkM_w~I(PtWn8q)~|E3>PEo2}&k#R?WlQ@-bsYuavoU^jy4ekb zLAUt6vJKp2mR_Y^&*K9HiRW**q}_&+GVyA%p)P|=0e{2YsLsX!*^Wm4^nbRiKjrUG z!+=Oj8j#LcDtjea=ybFYn?9*-e|4mF)(mzm`4UqG>{86Ue3|ql_=n%{Xt~anYsRR- z5C|XbiJPqNKWmgumuOLRPTKkOGUV8<3Y@NFmRCZ|qQY>zw;fqFm#a}_Sa1Bj=(OD5 ze0v2w#VGL({@!4U(C&Qa)qafh(K6OTlZWr)18oghL9xfayD&xP*%wA7Eu$rqy^QnY zH6CO%(o`z$Yjm)XYGnVkoye-$VA~$Y_VQ?{nL@{%Wg?~N<5}aieb)f#=ZAc~jxR;b zhS7o1Rm;n%!nBHt{J>0fj4d&mor+A^hx8He}{l1;} zE309D++dFIgp`R zL2$?odZca8E!pag*2ufNOs3igyVzXZYS-!1nU<$SD$C2t6ovJStKz1rfaw?=PMnQyR#f5yEVfkbZuMe6fqrg@Bpc>e2ZEN5Jv5%?@JK-Iud zRr-8&WK^sJ?u4vTU3Y~Mo~;Q0HHmz06g-m6$um|%-F89}mDt<_UM=K%^XN8LKu4?H zRg*Fw3aM9kZ@#ql41+#OnDWD)POW73d`Rhi-k0xwuWRM|Q{n1+1;PS6*edhlO1nN3 zcHBEmXxWs#Z484Ry_3Dn6#Qxx-(BQyG*`20w>Kd+&yELZ&gF3Hzy00{M?mA;!9aCp zNH2c=@U7FS;f->^PC2x$olRi*@v~12pQfd+Pr3<9$2q!~l26W5F=Ql;mM+5A0zaAs zcJAJNJDZ}y<)pM#Mi)Ns*c~?saiGssr+q^upYuWqyDsD+qrQ<{U7x0NIJVKS_l{N( z=t0~BGOOby=7vuWwBcp*?5VN?)UAZ(Zm*7ugLjmv6e}VQl|wq(ZU?OwzP~ce-0Zr* zUCTb|i+hh7{%LD~WqoBNQ*eqFxC8uAt^tVq9}Xd}-e)cAAy3^lb^{fwQ>er;&^d;< zzAAYO`~}`+_HGYqpkyc~oG&+g9CUn1=ZmywKz#R8)zuQ~9OCwSkxIGoLc_sT_DsHa z6)ZK}<%XYf6q!^}pE3@1(NqlJCc%C??2GM(GJ00_(Z=aeUtr{F|EeYA*5P|w(N)%3 zNTPd7#q(0l+PMeChA^Aum*z@Uj8Cw^Y-8w2u-#d=H~3^LFDj}=IW-7q1O4*U1C{g<;K(HS_X_9Hf3mbo-b z!T)N^B>?%tI^}(T`t(1cI1s;JR0gcR-qS=$YnDoOG}gh?1e6*pa%6|ciIsN2jj%?_ zAB`w89D3>3JEOe4sq;=oD-`KCSR$4?2z2*y)7?K(z^<4pdl3H6Y*GxctnY$$f&(vrK4qzxR>xCq-<^*9}ra$ccPpx z{Q>^R(g*0_cAjDa9Mx^3o(o(ymKCutL~5>|uXobwN$5-b-UzCMbrT`2!T&YCh`e>- zZNpV7Oo<<$d^m~p@uBH#Z09@Dun&BF+60tDubguw6UW@@A9Nk?q6H4N+jvk^drI7m zwU-{hk-Xv746SH|kHkKa-)@6)>UPAruwD9oz31WB;dmXWKD|cq%Za;M+IT3XmxI~W z`y~MvsZz0$6167ZJ^t-Spmue$EcvWz)%}oPrnMZ0Mj^c;k$X0C3cv5oWPEvjIKnL< z%HKbDRr-C3`WiB+$cp2lo-=sBW!^tARkoZqC;XqIsMQj69#AIdA# zz8Es+UAD^?{>m$B2|8C&dY;%Z@8Ep;{FHU!aTyIGh)xa9FljejLm^ZP?Iv}#-U=p{ zyEj(Ec{40Z`SIhIZ=d90DtKG(+2XN%&N`d zsUAk`3O%til9@U?Z-jx%Ce5YPc9#uH%=MdOG5TUWoEPpgm_O2|X-$UrJ|iv-j~t5V z$oB%mhte^i4ff>xR3)rJXX~)O$?W~@eBIFRa2Tb!W zc$RZDVnt@}?V&@x*2Wq>3Zjuhk3N(*sfQA9&q=nm-mQ6(&yg&wg2c*j?`vb1lK%^3 z7>Nu%eZqNZBvJ_PeZ0&145{|O+zpq{H!`TH4AsLXb2ZDMwZFOvKOjJ*XDq>OKKgoy z@I;K%pzX>c?-RJy1Lrd+wBJcgtPO-NpSh1IsCfYsP2gY{cymlijvZ3;d4E&AT1Q)d z&i_{493?av$C3Mey9J{4Vhb6WL`=+Vk!_o>V?oOgh{!jiY_dZob|_Y3s1lK(7n+%& zXw$r>de?TFk{Jo+MJXC_B+!u+(C*0X4HfaIWa2vwk~FmwQNbz{F|-;V)XhlwzBdvH zehI0|;lT-j*5C9s5ZRW*!JmIff4K6id+{myp?#@awbE_ifd;R8`d-tfb#YR&E@34& zyM-a};DM-_#iMk*f^^-5Zz-ZsAwTfOKIuL~IVq(m1Bg78X#KNG74mqYw*46lbSOwa z@Zng{tTt7jsH%~eSl>DYuZEqY|MR}|P^@GgA70a(20IL+>NWG_)Hu0bDw4eoWoEp! z5Ng5Un1CkmI`!N6OvbV!^!4T8#b$S^gTAV)$Ae3V>zv}1Q!zMf0e zbdKHm*S3778%%EuRkY#A3L16#*O=}Nj}@=*0mMR}N>!D9$ME&N!810+UwjSDs&Mi2 zI(l>dwzpyq;cD$rFp@&z&<+iS))rDp%*sx!Q#YrF_IAJ8AhRX)!_zup_&I34Iqc`f zP^t;|wn-w4Ciz0(Jr?boJ1Xrl?9vw)Aky1D0FMf(k#MqLEU__CF{wHS^XV~BPl?UY zI9dlC1CeYY^brpI7bIEaVW0MA+BnyBU%5AFI2Fo&t9l+zWvt!m)p+S)?b1OkuqB!3 zAW6sOvfgZ{YRU)QZC5m)_E@A?sJ}G+oEjH>Jl&JPD&`&KwJBj1&2t{uemYD#S-Kqk zT!hAo%JBnFJLNCjf(P9`j0nLF$#tdna#=~wC$6Qi$n?bo-G!g|lw@do-x{a#lwazH zrUKudBI09%rQt7dfnff2LQn9|{AR&nKpRH%DP=Z0O=1S7CCj*~eB$WzZ_<%*h+jX8 z@N#iU<|C-!P$~byov|zBg+)Wdm)YgfY;QI$j_Qs)4>G~jux=nCOzI4hy1DV!GEdF^ z2jmmt`9mh=ih14H=oDCCPpY!Hb-F3Rk4_x?f`)E}-uRfuRIB4B`m6PKR9|#+@!xdP z7p<*{W~{upUe>Lxxi1F0+k+p)>C>xu{Kbboby)I>S07V90c`{Xt!` zHX$Rd06C_-4GIylOapDGZ5rPl8HwrkR`z)bbb=;l)JWa;z+5urL}%mPX$lUK&6+w3HmPAr=m?O!!wN zt0%wf0-I}z^4PvPRgjss{*uu0{qt${gX|aK-%YVAaJnb4*ynkjoa-`vst@z4-jFj} zwVgp>+ETe;qb@iLX;i)z_*0hzhz_<$0LUv_CD_f?9}C4=iJM*_s4%@N#l-%;jW~Kt zDVNMQO_wT_5rxfU+;WQKoxIxUP;7LNpmWi=)%v{f7-Ufc5>>oA^YlHnBWVF4i~<^1 zdH+i!wgLzCTwBAeS(lQnb>ScD6p|xOJW3<+Hy(x7Mh;nO)QkD-iA*L_8Mw`%WF^Lf zIEEa=jwJe*X)t`YTmY4YYOP482N4hUW+-a4{nn9h6(V zV)eL#Ml&Vz<=h|P_`G2}0;Q^Qt zHPZjI;|k1pwwCUPr~XrfXz+T{SN;DTfN%JJ(Fml`QI3}ehoDgavpi>s64j5Sz)j8W z;g0<)7b=CcA?M?-Rg!O>k1dC;LU;hq6t>&$qcZTkRwCf_zRK#1$~gt!n^{6 z6u`gFVh)vFTN7CQF?P$OaYX_*q$C3o&nQsBAawtoX*t`dWIT|tO9?;xKi-aBpppxo%aI| z(BV3C-p^oPK8aCs)hCPcGM@aQF7Y{`&;cr}Rj6JY#C|n7lF8_LnF2BGd+XD?u_MO| z5Z6|i_@9ms04TRR@6l7c+}81l^+14cp!Oz{hzH;;J1Ma7x;c;pg`dZY%$Zt?_>JNC zWg0)pB(7OUo?q<%h9N6cA4b5Sp$v|vL49lB4S(`aL61)tis4!M%2m9~%4!E}mBp2{ z%guz0dW|U^5Sc-bXXeQ}<2 z4oY}8lPZl>yjqyFnnbi3bro9cknln#P^~go^~RG$O4NKzz<7wlKNvmA9}!CoFta!N z=^rQ*@*l|8yC2_@v_48lao1a}=K+cO^M-o6+@LD+O@y<%Rj86l=cN;_Fx;zjBU*l+ z-($cdBazD`Yap3ZV+jbQ_;~LA!L3oaL0R;ZT&s4Yvz&67CT*d7Zk1ZAY%Cb^{%1-P zFYyufmL{@Aki9L|Igig{*wky4jfz6v8u&^gULMZdSoBVqwf2K;-eE04@#cKjYN$IL zYnamHcNDtB>l`@mRA`Rmu|cZz>=I!-%3p4I`jcvLpclWH7}2c%$EiX-&EGn2smoniUS zE{e;=ZhqJKygkl42er9bb4ZIB;8OuSP$9ijg;Tf!i|b4%3n^v?F5*)cEdP zsq5MAkGLI5o&EUpvY$Qh{BdoXLj!v#awe0T;||M-g3M9};O{inhWjv8MXgfa(}jd< z<^AdMc$wbiY~z*cAYSiCX2-xTu((PZE^b^t^S!!FTDMRsPa6J$ge%&MO18N@Y7k;{ z2d1RmeV0Qa0GGcF@&2bdyIy6++)LYK#FC+$GQW4uCe)qWlf=(&R2b@!6gV3q) zGK?!?_<61ky+E8dfk%=w5BzK=PGg1WbXqKjV6IFicG92awErB}M!;1-cAnJ5!MB%JyW>VHUXW15 zGvIAD1PI?tUy~fEbzZ8UK{q!@xIZ-aMZyW5yCPo}wEMqR+KXw=TZKUlID$rLKBq_0 zR%rEbFH|l~+USYYDm8qBIqFH{Zkn}lxg5YwEK_ELzN(0+#4?Iz)xue%zEK2oSr0Ms zG0WwI=b!s15C#lOT^Brq6=cLre`$|ScDK}!EZ{5zK5t04j6XBaIUUJo}U zhr`x#XlseHE!uarzs4zpW<;RGGR%lh((-Se<{gp9ZPk<5ER2;mPXx9u59fM;>xZSQ z;!qkVwOBM6QzY8p;0-K41W{Vr1AQ)w&UyIPua7SKpFmA)?QpYn^;kpz+r*Zm0 zBs^Hu>VV8E28Md(+o$|lT(uHYIV65hS>M?^6R|JCAk5DJyOlcHQ44TIdH(g{hsnNL zbsE)oO8i@zv&}&Y0Mo_-W3)sQEVk8VgMhdO;KGMAv`+2&+@1WXDEkWmL5NE@1Mg3F zfG0A}IeFU^ApAbonT@M2x!30#_9sappbwPuGhrfL`qg~S&UL#xfkWN8T6td_lTvHcg@D4fwB6
cR3Hhz*Oob0l?ixv8uRu{m$hq*M4{GLpx_dq@2^?9IdP4 zG0QQbpwL9oWKqJs9^u}n!K>TZk+Z*-vMjPcy*6IgzENjAo3v5%frj63q-5J!U({P# z(ze@I9znWK5F+k@Ag27W`AZo3wzEP9kZ_JfA&zhR3K6lL6F=#CV<5UVf=Y`C-KyI8 z>FI>YmGnwZ-LW${8ijby)RDzjvnLPFlFz-VH;i@H)$#4~iXSl29moH(ynF>{=gxS% zCH&u06b#=04V6AMY0BWUs; z2>MS!Z2->`)Ro?1DDfwY^q-%A1Nip;o0s?%LVOW|IeBm`J^f_x@rMKPKaWK~P6(9>kSbhpu%g|NbAqHv%MJ za9qJ;e-{Y;935L7kWVetAJ5wTdz>M$fT!FR@h_UV|Fq-&6m=}OnDqbW!r+F$ir-fK zhb_jRZU6D}iVUoH?zd^Q6#u#KD=_;1hdbb{62S+;FP(Pv=FdvNBlU@TbTwK0+Tu?O zY(A(=(v?VNv#4Q##JFB!FFs(-OXhLs97UtX2!i(*00VC9j_0p!Z(~7z6S2lr*3j<` zBl`^tM80A0}8ikD3yRjQ(Oai#G0`KY#84L0@T3yGNbntN%iN(dw@RO?;g;fzv38jVh#8VFc#X>i%BqS8HZ z$eJ7vgKBPC?;TZk=W>HBmn*eCJaUnb^%?PxhC{?8$5O{j&=5Ytz!duf&^}%wo=yzJ zrW+j(_oNn6C0k=H_qRD3^+$Gxa}G*&`u#nT%l-GRSENQoF;Vn3JZhEt{!$Ded&tJ) z{;bGz$IZ*sC%c_YhT7NF}@q@_0 zX%l|4UG;f^%etUf32I0&Yn?&8eHhgE+V#AFFeMBIo{^x)`eea*K!1|(*9(ya4JL5p zf>bh_&D5l}PU6ixIyTpar`^e#u!1j$Zu>)a$2U{Tpy)N~2Fyw;?7}dNZ+m01KS?to zi1Dit9=p-5j#RMlZiMWBW zTnQ8G>yGdup%g4ibcg+^*sf5V{_^!Mv3b_g&hc@DrNhbjjrZ<1%(Hb?O_FJhom|=L zUH5N!ADHLLn7h9opYLQMEFyOaSm*Wtd-?ODap2dzWRMyKi|jVYP$D5akzvaJsyfHL zsjkXmn+}`h#^jSZFyF1RS@syOQ?1nRWz#`s3+7S2>ACS^R4sOtVz>KkS|RMXGa`3+ zG)s$2zyrq`dJjW$r`hT#Rl0^@c#%|l8SgCl7X(z!_4K~)5WChB7dw||FE_XdqLNGM zO1CfW_#!W1+0YZNf3QEBlEUV&Pf(jYn%vjgJ>NszOQ_lt$N!~t*8 zd`NrS)y+*>+)!%MOxH~ppwtjMp3g8uGb`;W+Fg$s&0D>fP9?9s4(p7(*q7rNusVCo>uLh2qIBAMVu13ah{!R6Qh$2RXYpIvB2t~c z6Lnn~zQxg8+2UeBaaq}7(4u!>;|;IL@8xaRa9AJI<8~XK^zJ4Gr=tO{Hz^D%t|6% zDx$;TSRz=~*lg_>5Aa~>KY9Y>iFOzkO{eRK>A;_tz)#p+qE?aNKN@55t8@$eKKo55eBnH zWq-0pM$3uM`_U)qjQdT%ZiMbLXg&yNzBU0kJ`^V3FjUV_kYSITT%yfao0J#HS+IWl z^ifO(Bw@<8U_NAi;Cda|KGYZMDVNGoYsNbaF`U4j3_P1e0WL8GqbUdrI*!V%N5cRoQrfar(duM04d*}77#>O=}eJs6-GQ6Ih_FZ|I zc5~L{h5tMDt5S4%{L4dLKHlxY;=AuTT2Vs%%Ko)TXIKL&0;-_ATR?zp3_LzEm|?Wk zINzCXh?);S6DCE0qle0hw4TX;#GnPD3sleXs?Gl1u$lKn zLw1-QFE#Jo9cj6$&b$^luqh_TIK4~xidi|EzELil$(1}<``eg>$*8Y{xo<5xFFgo@ zRvZX>*S61S5UDL*K%?Bd-OSI|S&_I%gT3Yl8pBDUZKlt8H=$<~oGUyUd5-79wFqxh zdtJiqY*)2HH;?;^5(z#&FVr73Az6Xx3tkHj04dHJ_E`1{|?C&ks9j9F6I z@97cc9Ml?iehMh93^bbN39XEc^+xAU&{Q8RH@n8)-C2*S4XtlGDI{_vaVUSO75zB- zRmkt1Sh$)h=b*70&??Ff9WpY}iAfd+&65ixkdFG~ibnbjrd1WYV>`2x*~#-z?D4&R z@Kc1PG6n|C8#^nayk{#)Q3=k09S;EUntbqjxHlt5OlAb^apPowF5zwb9Elf6{Yi-O zTN=ttK3`b{lSWq7%+Kjy=7Y=GLkYE%M|`!h^tKoTA!Y?C^=c1@=2LlXe-TODSup}8 zQCNC*B+dH}`Qa2v+YmxtIrxuRlaVsERHdJAP`!V=A57v-F4Jk1CO8t_DUYQ#>xtkO z7qm{1@zfb0&X-EU4(CS7Qk!Dw)9{u(S0HAq<9JQN+Fbh?5=pD-t5tj|4oJKq;Jm=c zD;EXsCVfWUoB(MLIH+2&WYdfeE-WIBPf$ghZ4`{wxZgS&@lZ;kR}-ChnjUTn!@fK~ zRwzE(=*@l}&FEYmWYH;yQ%d<##K!fCb);Je0S!`Qc|atrx6R`5;a)MjtK6KNhmdo) zU-!&)tbUIOD(6^FX9`YP&VvOc+1dDcBY(l}OeP~MS`l)1y^G7M3*+rNj9>I$)DHXE~1%o3t9dP}io$MV}HRt)~69rbJl zk4sK2nL?Hn@W!OwYwQ4x_pvWMy)>9~&hjzdTMiSAG^D(ysVncx$;<}WsQ8dll)t$3 z8-}=Tv>^Rv-|5}zTRSF0d;NxtAw4EMZsU5k))m>}U~OdKTWhtglP}8=9l)!|pH=hG zdWtR&r?<);=fkgek5jRFYS&q^*Sj}23N4YHzB<$$%_6Y8SrCw_G2Im7Lu06(l6gE8 zQtS@HKS$oJJG0e&#fe!ZpD&iXq=zaePoBG>A(4e0eU-2gy+Q>T|HO#-l-@ zxV&*BQK1UPVgO4+{DjS@iXerbb13-rWhTf>6hNdC`+?t@T%BFapgVNh3}F)T4$>Qr zhzQ3Z(6J$2>Sew4Y>9`bwKcXw=Ei0m<4QW4CuH)Hi>&t@4CSXSNq%xh(ja}@z@6Tf z#9H9|nSS7zg-NSkh*PfGB8jh2)AJ5BD*~; z2Zse2WlHn|PG;gN;YZi;0Y%p7<l^+kJ@& zgLz+TA>c(GQ&R81AsB7b-SRwmdQ^gLyc=jq9058Du$O`W+#>GIgoWWsQ;^BdruP1H`FM~Vo>2C5xenoGIk`h@rxomro%~=rs_q;uQKDbX z&`*a6q`|v9Ckxb-ZxDUM0W6sq@#(s~E!3H9%F+bc>D4RrOTNV}*KhSxc9V%g^Rr>n z2Xc$jOljI_ zmijvxM`Xrd!6{D@+)k*OC+=;W7|fKu7N4uu>}s!q{4VQj;WyqI_1+d}&Q?_w0?e$p z@Wt0J|`06kkpR+PJa3tm1*@TzmT{UpZ>i2CfCapfU zn1wa?wc?G}ppQwbRu$7R8E+V!n%QS*{=6KEG;%}|AC0iI#r<DQ?BPM$K}hgqrjCr7@FAR zr~I}mv7+?!77%a3(8NFQQ0%9<-5KSX$Pr`mo$n#4wpr%Eb1%b##j41n2q*lcB{DLa zEyAAT@Qk3uQGaj|@umJdq56zce#jX}ZyOX8J>!uKZ#=Dv2vjIk4$_tFjdvG>@A%AW zH`r!cJ#~zhD4+xc(q2U565M3khchLVDVnjYR%OQ0XJq@@B&ExN>=b$E7iqW5Vx{da0@c-0Bi6yqwu0iI)?R*is}wF`3_ z8Tf78erhKimow>J1DPuAY?ZH{&VVxx?@|%Z_-WNJxWpX@YC64(D}h zi@_Ny;wX;d2R~aJAKpUc&^WviV|FN3x$pGRw^&RH*@J#~+VP(@eHaukBIB-9(k(H@ zh;hkCQt{*$TT3nO#pblnXw@`CmRma}7z6!H|o+070PN_l|_&Tr;_i7prU zmaygd=diY<=OIP&L5dQg$UzG|Ba`3{h?`QpnQ~E>?qRsLl0Z1RqTLpnZ?bKNGHt4vcHE9O4<7$Hl1f87vm$s|XW?+=Ze3jzCEu_y@*5(?so2 zZ;1fH`_@H3W972)h^cj?BGSThXnk~VJoc*M&Mc9S=ZZNI@Bv-gHKA2-j-pUTJ-I71 zf31H)_)qI!_Y%!<%3GzkN$duVL38h&dvr0b2WY5+#J%~L$%s4@kY~_Qs0^X9^so+9 zMb*0^B-+&edLaYUp-_Hi#kI~ZY7M;U#M%Ht6mu>5>SCg|(0^>|e{Ss%P@`TD(3Pem zfu>M@UHI>@F?z+fVAH=xKY{tzuJx2afe#T57&J^Ij#pBD#M^&e4t%cw=NJ5>DQ=b3 z_@CcB{S$9fJ_sLG<{cqs$bYxgRt2)L>XHWmEB-&5{`+SZBIShE=C5b{cW0l22u0XeS_tj0oBX>anqUZMhW|sIAhKpQ_3dQNlx=szR8Ubw|5s-` zV%j3JyFF_HOkLe+*x07&N9!C+|K7{~dZb`edjpw3o}`zbA^ulr_0K3IgHhO7Y&FII z&nRG{f;Z$#s?XcM;^aSH90)ieo2TJ*ddzb8PfNz45IzamjtE8n9%dqxW_Ui7;HCTE zssFSD?g>ucc!sI~_1~8UahQsFhsgvp2ETZ^PSLjga*cf7LE=A{(n!k zs;7H4Tsv9oBoPsT1(_)ZJ3E>DJg`Iy-B4d~1YKVid@_2DAr=lIil3XEEi_R}vpOVk zsYR!d#%nD&-nVfFlsitZg)_G}vb94u9S9tNx^J zjJ<3)1q}B9z*Fgy5`EXMg+}^`@}+2c9GzCuMk+xhRn^g1w$*Rt)<@Ip27;&2&m!?I zC-L8z;b5{<0L)6D5bLdRpU1@EFobj1n~21ue~6wc)e?Ntf^^_yKaDw#cb-&|EC3a1 zwHB`c)jH?d?G>&YSl=>)M%)HAt3BmrPnuTzl?Yo9#zltM!ZC2^{5_f_@SUgX2u+`?uz8#<*GAgUB|kG6N(of2wG>-)J!f`#(14vT(;CKUM2#WJG_#7; zOy_j6zS%EtZBbH;IQzl)iJYxSp`M`GXbB+a+5U)I@&NYR85RykS8zghdP4HsYXPvI z4T`N)hm%1V)GvnadxqXDcLYV3A7>I$k@wURYYwHbB!iL68WNsxKexfdzAy9Swe0jrwB5;Pv&VCaQ!H32HX7@`uype?Y^V%~Y(OZ|o`< zEs-*<0#nay9gB$^Q1FkqBU!9>;tj>-jJvzPci4Qq*{4?oT4bZ5Xiz?A1k5;gGHD?| zJXLT3G|R4MJ&c+jP@f-py!<=#&i5uQ7B6PNf=UFa5D7Piz_J$ZrAt|vcGCxBLY^0P zL%mcCY8o2R&CRLOD7UAZh|R8#Lqz2XH0Hzh{mN+*0Q(gm+O(c&2;3SjomTtIam z0kurigPD)^AicRDFkNV<{ETck9LY3u8r0xgF=dH>&utfzH=KZd;F=4Hx8fb7Fo9pJ zI{RX4Oh@&V%1oiOm>T7QZ*IfOZL`x2s>a(~iERN|b!M`!elJT>gCvB5F|)z+-5clKuA*A#x58?JbX-!&*SD-Wjt6!UN2O^?XriCL1$=s*#M5kijp~0 zueG1Xv0=8H@wz!bl2XW**2|=Y-5iQe4zryFC6G+;HYk9e?0{JpvBr${D#Ck@t^?mxZTwhv zgf;dU-LvOpur&kw$Bsl4gao(6&u1KncFJeWn>;Uy+8X{Oup8EHHd|?HzeBzwMk3%6 zO}#zeMf;W`QCwH7Tq+>=v_4xUpIj0@@q8 z*}1trI_{5a(A*7=5t#SaUq2I6SDU3+e0fo0JgBZkFgxOzS8X)-!*^GDkKJl$E)4{} zqg6)Ks;^Dk$U_|!0eixs^nvBMKX&YNlZp(7>m?rk{jF-iaV%PQ`1sLcQ!L+XWi*@B zLW3N}{^o8WbT8zy=koJ)hlRTBJ`*5aa2RTT^F}I-S#DBh>5bLAgKT$nzGzuX=wpDa zxjC_TKgEM=-d$78S=aKA+4$HAT6ZY&SC_}m;P|~H{Z5IIh7+_xvvK1XCKGA(DnkZD zPn8Cp{c+wBm-C|qF_k*UP%22S%4oFf-yyQb*;!he0Q?wC;7Nrcb={<%$*ZM6di`Fs zIUX=yvIfSMTRmj}r%Gk>YH#uDu*}F4K}0mRs+KP~)fVu*Rd6 zOy3xK8*K;8hZt&=urR!PjeDl}dkkt7Ir#?>_uEUW*<8r`+wpRk|jn|tn0m2RxA z>b;9lr1%93oy7o&%{-jM;u}Y!Ewn#fnhW&2-~=vSYK1!R&8>Q~-uikJWL}hMsk9e6 zqp9h;gW?h-SV0vNedEET4L{MR0%$ZE{nGb4&jAU}*X|;H*X0ar#DLX;Yh%AyGT!NU zDVtgE>S!?+=(J^r@YpA6=UC>>ighv^rNiz|w$sI{Nz|JomZ-C=T6ed*1?JUIt)k%| z7$dIE&Bc<8g^y|tZePAtsHUq zz+WuRzxdSqe8SJ$cYx%bvNaf?(ov#LDi?_EDJ8r>5}U|@pJ$NFY9)UyjMC_G_GRon z0hhgKBJ9!XssCV+QZdyt#L>&k%eUp4B=^)f((4m-TDai+AT}8fZiRe90NMJ#+ig1E z)#_;97fpI>Vo!y|ycg3? zGqcWrD70|GLc_ccICGJ6M6enC?wJb>$i4=ZaZ5tov}|+VH%uf_Kc+^PI5yml}4D50do*cAP$Q~p;#>4QmWZY znePF48q9CHdVeQgUU?QKi$;DY}PdN{;cVT7Cgw zMY%2E6@feJBH?BoH?<|+3xdQANsLy^bPAr2$|URafrP6B*`+&0&?&5 zSt+2RsNPU9y9sf!hNTzsV$x-VU~9$lQEUYQJ&AIBaN=BYy-k7OZQZha_{aoF1DBqA zd5$ic;K?=)qhUfXv2YU!xkILu+U$YdNfa4>RWR$$?-R7f^{Ud=a?X>-&<~+FW58-7 zwm1ER{nLz0`zxle08yNyQMicoA^{@}gz;K-OAG9liSMHGs?t@3atn^G0?e`c{>Y3Z zC?LjvQXcWcmoOy2vafxT%ix35vNE44;2$!RuYR0D!N3@F&eM!%@Cz02M;HV3;mH@D zap;O&uhJ~nUcbks4Ld$oLqT6=CI#u+F^Mi-ri}QjaaJ*#C0j#Y6O&7Z#=Of~-j)`T zp02H8!bC?d#+Hs7Z6KV5$`y6%%Vevzjd&EEz;3 z?HO)ww8B=>&pSqB*t>VJqzR(nRO(q7TYT6v)1MxCXffqwh2K{lXo_yRzt4HwJtYG~ zjjx`fFK!%q?G>VdDOTGzvajMmMM|7x_L~OoFGE`?8D%chSIh9%%w`J#u!K5TFDJwL z>*IbJe(-E{u%O2<*T8G6x0>&I)=@r+()mIp<>dBazeDavRq_7M5c4)K4?Jd4Z)MUJ ze%woe@V~tPAfV6TM2TMB>*AH+VasI2@8xsgNy$KQ1>_yRx9AX$r784OzvekysGVO) zI7s-Mx0%pFrAfoeGVa+WrIn3gxwlZiL`|R8nn5ZSZ7xjd$zaysaI$WXs3yVO#sgq2 zE>6dT95B)a@9ypRFeS@6jT2DeWn0tih7$edTQ$5N3acKu#fW?J#MTEq%vuh<#tXZX zhBU|3v{csCj=n6kdG&$}CBOj=s$xQA`9MLt^yXmpvpm_oBeqWVqM}b_&v6Ebd;9Q4 zyShF|)%7yMsZ|+ndEwC3l-NJ~fNAU=wImDtpv;`tdw#{~WsN-^N#obxij zyD2_ZnC8K&!VkEb|KeN1`3$s_Qv&3d0 zlH87xW)IM5v=4+~!^P=dJKNYZ)k&?PKH#+n_#)7Dvsf(yburCI6um*XTcid{eCXewu zYa-k`)Sn^)ft)8kBXo3hk|&6Z>t3(BrcIqM`lBgBMIL#I;_|^tv$90p#7=YT`!zgf zd56ALah=mG6FA%0js$8crjS$so|hphQ4j7sdv%8?!oRZcU%SqgIGX> zmZ&xZx8g9q@tGC_2mun=msYNZuSiGzK*ALNtZ*Y2Sgq{yBqlu0)$T*>Pvu8y((8K&O>-i=TI`opdn#tWpZan3G#?QN`&rDVP~qA?&wFyGuijMdwx8gkH=-S zk^ybb&gE(uyOV|$}6wRz;i8^-rw*+dcX(dhG;C%gF}ed~D1D+kD_&ZSi$iucn= zjr~TBN;P_!R4PudSiyqv06RRT8d!Vauc%Y}AiBe__V zpCP~bePfVXW^FA-^Oo#xSFbqCZJ-Ff&P}mXVCy5buLT(}U3#YY-ibCU<0JN{1Fsyh z$c6PJ)?H5N$@*gsv+a+8_r0##Gs*f^DQq>ja+AlIrG%$X@Cu(Ksh2Lk@xuOTxT?;3 z)US^B_Yp)}H4tx!W+HRqR$v{?5t zT_*TSBnaM2xeD)@zx4|L?Fh5IO!i~DAJh(EeAkJLrx~Q+q8~gi55DzUdtU4n7dCu+ z{>rd}Q&_3>s}rXz!Bcc9L;>|qDnz=ua5@f{MO7v$3Z|t#=14}Rq~H}oCu7ALDddkD zbe&{S*+MEbOH{x=z3s0)tmv?=yIs7!g9(8veuwZ^S=}3s9PB7?&+%eAgQ?_zjZqA5 zf4&SMyWb^7N2o*xcdfb07Foq{IEn)G|l>0xB7Wxx(2{ZCh z;y(=~NP#O49C_CK0qZ<%IDdYK5CB3M47Jlh_Me6Zp0F|FD_(Wve|G`}<_U;|v|m8| zyNSOVGDZVeuD&HK{WDMe2RaRag8G=flE!cu=Z~-zTxliJ@xME-PvA`=65jp&!`G_L zKguh9jPzCi!SBF8`#l4&;+JPa=#c-u8we=9H{d>LY9g@yeLE1)KfJ*svh@le{-4hN zb*D6-2mbz;r#^o}jent+eD9wg-b(2Tpue^PLAat2`yE4$|$fwL1C3bcV2L;;X?ov*Y%1L~LP_HPvjKvUtxy_jw$o>MmS{p3JUCvW^*c)DJBB2MGEL(@}^3;3xRyx9^{8)&VVi!-4{zM zp}}e+*PT^R1nl?KYt5NIPf+enRFVXaI0K`j&$pMQ{12#|!K2_fp`F=k6Z@NUY~N3r zi`Tmpu5%S0+_~b3QFYdfm~;IR$j|~(8Q>U}iE0z4o6~E=SXzxQMaqQ&u*mph@G`;( zSXptU>au|TFi~uROjd!3Q!Xc85lba;`2zzQgOLDWp|Z_d1CKXi2`p z++01!Q&x=*{W3;iI0!5V^v3}LB=Cv-)QhhIT2qT@%!kf?EauPWuUT}@ZJqKXv)y)p zk50|!3mldPaDXRqGDpk$>T2D{IVYL*6UxQ5| zlulz5c9PNBbQTV;AJe+RaEGI*TA#4&UhlzIz-+6iX<7ku1rHy8 zq9s0_Vlb7BP#_`f<5smNS*tvwZ_!r`1DnzTRd!H5iwT zt`_jgfMtKXfcUc@GzxH_%9xN-3!u4ftVLF$(}?(XjH5~RCP1nKVXmXa2vyAcpjq(hKy?&7HD z^ErNhfBoj(nR{oPGtBI>_p{g9&$HHgzuvE?!AJ%40qN)FvUvePFiMR-_~QLlr}L;! zx2K}yNDq{asi=;A;tEX_%H{pw^PB|yN-G%D7d@iP<=YQPRV*lHOYQACG=E~Sy}S{% zUZ_i9{q_J?p_*4!yH@buIvtUFxc$g)NtlhKipnZAq0_h%yQ{jf0)32WfS&2Kdut`( zfT~^{666PE!=4jO<_g?r$MVj{@Oim9niM%UGL_uKE*oFXt93k`O7 zw;+agke3r!va5bj6k2FJ@b|<9?p2caHzt`2wzp@7;9T*0VL$q(nX&=0S*hsg=zzV0 zXYm2=%Nyu3Ca$vJeDJ&~gR~8oxlA6nNKxjA2Zr2dCW@p5K9?P=OG5tPt~prinZY>o zkvqMS;g%L0qpVt7Tc-DA9a~MqEy7iq}S$ zwScVTqQDUPW={s_mRcuU;bDLFG&!y#?9aDEW%Bw+0~}PH|Hau^Kw#EvB%phj+JK&e zjIVFw7JcIqs3j*4OE5t_vLYl{H(J5lCMo;6sCvU~ev;yZN4`KPHu`GRF$^wss52ezBY`N2wFBmsA|-y~6|r z$^2si#7Se@%^av`fV~_=sP}FIt0b0?v}l#a3X!mc2|hte$-E{?>-h57KAT6>1x{_=v>F0 zfX?;I;`wI*_d_&3&zsPzvwaZ&Jd*tkNMSb09TlK_EJ20O|3w1%88Rc%r^w;EaO`2# zta02P3e>lso-C=jGRUa6YDi^R; zv12jyFZJ+BBZ{K0<-djE<5KMV&7sPftd>G2-C8o9;s-XUPxQSw#_o0{h_H-UP+=!7 zVy24}m}ajiE@Ma?7~n(^0_u2EUY+i+2YFpy5q^t4ZrDkN?x!)ROb@U!0(vS{hx9Z~ zs66)_gD)w!cod|i`Q=*gi_Hj3bppM0TYUAzjN*L&O!8|#3;5?GiFM-Fad)dG+7QFf zkMB(uSqX!IWU@8h>HNJHd5J|UN>UcCo_xb|b*P{KtbfP;EaQG+&@H`k&s6B!ZhGg2 zfEtuT8G=O=kCNzT(`3~fXqw_r6DVbe+S106)DYsSHM7KicQ*@mE-MWU99UUooI3^v zK#fOh(B7)ut;PCmj%2Gn2RB#UXP}7UyNy1tpE!myG(0ruQNNmf4gAEQ`=5kt#?4*)xqFU}Z6Q3lSr9c!Nqta}7%8c0&z1i2z)5(HtZMF(s z@BjHt@F6wFxe%_jy|V!nGYC~jKmFd!>1>}?KIFw@s;ES(%Puy&9QLjCJxaQsBc##`Z5XTeh=qx$1G$rmN=h8@p zEZ%RHkx>0y5w+Gu!N^I`XOoe@%A}@hg#C%-+Ww;3bdCrQt?q|v4Dy4O4tqt;J%2$< zc*&1=TBAaV!VXSOd7du2rN!xh@}aa*zdA<)VPd-dI3>M5kpYkIyC8?(?J2ut8d!JLqk%7&1_uu}vUcGYiQ#d>2>z0(MQjwBM0C(<+w|8%GOgiaNtN39V`QQ|H~UJrn=X|PXyg}4khv~$!xP9^M+Z`YpOd9QQEQ5(*{e#Z z(r2p2qBV}sA6K~b^$&@mTNZ4YTJgz3f)f+r?BMNQToryWj7U#vy|PHcAcp)dZi&u%)xIpW~-3 zEZ2XJ^v2W=m0tOGCLp>D#$gHi(q@5bGqL=BI~OtSex`uG?Yi`FmdR5Lw*S^Mq#2K7 zG06y;Jiah{o$Xoc<+=Z~oqi^W@vd5@mGz_YynE5;U+gl&i`II zz)}j74Rw3!YzY7Tr2l^gfi{;;hqJV!UToODHA`Lx`=3t?q;Oc<`e``UbAtI#dD`s^ zXDRS^wIxaks#9@FyDy0Uc`q1!G8pg;(qGdcNF4jmLFMa*zql}uK%Mu={?Bpn-*=IM zX#=sak&FLFT?{@P=HLJA_aFZ+{P34Mt|0BQ2nlx*^zPd_w}E&s7Xs=^1qeWpA|+1b zAF51%d}4uz!l5G}A>j$A8WlkqI3v+Ow#xra{}tBLefa)^UudzRgiOFSaJQHXyBp;Z8ydFo%riQxi4tBCJ zq!fS;KlGwy0ktHv%fN(grHl82X*%Zz?-wY)YxNCT;)|GIt~u^!(<7j_i>P&Lc~Y#T z+TzMks$EYDO3@%}CjIU}4d0nAFfNrPZkPTA+O3bwr07_5J3?{3X_DYH3wOQ<4ABEU z&v(RH-1hS$@a9ym&-SfA0a@oU1+(*|39|>t9KIa9g%tX#0}Z@4By<{WbDNxei}EB; zRKhn#kQgr`vflim&sarEEDj{l;ebByxGUL>PG(7k4#(7(hcC)h8wR;#_A=KKTGj05 zMG8|}6DHdqJA>Bt_GJ0VUdO?yb{2eH*jkGZ#U9SGIpVrg=fmXUp8N;_h4tOTX;aY` z8?FAqz2$Ut{EWi>{N5=6q#V?nxR))ioh|w?tj@E~! zW|P!$6ZG_^Tg4Dl1?=^U?c+Yvu*hX_q!b?VNM^b*EPZ$tv{q}^_}pCwF&`~kZ2wCfJdsk1r%K`flCYBpQ0rJ5hwQmLNB??Va;D}k;3 z`k^x324@r?HZW~={~0?NM=a$10EO6$*v4~>hbJ+)n|r-`R_^|xWvX9^ACTwefNmGr zWqVa_eMlo8G_RF_Lb88oDh?77l3MG?6wqks*o(K9T8Nrja0>a`yi}O(fVp8xog>h1 z$#`iwl&h6tx4Hie^v!yn1o!}zWj2y#z^gS9mmijhBwf+Q+1#b7R#1G^jH_c)8 z7I&|yDGP-Gni$M|f$A*v@{4d*^I^$w>|3ET*Rwj`CGBPqn_60?OC7W|X*Q@RoVo7@ zI6beCiF)n4D(Eorhz(^8L2n%9&DoDd1yn-bY;g9b6#|9|rp~7e?4BS{Scv~SL5gWI z`?cHE%_ZWKO^X>->8$0DW|w{Go^YHZbbkKnIw{Pn!*4+tv_QA3@E~&qjN_U@3jt)J zZ+V$&6Va%6+`+5!J$Xwv%V@RV-k=gr#ums}jilD^a1aUju3r19kBNg=YE|6pvmZsk zYdW^0!!Np2Hj6*ufJ+13h!uXarzLzaj@bzEy;;uS)Ks{7g}$6vxW^&i>~0XEI{a85p1r5vvefMRnur zfZ{Fzw77YI%`<4^tMDWzqs6tv?srEWsJX}i>@4|%{eRc|8KCAjA^s9tq7PK7T933k z7qVb@so$2t*e~>er-%|K=|mcBdw|k!_4{P8k%-IHX&UERzp(E6{DESCY1O$;7++od z%uChE1oAXwd^X7u!SHIe-_<^V`-X&PWn9`g4yOkPV)%IUnmi+R=IjMQQf&_5Qj68h zg?UQzyH4AFBp-0cAO}yy@G7=Bp3(wJq3DbXOfq1_6#$rHBA!M?O^SfWV@fo$K8d{D z`E+OQ`%O@ZiiN%BN2pCDGKo?rP|xO25VuRc#iS|(yp%GTHO|fXQi@xSSELWHmaUzg zh>mKs_rZ#vqHuD0^k%tHo_L+huDTh5W`VTXsSb`x3kHRZBCsEwIknfqqiR=U#U%;U z0Qu6ETpg&dkqGmHS@$W8Gd1e1pMdrWwTDN&dZ0A0BCnfjJ^+L>x&XVe%(lUN1vY8Y z@e_+$K^tFzbzlj-&Ns3VW*qobY5VdR=Oj?zA3b8SYnIP7ielohmOCVE4!3HtA;dz} zurR7}lcPa>tT9(L=~}_6v1y$+Ijg$*tBOtUgBi!%HRshCBa;{BeOS?$h|MI%cJTCwHHFYV%_D zBx;@dT(6GDLdu(-bv zkhWH`fOl+#`HYzX%m*OZ6JT>lbc{B}4uE`&P+jep1tJj2pZV@4W=(4L)YhskY})B? zmca)c;NRMe*@Nu_@8i!57d#!s@iiLPR*&})g5&*INx zYxTRW51Xx8kpz^{MEYj7-@G7Cb>Jf-d3ogPqhXn{M&P`CM8QrI$LGZ%-Psw)7C>ng z+s)>)fRvyXnAqxt^U8d}_%*|Vgil1;mkT(BBk!vNOq(7^cAF=(>e+TWjT69Xs|0xI zN{di-y_c1?I$*A_`ya_9=|2w{1G+Ogh16;{Wst$hX<*rFI8jnbD=G87@4fYtu2#Te z#+pZu8^K*pQ1y=4=yjibkvf}&XE6U?bvG`h_h zdt7JgMH3e{rLs2L1FAv%0urNSy2VMNAYkUGKmRQp;LLP5VWnNmr*Vhe;txG$shO#~ zoIM8=9cL~6t;~a}7?^cFk`5n+wsi~j;mFgej-vYlP%Unu4Ahoqfu+4Dlj+vvJ;TCg zI1W?kC!n9s+ns}=3L&2~dW}>Gh>>b8KP+b70UHK=yEo@qZ!syORaQkQZ-v&E9N#^j zAQeNPftUbA<`Fz*#plCsA~Z~hNdmVgaja%22c9{+zmMjjvXBP%QxY3`CWmdNhGfE_ z$y6{&-Oki?QjpO7*@cb;2-dD1AmV$a6?79sPPTrM?JPE;4(z#a$`eSLPm1lHv~7D0 zVnr-=3EX|1zee1f<4_)P-d!>9mIF2mg~JR${v+I{kUlP8SIw{h#B^Naef|(o^QyI| zE_>4{3k|!ENQV8fTm#971N);pgREd|)Wl58Xy3ZZ1`d#eX6VSZuAJH1I+InX%ddAq z%qt2vOW=%|SBd$JAto+vA+f(=>@(5v!YQ6LMjjv)nC=PSY@|JQ>RhD=r{PKCo-0uq zGDSge4#Q%U1qaCWj|2fiJf`nAnPIVM4BF|_+_2Sr8z`B2g?`Tm&QKb7)&Bu%iwLG$2=J|L3uy4~p}hx2q|KI3v+Q^X`{as5%KivQXV=!xjP zd-4iol44p_0A4B_H;<~~EloyBAmAGabpL|4S7&+e@r7ny?3=`i!Dlf+gn$!64o_P| zf%-TXK<@@|+=v3M;!?_PqCSe5@lcwHny7R?GWGEv(u^^_Uh~F54IxxzYwSCmx zV24C1QU*aTg_i6-#!IWl!>rYvV<%d)7?VK+4@H_3f|H!-O5XP`Ar z1rNLDFcQt*pK9#lzSIFoywzFjcC@_^?54Pnwfr?@I2t%iukTkXTxIWd2yt&Mw7oJ< zqXrtzYzOKGw7%;kK1O30AU!mD;kg?4*soM1R^!^z19IhwCsX)+RKn8HR<4Nr%Nn#0l|3@et zB1Qq=9=JoOtN<@g9P~9yYaD~nYBIkNEJ!7i?=sqWYH*cVVgoi`o}rd$9cxs6LgpR_ zG!1~f)C%7aE8)6_K-nW9nlSu_JJah?!k)1)V$jde6u{4{O>l?|c?JC%6|WRq6{oWh zG=!SRynN-=-*e0|#NoLZA1_T7NGpRT1@)`g{ML&PoM}%x@&If6u=XdSf~BpZkP+}p z%RN)6>Fs66!)x0xBM~*6isj9-Tc{(!Npe0p+vjO^T&G1Q;?U)j8NHz=*!aqA>5f9m&W27Fli$BEz2b2ne)$;{d>$%E` zu=iVi2h#uR!F_->phUiFInvR${$M?78`{IjA_-&|aBXOG5oN&x%Eb z;ne4TgnQOPisVtl4dER0<@?b#K*9S0@+b|%Nz^At>(>+6a~Hx zZ5UX&x2cXgU{GP(%FL|j51|rU!^LE%EtjE&%uR+eST7*UWtCEl;j~|Q3TL5A1i#Lt z?afeXqdJRz{)f{+M1poM!Pr~>O+*kzs4%#2Jn?S1UseLMUsjK(c-v8(^6k~8+uoOw zT%2}a0_x%Itm|1<(fE?3DR&lalW)?(+jvyC*|?j}M}vIO-~e~Uhwu9?sW#kHN0#i1 zEd}naEX3V2p4ly{31W(?5?MH00ed5c=e)<^^Z6yvE6ggY4oLq1LLV^z-c#GHe;vRb zZXXKA^j`Ah2cYsJ`Mo}2Y}Itu+nH)Pa2wf^5KR#n7u7|8oCx#3kZAsppLbDCkZ2k4 z04ScHz3rV(53Y8wCr0>#={*OfgXr_U`2OP9;7He3uaqF3cW+92*ZGRlQMp8hvXTWo z{s95GAu9;^8JLLbswv&{bQfX3|G}s}KD5hfZ}>$K|ALU{-w7JhJ0O;^mHr>(g+y7P zy`nfL-1!5Q{BGnM#sJ7q%d|0P^54$>uRs33^@de#(+Y;VNkCMo9Z$_$_|I6-Tp=YQ z2~n@bSk}TcU%sp~FqYJrko_+%34z9p2)a0I;V%U#{+d){j0fPiq;W$1uea1bK(+ME z5DEW9aM41gz?AmnFN^E^3m*Ua$l*W$D%NhnsQyI_vFt#@f!-J2SDWW|N93)91GVSjuQn=P*_rK;meI6EshHW<}rA3 zVpKd}$N^9Pk@h(^-^7hXlkm+iE~W|DDLw`-04oR|%)lU#F2iGBbD2?IQ4z#vzY|h+xXnBry~tw4<+XvfFcac*vBp~UE#SXaYAGM5dXiRLQ$aJiOSXf)&OoO zAn`Ad!<$QkHZn6412kvyEc8z$?msaokNZ0dWD*s@={%6H%8wLi{y-PcerI*R0y>FC zrEqevLp>&<0{L`%DGlKXYlWJ0(r5?>d>qaat8*DL*KV_>03J`W8qPs5r%eQSmV;|j zYhGK#?AFtYZt{L2!HgyLL06|PZ}@n_Cd1Z;wm;t8P8WnX21F;aV2}K zFKZF^%LCG+lau*}17Y)>QQTslcw{~xOHnjqCb`Rcr8cd zwc!XM6i zpIL&wZ&Z^#YwLV$b|}7 z{3*jpG;wL%mC(&{X>uDAD7_JQNg>VXpcCl=PP_50nmz|#n!>@(FQ=bbK`pD+%_ z{iD^gApZumk0yZFFP_5(09`+44PDqPXcUqQ!MnM&PMr zG`rM`kock+k7&7TL+uGT_+!)SN}3G?%cG&xIEqsh$!CbqxKHk__$lCJyuDZgqMf8n zJ|Ag{Fq}MKh%0e}{0jD;2R8tY3P6fo=&)^_q39x3b3Y!~HUnHB!;n_FJHyu2w%DVV z614CnXmu(fmpc5SOkrAWl172YHO*iq8C4nsMK-VkRszs+acB!H5UquP%nRuv`CAz> ziOX?d)L{jB#Ma_1-j`t~KbCk-6&N$@GLK1zH|my8E~-iaw|-_W3uWU)*?@>0Z6a% zAQlbHR}p_t$}-goaS-*K4JiH3;EX|^vPh|1Zf+77%vpYV9WmYJrQx#mx#l}>ijyvn z90UXr(8LXad79`2Q6+zO)-i3v82}W-fTpCb2KXtAbd(;SOX;@*JtH=^p(q2#)Y3F9*fK$ z>@9_i{9I53A)Q#~fIDT~6O#-VR>=4`&9ryZP3z|3=k|6SmGkBgzMh`@0s;G#iS?ui zAe4=!vpEL59N`D%!T>LNVHP;*i!5jGD1lk9`tso?xpv!U`bYLl4H>@IcCnF5asmPy zxPldJyg{v01-vYCdjKfb(wUD=e%nf!&@|{Xv5rp*+hi-@d@(-g=d+0MZ-HU$wmw@yDM&>~i zv53_~SQG)+C$qW=aJ#|gJ|w)+lEj|UJx0DOzJyYjY`>Y z9EL)0cq!F7^5p&Sw7d2)gWGE==nbmWa3HzW+**HTMx}0BZ%{Oh^F=>8A+L+drWhJl z9L+zVV)u%#$|ArICqe{)DAsaty5wXcxKO4}fi)Z3E*$A2kMouwKq8SRomr0Kq#~hU zQ6~pfs1jyrB@6F~(Cm`o7yzcGD3P!GlWupu3N=wyC^kckp)C$OJDVDmuLRIydzP&#X+J{ugeDwS@GyWK?Ip#mMXG9ZD z3d(F?th>7s$Tc)Htfv;@V36>;4<<(I%!aUgG)5`R3HkZS+=cC)xg1QWz~4MCRG{*? zZ>Q6!jJ33j`E|m`h5d(56SW%8XfC*Nlo8!IceGB2WKGpI4s>dg=3*3WKG%sGqiIzQ z_ZPxa}WQ_mc16bx>lwi{92q8<3=qp1`H2l= zJpRBHnyRhkwifBgph3pZlC0idabiGe9MF}yx>URybgP+ot;w^nv0Hq~K0 zW1Z5(%MrnU9n;^+nEV_xO#&7w$feQ&1rzU^qZ_W~R$7&1 zNLWd1B5f znJb4b*8`SeSq>#O87_BbZ^L_tf{IC2zEvT=s!YDmQM+Y_Dvk04J6(k?7%o3SE{=0P2YdW_8Cg87XppFDsIaAtcp-r_mW zo30jurlNI+>D$$Et!EXo>!MJU<~~!JyM=90&N#Ssync6q4YgMK7252UAVYg_Pk#7O z5T@q3BTLEgxu2QhLzBpo*d|Z9dLYMg9-J$B3dfj`kG2kYy-!a0$oqf;=BI<4Dqq{G zc|Qh^u&Mwl(KP4V=IxKfO`33F7B&*V#D=2m!>sh~(|U6D}xQea_WW zsCKR$wlqX!y}DuPc;hS^$Ro^FU{fw1Y+K;S#~ba5q(41Fc-ufFRmU02QEpIFH^^>1 zt8Q+ydrHZ%b^`$8&qd`;2y1_0jF2yyNW7$9R zH8q3--ME{aiHj)dE!IyK>oI;+znh;$>ZZ~56Ys+fQ~=vHD18cL4^SUb2wzC5m8mK% z>~Zh)sec%svwSzX(JF8+`-D!Ne4=vK#yJsK?W~IV(K3c0JjrLb34+-g|3S9m_HyN0 z>$dx@4aWXgf&27TK>-&{o|&-Q>}QHWuOr%ZYVUxF(4g(Y*g*s9H`y3giil@?SG&lk z4Z%flCC1p7d^E&g`J@E+r_=HgO7JJ(q@iUe^z9Z})vQEo%@Em4PS4Lbi78KC9GO4H zerZG3@d=!8Csofd<2aCretZg?FEk9tIabMK;(g^R@R|3;FWrG5*8{V~(Lk#E{U`ie zS=q$ISMHW9WfKY*N=mY?yU&I0E@i^UxnroMP|>os;hxEu;p{q4ZtcbQycrL zQ<>n8M;dH(fH9FxE}J}_P3Sn`6Iq!YHuA23;Sv#xjoRW{*JI~kA7KjzL8R}+VHgz$ zt3Bb;ahZ{iA>eb-m5)qv`evvv-oP0eTk{zQqh5EsBJW)v`n*z~&+3O#La(wJ+;Ra( z2ps{pUv!%?6h7SA#b4Hy+^j~?1-uZop`@HMlEc-~@!U-`s9wiQnu~*SNq_MZb;O|h z<3&7%FF<+5IwfXH6o7|Cu~`|mG+LZgGcW{xLmeumnBe8@sn$Qyvm zURO0$vjAMS%#Vr+SP6JxhDyMlETRRQN*}f%J-bSyJuNcBN^>pf?&;x3i~_slLK<&H zbX(ZZf?I7Vb@wX#KpLq1KWmV+G>hvD$qr*v;2Myjf2#X_beo<>Ej`^0h$+f_KY`>N z3Id(Cn@Z){`x0YIghrdz?&CG=8<8rNB1T}TS8U@5JTNUk^$PBXh^jfSIrZ{O1FKF& z%COVjnJK-Z4ibJn?QyuDo8@XPk&|B}v(tf$p=|$a;`?ZSObj;T%cBHoC<@^e?|eQ2 zo`xbozSw@55S(``_2N9kR-6WtS6ipv-m1)1w;^Kc2+-X4#TRIcauoa ziNtu=U$%-`A=M#)-1|1|7P%*uClUg1;mHM_uN)$z;-H|ONX^=}Q5E=da0HmguZI7F z%b(u6)oAj#dp3+O{k#c593~mZas(}P)i)tKwB_|!M%v!TYmGS_mHBjYUS77AsQ0600%hQPN1c%>qn}4(JCY9$#M;XKc1OMW(i%Q>jL08hUa6U z6zkKS$pTCXvg6#aCxcFwdm)1dLM@bf3(k8sujKSNy+1RvZ%F~r`SR+@ap`55SNNC+ zNb8$F_-4qqjm`!I4GACGl*yTv@YZlWk%g=Z$3^^c!iSL!S76YqGI4E6Dw0V6&rM=9 zD#r8N{6y06n;?<_zn@ZoJzM7fVm)ve36D$Dq2%zBmAiHiF!d3V2rl<7XQ0z)px?J3 zVv{SQmx*p7UWH3wm@&<#z*n|iK7I(#>ywH z^**=@F~AbXsbF{)Q|O@fOYEo@%F>`HI|sji#U^vh+C65CgV@?LuOOdG#9_A(45$S? zMnC$ZMGK}Cj5lZ~OO2(IpIb1_I?iS;$hT zwWX&Q{hIjdZDz=U&hm`RSv@>R#?s4(^1!O8LC%H=JD;c6H{o=U!yZL9T)v#-!9FkM ziNulpQN3?{%`X+5VGgu$Q&SVnLWh z_pZsJA?1BTZZ~k8Z$;_$XQ27Cs%bz&_=g=4D4p4NEiB=05a4nTmy}ov#!eA zZ+5ExI=X*UGVV#rjnnL%P+DoEIY0U4yS$i`!TSmmR^khN64}8!LV_!2m z4A)Boe03+uI8KTEHLFfI%CXcjD&?w*=+B5{XGj(94v&GX57@u3f67>5hakB>Gj z@XRjln>HOBFKYfT$ELYdNX5nYD(vCcH-xWQ26Y23z{JT_uYky z7@;D=x27W%TD5+t#=W@OzN>UPW#)4AhVPkRRWU{~n#dVoRQ~#Hx3-+qO z2RY(X<`;j7LV@g53}CIIF=9>s#}_~X->lQW)9An8ECd?R`+){>82vB9ga7qvfFZa# zA`BABzdZFn0!GmX1X#&biSh5DK#PE@GaEWQ{V&k_NShl``i~a?G*B@@*MyRWJN_TT zeG2(1h%53Fx?nc2hC^4R4g1rA`1P%h$pK0B)p2<;?lf&;TG}qo^J3UiFt`8x$MPCr z3nsD}c{zW*umMzz$xSEFJAa8g(M%p@nME(+^T#W;Jb&}?{#K> zhVb8u8#00)GDSuqoA9qogYN`g^K@#)lKQ`&52hHCE&?C>(Fd4Lu10Kh~#6*DOM!(Pl7jd2a9GR*} z6ky-NN5{u`;J5%=QW+6}49+T|Ko5lI$oSwO$Ec?bBINnn{O%e1FTr_bye}%?U0`}Q zxYNiXU#)gOG&BL3)9!&}_oEVcbjyO{%!3+iCo;tK)9h>_K0f|dXAmq*lIAZHuFe`x z`{id}=Dw1<{yL?B|I&y3qjYD&Rx?4Y+LO#eS8W&e;NJ5}CKT|Ht+$@d1wt|apilwI zM5}3!aB7Pe%LCaF2-$+I4=(`$G0mln03TzrKZf7=_6pha=mH*izzZF3j1~pGK`HUn z(b7@^cu+iX0*Q~ObUHap1OZPZPzB_SwtV<74$QE&z6J!TmZ>v@Sy;#bP-C*bN%-<; zdVVpDQIFLgr5VA5OdDfsyq)0oNQOOAvPK;fMWs+S2N(h=*5ry=U7c8!jsWw1B&S8H zcSI7AZ}Ce!juT<#8bFt!wT%Z4ACK9A7%W+{U!VT3F~>b(14h2~z+o&KNejL#({$j$ec4x)yWbd!4H-z6bk zROfx*m*~U=tcaEn_uijoZO*q*0qFZU4fA*uuhzHC^~Y*|dCGIy2tbIj3a*W#QfQBP z7J0KHlX5eH#RYysnJjz3`#X?K@p3<2p0kfpw@xvUQHykau){vMN&yx*Uad@h03?cy z+yS|2e=cWaVm`=w6Gd^nxPY$)DiTV=LXO=Yr3 zfr|6x@`+1%k_L>=Yuc^L%ok@%?LLb&!;k+&=>&|+`STmwf4DUtO*IQ!6y=&71nX~>FCi`LRs1$|Lj$({lpN1_kGhdnY_Zg58ZwT7L-P43@v*(?>u z@G%rd-h-e6^TdgPHsEsAt;V1e42QlJceZZ|?B>jg?qdWU&@c8IS4;txD-I_@z+C)D zr3*-7lWCXQU35k>1gHS-CQ%G=b7)7c;~}5qw&G=>>~but8LigwwRYpB;j-7aE{{Bx zH?#PD;4$V5TnzsCefT;XxX*Gs{4Dc$DR6Tr0wQ^0?K?6^!9gq9NaT&=$=1YD;z-25 z*;Sni{(+zIUIqa}pldJjy*H2I3M;Xm3Sn+Rm17tj1K?y z_<%D<9Jv%}kZK%gIazLhDJVm*SsB=3$s_kVvz6~N0U7c=llia6z%A1Vy3w+pCtH-j zBK^rli;?R6P1CV_pW$_8n->MJiZch^ejU$p=pEPkY4@Ha_Icid9z}t9*o@iC4*QOX z1cN`OU?ns>#23HJ!lx|8lEMW3pFx=X_h`sV#I4&bRnS!+BWu492I|qWKy~Rp-)qQ7 z``NmbiPHH-ogwg;V8f|D1pstI1g=BP8K3*1B=Gupss2;HcqKcaT(?OX@I`2eycMOg z&J_yO%4U^wP2p)u&EsVMom7&zM~`dJJ~Q6mU02Cwa4h8@i#+kWT*pySfshY_%8sR2i2CY$hQK42@0BSMU5g4TjhLMJjg+EZu@ zN=f1s@8?n*qbYW5GJt%84!z>LP*@**`gI4^e8_n1|kQFUA!{APo{9v6-EL z``Cp2d&}GwvhDat^9e2=4z=ai9pEzYqi&kJP$g0%LdttGZQ0?p-e z&@6x}DFKMit1<>IXG@WQdm+2ScUcLH02velZu*C~8KY0HhbHp+@Gn0ILRQHTH}m6w zP$`bUCqKS}N zXq+bQBFd&r`R{reE#)7m*>9UPSNTL1I;Q=`I$sx{bOl2)@h@T!UXI>T9DaZQW=}k4 zyuIFPQ5*aQG{8PH9yHo<_va@I!qxH=rFkN#9zSDUYjGQsXnFm8wKt-SGJ&dQiw@+f zj9)|x@o=uEJ(+A3li~6@D|h$uD$82jR4%Y)H|TSKf^VgT_@blUg#|y(`fW@v(DiIe zD9AL{IyEQ8b3XVxY)J|8D14jvsUwHt;RbcIzCZ3pP>!|bp)*b z1GMh)+naxL*5ERrsY63XT@kBifsg~lEfGlqmr~j8u1Ba21COQ(-{H1pG!)9l2&3S? z*s5a)dD-GTK^5v>50x?OWHAQ6fqZ>D_T9X$UAX`O<(Oto1PyiWeKkQ&Cmu(!{mtZ8 zDomMrRCNhlqZ^e1d9%R~ke>l*AN#0IW_+~kW-K-Jo7ouZeZTjPjJd)-J2A2>33@vr z1UHB*qX$kuob9>l{9g`mMh&o%8J_=}P8^I3S*HWv>sses|3!@BQ-9N`g&O*m#UVKm%uRiTYYC>7ZCjzwL!$`Ch8D$k3-r!lKH%k zyZh&2?qwgV)9ktU@$0vh_i7B!g89hl4q-!>VHp`)Ng#pO5x)P^><_-*n}MC`{0)gU zP3&0h)(v-0ZJ5Ob`!|o;d!dU!|JQbh^WAmW$&(P9br>5@Wiv@rY!@4rY#sTsT%KKD zcUB$im>nhk$+#nShZmKOCcex5S|NPF>jg*%(<@e?`I4E!?@dR`+g07xKC@(nh7Gvj zK^J2U#K~AcvGz4+nRSL-@z^AoAunJ(wp$;|Qpl@hkEz?9d}eWL!@W7{A8^a=XAOvT zA715^>ohLIu>kttxa3QQf4(`E^Em#M?YgPwSw+xKLXv8G+Xe5#e>i}84YCb z=n0;@>>uBI<;Fd)gUcHSrPDYv2n{sBiOa^0m7nFox~h{Q4&n98`c$XL8UR%yZ*_?S zIE4(0yubVv!PU}QJL*JtfiK7MW1MHfb#e7XiRfK?w@E6^<0xL!*0M zWHcq(kA0tGC0!(8CC7vhrhJc1+fNwcFs9TlU$MKR3hgTuW6< z2UlyjnWV14zmngh1-FO=h3K8@Fm0rMhn)T1M$ulwDWV~YL=4D~{5SLvj^JS}h;%h( z_$xiJ3k?Fn*r=GrgXZ6z_}3pfL0~@~pO7!7__LpYE#+ZXc}fbdjl@pL6!O>ibwEu?dKMVBl4A5~Q zFjrfaRI{;vT^k7JI2xTJqmss;a<88q&P2_e|;*8ZP-@qfNSFbQPLr}6sW3mFd{23#k!W$M#Fyft@9U8UHz~vezw)n zfAXInE?N+d65IsPoG4}De_fm7;o52YgJq;(LIMOSq8atLI}I#lVo1eeV=2PkG+5WR z{~Gx>e=uOqFa5wfILZCza^l*6iew=4&k@$y3V}V;2paokUP6r)&52xA2SY!B_ z-rxD_sCIGPZhdwu8u#X*14Zhs)e7Wx)#Vo}5~hs#u^NlVm43d#r7WI7L!VKM~|C} z8dJ$EeGE!zY6IWPguUgqzL>stMPLI%qtTxJrQK)F(#Uo13r&{KS?UhLPw!v7`(JL$OE1W*uMP7O4(_ecufgNhpB`It7 zp@JLD;FhklRHBfI&42HCYP~(v5@lpXaB{Zzs$ft;jod_>DTUK7U~lChGbUkWYQ;m~ zmYPPnFd7*8r-~d#rat#-V4HzRwVA8*(ndP|{S*jBV}#=87t}f!Km)-ABdGr7O|q%O?U`a`0>vrzx` z>b+9Pw8fad-8;ka>`rmC>)k4qyV(#PsYlR2Tw6qN)}>sfua}Y+7{TYmZJAxJT`xUr z|Jjm5WQf~**s{c+BPN~ywvn&Qba0J9E|ntzbOI3Sn8AI=7K8xo!(wx;%9DTqmqtL{ z^K_2}KrYS;{JuBPmx>`3x7VmI>>Aa5?=CIg65cXwuT*8lp}`>Y07ab{!Q9UwrjAlU zO<`cZP6JS(M7TOLqw{tkGn>&u2kkqKt^6D z6{l`KnoNu4TI79QF_8A$0tHzA7b6jH=mPj*+xcd<5(%gtK@{vZHL!wjVLg9ClwZ=I zV2QWos0)qrYaQvr2kS`Zc>Ys8H@UYM6tSQmJy@c`-z5TA0{L!L`bU?cfF3eLPBGp0 zZ9GTByzUOr86rQ)U@;XEw6bDw%Hw++e$7E}6M@c%JP*T)8RI9a%;=eV4{YPNzcj_g zGZ}Qk1*nzz=4aH#LOKD1)YyI?HY6A7x|pn55d?aV(C)Vc5@$p&h`}^Pi^5|Le(ekz z-nzCZ)w}v0C4j%#fc?-V&fCwDDobQAoy;Eq$winpNtb}QmjS%9G?yc<8^B3MD-4@~ za*KEYv$9d}?#5ijwhem@8nX;2Xk;Mw(J05cLGF;K@2yJ+8D3?hbV5|!4K;?E#Mc;L zak%q&>@mh+_@iLV8?({$c^X97ov>Y}xgvXkzB7<|D-GO6@XmvOg$uPj@{2W&SHrjuHQ_wU^Pb*_X9N2@ zjqBk|?eTzWS;FCyhqOf`bW|dB_&Op@wjJDpX`2n8nTH~TWF8JwA1uPj11GI};D$)u ztOw4qjzI8ydDc=Q{AYM6Njm$L%e(%*BkkKv2F0WnavyXQa`mH+U#fgnY3NwKOPCC4m}vEyIm<`{Rf> z*|-rgY+s%I+$g@uT(d~{e1#5U?{<`5smh26>C#FC|d zB+>npDe&n|ysXeJ2uo!ci39a+iJye&6nc&H0I-LiF42*=t$_KBo}BP&1i)?oX!F82 z*msX5e}z$^S`ushb>nFq@r#PN3Kb1*w{6-i=dEIMZjBiv=8zK*H#G8StUunqeI`=Y zb4p8yOQTXhVV#zZVM@u}O;{7+N!NI1C$*34lf=P97sAsw+Y_4m4!0NIc~$y2>=yGh zrBdljE_*t}k?{swnw-+Ozj3$UUD@>U)BBI%&_=(UMzJ{H_bI5l6B>#JO4c}3vRz;d z_axaJXzH|pqx-5euZHATH8{aCr@Jp6MIt!`OL>6+qrpvYFnN4Amch4M=QEbMWX>l7 z!&J1dX|+<>c9X50>OYwy@@9RbLT6GaO9ReG5^V=yw1D7&h1}#7^bp$9ASO;3mj&C<$2I{4n2AgY#rh%78^N|a%?Kebz@iI6*vaIOIPy{s!u&A%5!P?@au70c|&h8si|EWRc z)-IYDpHQ*30>j!dgw68nBiu5|m_&1kyxTPSCosT556QfY^GN@oYq+b_-wZTcf_ zKW#}#Ui1qj-J@F$ebdlVAHj?P&;qT^+|}8x_6N(Dmj-kiReEv2sEb)hTJg0_7qKMh z9j*bYeWc!J4%2?aK7+`;d4i!VU+(dkv37q!{Bhk1gN{yUmd?{p1{JMWQ%fIwm*A8I zZeM7u1X=C9NjYBzEAG298=$uA?XX#HeN2hUcI(bW$mOW&da%IoG+L;O1HaZ}6Q%a$ zZBQX$mnJBe%jN!H3IT=`f>Ygw-79x-D7_N&IowOADsh5y!I4nVG<2OL0!PP@gxnnC zx-%LbiT@xpe+ z0VySAa)BsQURcZYOty5la+`JLbYdwg+U+;{iRopBh(J;UDn zdDdFbTA%!(7pG7xr^t$^Wg2Tgtlt)Vjm39uO!Vj6<8+6wBANB0Qd_g;h0@w=0bNhj zx~2dX{TD4h;UO{;l#5%~ndD8n1&8Z#Ud-HeQ-N24JgEli1k#?V_g8YuN|v}%jo2A# zohe*jo5c$W!;xdPt5S`oPe@E=L@**w_g8$qDP{{RX7~f+hiP%*t5|&B>Yd;h(^wcb z;7Ni!pi6N=uUU7<9<|PXV$$aQ7ztpWw%tJ4=|SW+Wk_j)d>Q;=>TLbN&?{2sxw@lP zcbo}RubicU(@!@ErFOb)={|^(5xD5(E4>Nw3(B$v!@s)9Klojf^d+A(f{J!YwQA$H zK&!|;vvapM>tS;%66W*?N#t2P&E<2R;qGADPX!Np|I;;3>f;Tkw-U`$!w^{tSoAF;Y?3SaM`O1lOrKen;OlT{r6uyEt zCC!{iOrGiCBj8E6FY5We)*mOnuB2AJZ8lzyfcI`P|JmIQ{U9RPjA?dEBy|m;T;z=V zMpp6m#XDOS+XBPBxnTC*vO&h<(|&!vUpAh(p&FzAL|>1!e0C1bh+1g^iI+U)|_)}7G9xk0XMs(7VK7a;O+MZ)guqsgS9vh3+X*DhDnN=;*^C$Yk#)p zzvgC!+JjB_RwWUQ($qL+gL2XCINKF`OVKO@ztSVg!=<`^VD+u8+zHUGZ6DPsW>Ofk zc{#IX+yF;RY^CA?`zSJM?1Sv%6yIk45>(Y4c*}YaheexbA?*veD|&UdbeaGr5$_6% z0i^mrcq$;vg0nLH{d~7qmJS+#l_~lqwal;ZG=4CE-nh(rD0iD=wSdGsQBa{R!T&|E zP`SsbEhyjaifgMORb0pn&ZfIo@3d{0JO!983r>|JdzKer#{ff&h~1*`eLy&pGr*F$ z3_%I+ieffiN5qX+>p!*1ALsxR<_%cSe_WWAz<{zk8g%AZ2pXze0I`#zfF>jTBwgsF zp){+h0=uBB_=#?v{pNSQN@8CbK8%wTMm=E{Wsje!rswA-Be_XGLBp+jfnqWB#h3uG zTA33Qj0{rn!|lwnaZPhrCNsn1P|PjAC~~mGOa|fHKYJiT3bf3ZW?nr*vNX zCVW%l6L_${xdlyOeI8gxFmNa!6yVEr67EXlOPhDXJx=&($PXCyU+Dfi*2_RXFI*pD z1oy+@g(79umJH1MBJ;LTki6z!##ks}HS;{ftvuPuQOrQLTOztUlAdaCDXg@ZPy-|| z-AoM(B50$EXupX6l4V0a_i(R(UI8?=Ne-f2VNR4ESmG6^na4wN#iGPLBx<&vJnuJb z%0m?FBzrvo;%|0;Cm6)*|D-f-1zFOW12!*NHA^Gsdg$oK8&Z!fW-k2{dbxUN&G#q49v zo%*qCC4{v3mA@rYP~eYdcU}YLpIt6kR3kw z3n@#gH#l1*?J}_Xul1rWS@Dtj^Yq58S-xfKWeu!rTeN$`Om5GVAWFgj;a~Q>nqW14 zl*$vositPWtx+q@n6{84%77hskBye8ysX!_sXI>`hudM*Yeyv;W2m0T(6Uo%6Z$A$`^hG;jw=(H)9aGJG8;`@7mD zlO(*c{C)T`r}KBYs=3~kW$I0VP*Bx|Nw1^)xH?aW6vK(&Vb3h~#DfY7i1D63!;7!| z#ayK5SA$~J2wpM}MC_dASKe;JiV&eG=TnUU26HcYLA?G|YJXkAt*0u2c-?-6T&lzR z1W%dXDIwBjqzcgaXOu(9>o;aUJHr_MgJX!2f=yV&FpgFF%L#|3)If~wLXx9j#xXeL z!BiCXzU$=%-QB_TO-wl|!FdqU9XL$NOmtJ#DqV>i8Nj#3BxNo=&z|nzGQ-0LWwZ;! z@KjpKXX$Y@$2ntR|AR?FqwitOXnP@@DTa6NRb0Yxa1Wev%;^j|M*-Gj6alI>`J0p0$N z_h6~px`cYpSKKw16kk?UWq zf^Q1AKRQ+jO8=%Z{`V(I!~LmnUM+dz<6lEy2W%gZ-EhnP`@i`SmO{Z_+8_VZp!F9t zt#f~;CzHIN{`*V)?@x%;J@Q`jZ2jb~D}}^$4^GV^75#&Oxc@%<&iCWBp^BsQf7!VI zAz&u&vFuYy9pt~@@>W9dmzIiuyEOjS{o+SxkpdU+|GyRg8CU=Rt@z)8|F2*EAJ2-~ zQvRRzn=3~Hf%OaczBNE2qzXtdHI*3@S^!ev`2;oB zIFH9I;*r}5hYJJ!`%nT!poW3!(Ea)KdDkkH!{(1DX#TwJ!yS~GaD?pK3Imen*USu8 zx+%vl)($7f)BYF+0g`{SY&H}bi?76EY2OfWRX(RL4auo?JKc*}>H5|U6x;?2w>M0H zf&p&nl1kGlLEeiM+$bvb(-#?w-R=d!;YYp~N93yIE9IaPb91H?R{GQHd?|>@eNyJa zWL}y{zmq}x3eipobdIjn#K5|N1D%f8Q(-mcOS?#pa%t)}QlUrhy!Mi_L=)t`<>|2N ztw|)XTQ%L&AWhvLxIm$~u3grOw zJcNj=-+UdmNXh^pntXs^+V75QdS30a^U*LGbhFFV%8*vvIiyW2I-lZ_2bT`)x{F1> zj_gg~Qs%wdvEwU^;SvRYI%cC@29g4WVrn4XK!c+B({oFMT6YCY4Hzj@bdS&ZHEw%& z9G!X%au55!h>=C>xs64ldMg-|M z1upEqL=Rez%Ts2H(>5$VaafZF1?Y9K?6Da7UN@zcWvyFtMXGLC2KP)5 zWaeinSz2F>#{L{`n*jt#tbT!$*T?yApVx6aIEzd3|-l z<4buXsTy3{=&d}ing+ND({RJkH*e&DPN|hROFbCf< z$MOQct*X4!D9wvA?O+tX63_Ia2P$j>FQ5n@$R^l&O{4!985RpUwmyWAkN~v;CI^`G%VCm z2{4{sfira6Y@O|9$8LVFcA3mQZ`t`=?OA^+kJ*+Ne8J7K(0#4fBp*1pT&8~ZL4yg2 zd0fc>j2wA@OW7oszro+2Tp3(+apPnssYD9>Ub;|gS+@mK)1^-l1je&h3z-cifT4?J>TRZl0G(j zrBSh2Ig<9&$pCqx!VBa%$&ZMHzr2)R-mmY`R1eT5c*W(kdwPDDapNnz!#?AU*W{+x z5$Y6;{o<@Kcl{Ta%gpI6(Eh#^@z{Nw%6qrhBr^Do)nryhl9g=gEBZlCnQK?{o2Vkq znig4Q>*+ePxF?-y3+6rFC@LBX1*bHKYAbOn4a#IT)1UD%Otn1_DbaXmFn9TU)qJv6 zHr(wXY?NP4`P1do2~92}M=!n6yrJQ0O*av*2b6QgYHV>e3!XC;DP@dc zR;_=oB^$-rx5y(hrKhl0eo^2Y5|S;R(%o>ptLzu2`1?!2J39i$V7 zIGr`+?Cv0@^t%TMxj8a;AN(v_KyA50R%Mmx`udH0)}R|hr1?UV5T*33Fhly1E1$(| znZS%k`tjDB^3jC`3Yt$T$Qu)O$q}CHiMx9>0v-hhcl&J`jeI{7A7as$q7Rp9^L@^| zer{}}J7!h*;%rp}xZbx$wcCRVtkQVbChsNC#WOY8CL`f}KToi3!x?BmZfJ`6Y?*9R z!eL5vXn2I)Y771K4+QGR6MM>6Dh9~E=DLHlVbjf?@b|&cA#@U;tda(FF9nTKNwioL zCE@&Q#FShRgt2uaFJ^7^dt&`LF+pg2&dZV6FyJwe9gkxO0x+mkkdd42Z`b?N2}PzltAdKd~tgcp1#5@MD)A@p6YZB06XO7GD*z@U}XM9<$4qM z!SZkR?5sf}JVXa2PMbge@+h;@dtDyz1ujfavrq!oXwE7#uBNYp9Ezn{lCGy4q(A}P zI?D6_Ek7HiN@UK`G7q8VkvkpVVF6diU+H;AcITNKdyQAEH8jz3 zzTEKIC9_Kf#3+L)Tpr;pg3Nx4SVP~1qiEtvIZTsZeGvD7 z8cI8N8uiD&{TNHDWFcd=nR825W)&1Z!a?No>mw%E7yQW4^XCRvKDN?CE(vP_P_!|i zx0lU@TCqT%SL+4^on_5Vz1lLSD~giSSG)PgGf#`NyGD9CwaU?OA+m(56n&W7XrAng zTbuR%%XpjLvas?*f>}hMxM(_@(9RvD%1vTK^}8UxR?@%~9vB=tJnbh3Wk2j>=AFzI zm-clT2(x*zdJ-M@^seX2JU8%w=KC_U{C!Usllg`K`>ok2dlIAG_%$|22h4P!=`6g_ zGGEd-bx=-}p2HT83;Y-ebI>CeUzRGDHq9pu8H706@4u|fRr&FU z!xy2-a%>o#qY`xZR-5Fa5m}=5s%$#!WqVI|a|U_s>cW)G6iS!q^`f6cbG@e@8l-35 zO|7?Od)@i0-EeCzs4`@77W!_551`aCFtF;jI!|RFzQEh;aKq-M11T~euqg$~THL() zwpfSDboD$2*hkNfu+5yjVfAM_ktkSj!Udi9tSLl?#Uy>{)Y-F#jmsd{(zwas)K+i9 zK`n8|Ze7c`cGsn76$FBRoac5qZk%k>D1DkmRsuR7Ul0x+!7q7yuY6d&52~7^_oeDe znv_nFoC1MNCedmRG&zSr18*vf1|n&w75s>i0=)O&@r-&O^M|t~hLqE;*aRLNc1Hl| zmW2MJn6eSiw97N80Dg>*slZ7hI#xiOaK`9AsPaRv2T-qlE)C4C_)&rtpRQ`0;Jg*F zvS-WcHIgW(G}~^tr~@32m?sKzFiy6ot12*CLs+h~PSbAGif#jM>XqspC5c9p%>C8l zpM?=IixX@$-po0TRMImwqdQg`-+oH(^Q7H6R4GPT>r>Y$sM6|tGvs+SHnEU8JJxa= zc3e52M<3Or8}5A+N4KM?9o+C;g>x9}@IBw;MUHnDRHKrCJ;TYj0g(`Eh`{L=AqqZV zcW|(8%=P^e9^rrE{8`Wb1c-9Nb|}PSBRUHbrfW?qrXGbNBIuDoe^FLh1hP>%Y_a%m z2yXai`XTl8E0c*NtO^BTB`lKS?Sh?g#HO(>IU#D3ETg@D83?a7Y^*?Ia#HwsIyah+ zxeL|m_oN8<>2Z4jskmyFOvAz~$rTxxHzeeAvTpxiL_mN&TBY*iyX9B)O6SOn+UMi4 zA8B+)nO%=-UQm9jXu8w7ojeP2$yayz(>^p*MW;@Lu$dE@r0^I=q}(KTRulN{;&fJ? zO}CwFPjts=hcFPmaB|W8VzW1^O17vqHq0#vw zyTR{2hd)OMP}zM%+D^dpJ_uDUb_u9!im48pR~!np;EU3HtOn+zoRUg2p5*rS6NTfC zrZY|CbH%1BO^DWfQrC`Fcn?bFYtO8;|Gd0i>LBi8@+ZyG;&C&DCL!7`HVagE5|Op~ zJ=)}vl_1%lhEvT_jhT%yWHyc;tdZOV*%c{Us1l5^sc#}ah3=ax6db=2PmjY?0`97I z{T~Z)KFM{u%CZwN^dMcp51E%`@XPIY1cT4(QnyO@<5v6zq-kEe%cxG< z8s{ga7&{l~jMs+UHMaYUq$5*pM;gG{CKBs>@Fzjm+!zu$Na{z~Xr19}IM+|7STIUJ zo?BoK9~uey;&wPMP`{>FL2kUXf4%%N703b{zaQaal)dXZ@)~4mU4WnxFoq-&1KC1A z^^bDvA;HXx>hEQZ{f#SP>d2!nPu-s8^xZO^`pfh0KA;>cRE*-OKa0oY8fV|A$K>_a z`Q(5H=zB_$aH6qAha-5=6J_r@< ziM1e?TM^|nW|12&zS0axw!CZ-TR)Y#+jEniw{gA=+~gWe-RA|unY^iEg#{GPYygcE zlI%A%)1&4%ib8S%))&J}n{PfoR+ zel-3-$*ZNO=i^J>WBgyPo}Z5FHot}Ud=Q=sA618`4!V1s&DAiOjiVo-FXQ{1p!W*M zo+*o+n*F#WJcUntKlIbD;Uyl#6g430r z@55!vZ>Qd|lwX=eJMhl9%_EhQMXO0N=(`J zjA>vHaKytFvxzCtZbus^Y@c-Hn7U=1DL~XJjU!d7?{l((z#n^0=hS_>;5E>jmsItmBGFu}%a|a|3mvSV#ukp3zw`7` zI8PL;>C!C}G{p;{U;6)mDga{OxAa5&a4r$u4twwP)^u5y~*Xc)8Qr9MY z61}XGzQa1?FTIp}2fd+-sI%OB3|1Ae$J{RP=4ebrgx#4MW=eIu7%#4p@VgJy=Bg50 zs_WHv(~?PL9xioILtR;pW2u0t72vMZFMCu; zT|8a=D&TA9lM^f9!LTILQ(|UAbUmLXwYMef{c~J6iYQ1C%>)94g9H4U&GxpEWIpgNQ9Gwh##UjRHeG0 z4)=C3ZdhLAT-UbN?9383hqz-|@1fVzb9ENW(ZMGs35%mL#qq4Ro314>TVIimaLJ85 zfzxm+@%N7~tf6U#ZKCax$CGf=XSIYmUu{P;Tz zHHHa$agh_!2a_W}l)6*c6pO9#AkiQ`JsH?9&o>q?no;oA>;{|pjmw1XJf1>Z*_>PF zv{sc(5U&nS4bB5zbsJsUo2>dLe&9JKYwo}$(l4iyp67aJx5j`-3l0hu#HQ}Cmb2Ex zRsC<4k7AeyJSg?@0=diJ6i(}IQ>WI-fmMCDQk{e{T;?+cepu8y%^E}lKuOY}NV-jQ zVs-pLay_=Vtn!(+CKKPu`Qbb&$ z3FWK3Zn&P`v&Ca@;zO}sFz|C0!gs|mRALk)i{>cM9|1&u*d)%w`+;ZLf{DGZc@nQj z0b|OCMY;xJO37prAOp9H|IGEY^(hV{;GV&16+SBMX6R8VuD+I6?1MRX;VctAin*xV zWPTB}RCLdg z{CKBboJ);$mrMPnkcY3fh>YaHa63nz z9Oa*$!$>#$wNWWqAg2hDX&%SV$U;?WlFBX>+j}grHh2YG9*)h-y_0>5l*#fI;rU_f z-pd?2`lx1`gjdW=3%2L3ma)dG2ip@-XP7K(cq=Y*VL-F*`{*L0-|k8L*|+-b|1(Zf zE8BM7*=3tl7;=`5xa%b*(V4Na`oMP?gxZp&2FZoPo=i#44~?QcMU}-W7T_#QO}%aX zh5j&gM<7)vc0!wL=V-l8Uc1}3oIexE?VIl)?>!31gA@7r=%@U-l}os42Pq5@L{o^7*7Kkhwc8g(FwE zAWgSk5j^bP#}K-EiV`#7L?cI0*GW|;nfG2G% zwip`m!%53^WuF#S(ume1aRxIVZH>0TlQGqWyYP1`3Zbmd2-jOA{>ahRDPyApt*jaH zPFLeZbjx00nuGd4Ek76E*J9s)nZ&D?a{=|p!_McH+;OZ94Vc!c4tQAQQ5su@Na*v<^wiM-H&eegJM@uovx}H#0e+EYht%2N{uGf zhgg5`TpsoRjOi%V28)NWa(w9bWh-esjj}zvD@Rytg;9^C?|Jik>eqV|6Gu(vZnZD1 zw9ovbsCK>iky0GOa&1j(bj=ID#8BK%aMa%5;@Vq+L0j6O2H8Uf_6B%FC z%=%~`Fh2=+yKNSiy?eWGhv4~K9mVZ3OsIpe+%stBOu|1HyXe#PHIMb8x9NUGQeMod z1b!n^y!Vq+3oBQAPuKnMV&$6DsDQ7zXJ?rryiGPGYq!p6P3Eoaw|ou;){^`9H3)h| z%6v5OPwUlO^!pM&0Nl<3!L;sW_z4JT+(H^4>w9bL0ndMv>7j#d_#Kbj@Rd)Q;J*S1 z#N6?qEpM7Q8?WN_b^WDdd0KYLLZ&~p>Ef@Fs9`5hGP?R(hz5+6uI)M>gszdxen%=8 z>iX`9laRtodogB&+C*b5B?)}0s1F`SVa1oIpc*PR(wrPGS3_M*mi)k;`L zKnBUlN6?zLP{aPu!f;tno$VSEk?Z?J(@Y;Z_x{!1X->sGjk-QofkJ(X$@UIKC0JfK z0nT~5T*fZ2Gblalphxf^;RNZ(tjWA9N>^m%(L4f#3_xW!IGjTXy`FqqWF%+9GptU~o+>)c)IT^=|xjW+Y( z$c_JScs?PC!{&QKZVpF$NwYZQRhiQO@s>{GxVa!v|7jlQ9ei9q__1&zy`vW?wrxMqcH&q|6-F}+%-+lJSN9@l?V5dC7%($ zg^rDVct-tZV{IoB+jXT>)otcahp3DBUrwI>Jo~0?5l|k!-7{rDW!3P+xWxx9`VVpAHYm^e1o<}@=qtW@| zu~~2`PyMoWrmnC{Fm0_~2itO#kz?((Sr+Lr_V1xvS`r!T%44@wGw0?GPvQ)S_E}>P zoWr;l+mIN}i$v8@W47pho1^q@w5T8L&mUK*-i6Onx4E1ck0frbEq;%g^MQt{HNvm( z^m>0=jWi;s0hk%D89#$jSn}Q%hv`7aX7;t^wZ0cEwMFQuvkPUGEsJlE$SRq8cb2|> z1`JEhuQ5owXdc-&FSvp+jO2;h4dzORhmUCf*Jt$Y1&keOz-(V82*U3JrXHn2Him={ z8KMV+RT7ZhaBl|DBF_xB5KIjOuL$ zD%2a3n{%Kf5vx~s`2G5q)@vGQ)_BoDf(%}clXdrp;7zdj7swyT*5zRbGi@fTe8#0< z!(L$Mo~#gY!exBiLbk6gzwTuMn#mkl1_-GpJ`#{1_&g@zS9LKpZZb4&|3;$gj@yN* zM(m#<8ON5=&!aSPo)Ys$zTN+OYKGcN@^hy5qq6ORTexFNMD}LmMI{yeR*dw<5K2hY zV}W1d`)LGRjv@W<7>)8gq4N*LA_yKXXYh#*iQss3KZ@%NLllsd%hdYe(teX7Gx3kW z>vxVj@rofM^vP~ZA1l>};()nltJ117LRv9XSz>rYgKFbRtMp@fDT(x{GFgwOrakc& z`cM;WyYgo3&cj?mfk9Ir{Ix5vk5GL_hW3hcmx$9|&MuYNuq0oAp!GR1fxXPcb_dQ* zgihFbOuv`E_rv&oY}vdpkSZtFiYKt|m>P_DqeM}lJCjUCmbj~X0R3(oWrneXslNu?^? zwN-QzXLNncV|YaOmh*AE7bdaI)lIY{3QjW-u?=h(WVWMUf%pn8WZK{t@4lHBaeq}3 ztgoGq+lSSsM{x-;m=~WR`j(OW83)w`=4YxbQ~8u(3~?4CIWeC*6FyRz1XcH)ymL~1 zCTNH9%xPO~e_NaIy`tnJe;4CN#p{RBmrlonXAnWLT!~0I6sJF%6^&EIIE52=IZJ~{ zNf9H?Z?SE#j)1?_h*t1dFs5ZZE+$a{Si%wtoDb*giZkberIh#)M;24Y3Jg9Haderi zg$c1RPxxD}j}<5>lG4LgT+TF^!*#mkC=7Zxei)K;b>D%7q`tb`i5$^Y`;XR1G&t}v zoF#efmZ>6kkcjJIg;JU{E+43iAV;aWhFgq<(XJIEJoo3L&m2Nfr`~SAw3LYDPH2+9 zOgdvfFCKA9bZQS77O!eIKEt8TBbC9~yF2F`YXM+ZFBhio0`TRyV~7e=+(`IQw&X(W z+;j)A;H2>(j(f9X++S0h;@)c1rc+wq!j@%>j155>PT@j{>)H%b)LuTp;Cf~_FH6S! zK=M0_@6eyTX7LR~XSX7zT0x)v3G=dl@2>k4eajWK^han&+U>gFT0T2kkcQcvf~@Y8 z`sf4Jqq*j@>{?7pL7~s^uzDou}@DX#Y z6Uv%Ilp6QHwh%=JiTa+GkS*0s7bCVJUY;;{txEC_( z@Lyc>FRLbQL(8}Je|o%}*j{tppn|r{%xtS}z(V#Nknv)KAc}e0jA#L3p$CG^Uw5`k zMND(8{2U)SCG019%K{PxKE<8V+L-&Ze&3{b)Oyo+;r#x6hmX!ZIusCns$#d zRRh)zflf#npVEj)y-UUR0sR$#4uQ&PWb>T^9d1e%+^BDq;er^yhO_;k5sHyLt;Iq83zRm(n&hddcJ^`t;I#)sejuElXTH`F!H`h-NAngmzwA zZi8P+u6{0%rLDC-_pt(6zWmdbo4zZJde48|UiMINZhTar`DK7XD-{pl@4f3cRhT_!DzWL^ zkhPUWNl8~VMB_n9&+eGD=5gqk1u;54WkMvMChMMPf%AwLL6k^63@I;5Jf=IZkI{vU zg%gqn-{QuwAn2W1D+mwt$VyVU7mO}CpdyXAp)74i0+-muPepX>4wPq9@wK{AwDk-0 zZ=tuW_{^@G8rBs;zfQ_=ZgIWWust;ZS0*kTdMRF|yKwIH;0%a7{W%dvg9Cn29>H46 zU{*3Pp8|MVr#!3XFY>Lm;kWSs!HBV6e@QJStOSV@E$%I1~ZkbDEs-O;{I@Z2#R07c8A5BtbP?z@a zif?!V z1-jkW#^g$of4yLSuaT{uj#3u-Z!s(s?fxDkhyJ4eOWEpAcQ3 z(j!$q{3V_(`v8Qhlcv&I7=I~!U*89d__NA^Xej;tMS*Xe4vA1D=CrN%A$YQXI>Ohj z=DxrG{0cZq?~RJ*N%Sl`zqsGZW(s3e7kuJPZ*V!<@ULR@Eym<#{Kp^WU%b~3pG#=h z{o;0+kD*Iq|5#NpR(V9c8q34R_U7ymN%Hw7L^5ljy+NcgeL(Fcyul+l@EYV@$61uoP!uCwf zRj(mo7^}k%gjfT0S9vqU8#C>S_cfIoY`I+8cfwY zm8QAW`u$@BkXYZVjx|zCHK$AF==~EX-uAX30Cbt^;&APRxdvqZy(6n33qkztc5e7OB3=3Wz6qc{*hr0#irKs8nEQ9pCz zQ+3drFd|pzn9@w$ul$aI%wnv(E`*S8XoA=Be|6O07YHXX!l83kTYe0zo5?GM#fsr} z^Z;&d^(!_Rxm5CG(;-NYlDiM(0HU5z%sUN}$ugf(Zouls0wrb7X#PU?@g@j|N|_Gh zetooT*cY;?0g8l)Wz8U!=^oLQ~x)2g{o1FWyM2-#fw_WP?u(!CHD zI5SCUJa2wiqE7dUpX%9YilX9@N1X0XCz|!8@+2kFI!3aBC`F)5&2rpT8_)O#;{Qfs zpONi|#?mHqgPy>A7gzHx}Xn&KI; z!qCcT*YCx-?1;psppM5muDau_0Po zhrrQ~aMX;hwqPuq*%i~H{6bMYL(ml1gkNm$=N=t)8;{GDHhEm3Nn8RbUfVL zhWA_s(Ct+7^Yhe|*ZN!5U}lTpk0XS^M}PPMJ=M(g%4v;gWb5I$x>bJ8ez5s``BY_o zeaPugkK-W`*WxsB;Ex24D(W1`$14N$Nu%^-LFM-m0@ygprK)6iR||I`Y3UA{>;0+B zzdMF>-m;oqo@W4lS2^IASkh&zKrzPCb?q^H+vFy5;FF>gk|=}_vkPz*Kp*yXfG&6-MDiJs zKI*;x-o~x_hn&oLcLBCw?x#HnJWmP{9F|*Eb3*dVtA#X;|mSE51Pg2Fpjtn;-gl`BLRoZSdjMD+2{q&hfOVv z>k0=a!gQLFxm{Y#>)p;$+hE0jc9&}u#@L+9ufVkFyOVePAldZ$ zYh35nn3?`rDvu%2osQPDrlfeW{mC@z3D1nN(sL6}h(GjagteFCt>G6vB820T6fVU! z)sY=~jjV`kpq&+lv;~4>61zv&d?tz#=@!ex)YNbDuV1 zR-a3lo_~41oWtbNjt-oZ-_n9oI1X}0n!r^P0sg|(Nu@j)(x)WhoZ~+pfU@G0**fj_ z8vamZ(zaBEoM;dVIL)503SI}b__p17ZjbXKoZ9uHC@-Y8Y(F;eYLRRr;0tz{>6 zn@^R;`vK*(c`sf^C=nURNMhL?0Z+U$WLuhlY%9F!?|s?<W7pvvvCuJ+kM{vMRBBGf&RVQM87m4B3C$~Y zN(_>RjlY(1hED?)EBT9G`wuJF?Qo6*jX-n<0LHpT;W_!A*U@v`_wldH&#f~VX_ZTo zr=BzCg!2)OlX8HE(NIQX1!mwN2jGVeflu3@bCFay$tXcf%|&p=sy~_6_c|njSz-G` zFyiX=>Rp&|>Y#!kM_>ZT5TqE3J`6!8zO73%d8BFIG1wk?@(;__9oY39g-bzq0R8DZ zTjw!xF47E8*KK<%dzCkQljnm-l5@zG*yQ*yTWe(|6Tu?~6g(YlH0dIdtpTe)6AyOf?WB4_h9fsGTCECx>@2`wmz4(A zb5FO%;qwPWuZ2VK#K?3~9o4-miI5J&q9{KmFc~NeTD$=QlBhEpg=6K9Th$Df|0gNV z#~Q@oO%j^hBTc@()RMhOKNO*~Mv@}>Ai2aXHdSE|2ewQz=l(<-&V%P`D=zHeQ92Rr z#;vb9ZxMNikcfE>Q5%-bODNlSdZX-quC*ZbGvJ#~lr$!?xjq|ht3o#;GIWOG>0)w# z1Oc-O#$tcf*&@{iu4;j+XEPM#`oIoLbakWdEjBm6cV~W`w|ECqK$1bubCC~aIS1;e z{rv5@+){a2fQ*{0V=mWJTQ9w;z-r+o*=aTEPSs%}cqgZ?ofCV?ZRL6ambq`_`4wGe zJCmMCQfenNUOF)B@&zwg!vRf2_44zaI6VIN{OR)X;+ecRs~)ON?sbgo8a1a*U0Flq zx+P!IsrC=_?$gu$^UqouWI6BEA$5({%pWp3R9vSm){hn%IoXwT$|f6pvVu#5aW+eo zibV-y3OqEl1!Cc7rGfy_Z@)z31A8rIDkJGxhF|$=k+f)_BRqNqv0*^4+;hl(S9o>#)b8+kQ~p5#y0imoRO>1M)1i`I{()_b2DmMD4d zEHvpQ>5p^s6Jig1McADcq))S6VI1`?qMP z5AA|xbL!7yBHKSqC3GyEmabh!TrB5usu&lpZq61`D)EDLf-#-Tv$_;WsCSR!NOTc0 zv42P<0v8y5uAMJwyl8B1!s>TZ5t0Uw1gu$_{awei5)e8g7ej14nQ2gm}Rjo00_4{NJi8EZ~&eKDo%u~Ise?6M-c$(O35Q{-N?QP?`Q$QIOByi;z z_(s-@m!>>YHpy|glb+dn-cm&&mN=<_)sJXX4%*eXYE)GH+_+h$d1tjFn6V9ERhJXG zytP*Z6gJJx0)g~zaUQCpnW@5!xkd|>MR;%QgH7K_i(Ve%hmnDU0p;IqP)^o@*SVh$#K> zv02RLjcDjr{69bOEI6XCr9Q6t3tEWakHAh3SBc0lX!*oRu|2x9l4Imq{c1ueCXEpry zvIt=UFZiOegF}|Ngj8eeeP@zS^_DTdtgsyHxQuy_s1pa zyvJLZDrw{Y{)Yc|7mMVXzOm|Gw)6+6wZN_1%k$jwAJ6rFeI*}~d-HMUV7boU-w>?& zzVU8vXT|>;1mZ^!xOZv)f8&O{q7a38$wb8T`wAiZyb5?MJ~us9Re~+yVYSdiM7>26 zA0q+cb@T^fDQ302bjtNr|9Mj%;s(F4ezpdYK!Cb}`fI38qm?F|0DxZnifZ*jq824rmt(Q_;4k z=8KTP*Bv+wTjTj92+HCeth&Uv)#fKt{zqg3Yjj))yVrl6hXQ1sMk#2k4alx&AaPS=>{l2*2VRZWEB=vs`W`dwK^V!C@ z>^@2h>1&+s0JdJ7>oU(%jYZ=$gv_SAI8C#scm`^;JVR`1E=76>%h$C3M4Y^l_`!&R zG-2ropS4LpRs`r%aRj4>LIqd+t!nEd27cy8Egd)y{Ny0VPq>hTM%l}7RMaNWs~=_) zd!oDFK1_+a`sdVX<$ZV=9I($;U8(4!C~53O;$3&-(OO4KOr1Q?O&IT~=Ho!*BO8`$ zPNzFKa_u9_9xL#VAr7s+cal`kOiYXkkRj29QwauSzY`}4CU~R$6io;5x)Ndg8cJK- zO#U!Aw*H@QD@r1If3}iq$bjv8`>Ofp(D-jsRQk{-A;{yoPuVf^{o~}oKS_QM%2t~N zH&Vx62rvi0ym8#y5N7z^rrZ6Tf#VAEtspVlD2rJB7l5dVv_bX^VTOjm@BeD&%KzES z^Ehd$4>cyC;;0u1?W9vmP)ExWX4tk`ZAV93b)-n^9MwkEh#X>$3JH{O$ z5l842H5=EyDB8Gfm~oWIG_I0;v_qbKz4kxY{`%yb&+$G!-_H+u;`Y9|3H&3(GV2#| z!+WxU`x$|7m8u8$(g$Jl!yRcf8!8Yb|C5iLd}8EYQwY_`Z|FrTUXV|Z< zD4s6w$Ms$kJ2bR@Lr^|*iWN^O>Hg(*$V0+SigN>_=i$8q?3`cI`^lrUwwQXN4WpDQ zd69lvzh-1QHEW98x#|(UsA_`a#;v51B?E*4Ie8cyK%nu|mT&L~@4Gs&Hc3v6_m(Iw zS)^P#(sB7Ut804p*{@pdY>o;=GVE&IQ0TTO`tGtqOJ4T`w>s~Jjsc$fG03e8@(%M3 zLftA~+I?kxWXBuM0lV=pGu;=4Fo$#s61MIw%>6DFVmO;4Rfk=D#U+t?CZ8{c{WUiS z*;!N<-K(%uiCm(*d{5eErE#voveWZ-kx`FrPD+9{tk7z~8CY z%bPeb&yM8#G-PjnYy&Jstmxz8JIb~40|yXG86Jraz3@K)-Uew}7127a9ockv z2QxIjh)PAf`y-Ag?lK{l$c4k1+;;Cs#cg6fK0ks+r>Seg&+R@ycoNxn7xbW8UzAzEE6v{VCa{@y7()F41 z<=@s8LSlnn82Z~jOoUzr$KY+;S40|Cp4Dpc%2c>B57mjDRq$r)I^q^2U#K%mevFC`^Q> zv~?u<1SGl&0@131ukX@FW^XMq&LM!Fl3I?ak|!_&oH5K|YMEUosPm(Dk> zcYR;uwJIEx3cg=mG_lms(6G^qqLy#VOeofQE$6$*K^mjT4n~*}`1+;W z)gEUGaMF=5T%9vvnDFcj+_Wci;W;aPmzkDacxPHZV3+(a49o2=tSeDG+Z8XuaDCku zSL}D@u3VxR$U)jX*w|L;%9(JScg2{Af14sBXj$jPi)=xhNd$KHtUQc_#2JwFB{{-> z_C9LnMEwqUi8RKrYV{Fc^`DS|s{?A&o-d$%HLqz9Pv5DUgkf5RhAo z_KA9RWQU4kz(q{K=E;8xbqkOU)3uJfmq!tex|o)wnNsTfwOrgrOpN8)W)*OOpIaRi zJ+>ys3h<~g1AZ>EtT1RwC(3v~q*4p8cXiEF;LHNwR{ug9-|~8}5ql8ksUuhkSuZ~5 z(l3Z<#B(fUmqts?2m0(Bzey<%a~^fp+?Zm&0Q+Oig`@dYHmZI32}MR7qWJLmyLTk* zOl7awHNeeuKdO0wIi#T%LO)Dz`yrrggaW#rWA4X|^zOBnH4QvEaH}k^R9EC&>tI-~ z&GL`h@TV06o@+4r(3nE$t^Gubp3$rmcc^=&c~a7@d@X8N1_l*>4%`nirc^P8Fpq;3 z%bv_eC)3A+DW$~^pBvJhioH{qLnMrBY7|vaz14bfmdGb>ma@)W-E%3iYe9Xp%xFUL7Te+2c0nrx`}D#|oy**6qebXo zIk|liGCfkNECb8;mc(<81QVW8;^@Qwbx3>rP*jI!rG#4*#9C&zk`rj<@f!j&h#3&Y z*l{f;?22kY$gxYpg}b3$8I1VW3@=-?U`S8n6QIeJGWswa@|ZIeJ?`B27u5!fHSZJig%ZhZ3o@n3upS83L%W6 zaYxW;pTSCGs|{5y(O2G~jBqv<8crD;a>>*ZHQ9{E7ZLD?IdwPLt_WsG2IYn>nuTZs z@tZoL^>*7Mtc*fxOA!_QsWch9pHTZh)mjVZmB`@i+jLj(oh~`K(@w5mS2+47{RiF) Bg?j)1 literal 0 HcmV?d00001 diff --git a/src/tescmd/openclaw/dispatcher.py b/src/tescmd/openclaw/dispatcher.py index 3906f18..c828cd9 100644 --- a/src/tescmd/openclaw/dispatcher.py +++ b/src/tescmd/openclaw/dispatcher.py @@ -424,10 +424,17 @@ async def _handle_system_run(self, params: dict[str, Any]) -> dict[str, Any]: Accepts both OpenClaw-style (``door.lock``) and API-style (``door_lock``) method names via :data:`_METHOD_ALIASES`. + + The target method can be specified as ``method`` or ``command`` + (the latter mirrors the gateway protocol's field name). """ - method = params.get("method", "") + raw = params.get("method", "") or params.get("command", "") + # Normalize: bots may send a list like ["door.lock"] instead of a string + if isinstance(raw, list): + raw = raw[0] if raw else "" + method = str(raw).strip() if raw else "" if not method: - raise ValueError("system.run requires 'method' parameter") + raise ValueError("system.run requires 'method' (or 'command') parameter") resolved = _METHOD_ALIASES.get(method, method) if resolved == "system.run": raise ValueError("system.run cannot invoke itself") diff --git a/tests/openclaw/test_dispatcher.py b/tests/openclaw/test_dispatcher.py index 85ed31b..537ff04 100644 --- a/tests/openclaw/test_dispatcher.py +++ b/tests/openclaw/test_dispatcher.py @@ -808,6 +808,30 @@ async def test_system_run_missing_method_raises(self) -> None: with pytest.raises(ValueError, match="requires 'method'"): await d.dispatch({"method": "system.run", "params": {}}) + @pytest.mark.asyncio + async def test_system_run_accepts_command_key(self) -> None: + """system.run accepts 'command' as alias for 'method' (matches gateway protocol).""" + ctx = _mock_app_ctx() + store = _store_with(Soc=72.0) + d = CommandDispatcher(vin="VIN1", app_ctx=ctx, telemetry_store=store) + result = await d.dispatch( + {"method": "system.run", "params": {"command": "battery.get", "params": {}}} + ) + assert result is not None + assert "battery_level" in result + + @pytest.mark.asyncio + async def test_system_run_accepts_list_method(self) -> None: + """system.run normalizes a list value like ['battery.get'] to a string.""" + ctx = _mock_app_ctx() + store = _store_with(Soc=72.0) + d = CommandDispatcher(vin="VIN1", app_ctx=ctx, telemetry_store=store) + result = await d.dispatch( + {"method": "system.run", "params": {"method": ["battery.get"], "params": {}}} + ) + assert result is not None + assert "battery_level" in result + def test_method_aliases_cover_key_commands(self) -> None: assert "door_lock" in _METHOD_ALIASES assert "door_unlock" in _METHOD_ALIASES From d5603615d66b6862d1f13d9a1860c3a52a9acc9c Mon Sep 17 00:00:00 2001 From: Sean McLellan Date: Tue, 3 Feb 2026 17:21:31 -0500 Subject: [PATCH 5/5] fix: prevent concurrent recv on gateway reconnect, polish README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Cancel existing receive loop before starting new one in connect() - Reorder KeyServer port read before thread start - Rewrite README as concise marketing-quality copy (503→210 lines) Co-Authored-By: Claude Opus 4.5 --- README.md | 525 +++++++++------------------------ src/tescmd/cli/auth.py | 2 +- src/tescmd/openclaw/gateway.py | 13 + tests/openclaw/test_gateway.py | 30 ++ 4 files changed, 179 insertions(+), 391 deletions(-) diff --git a/README.md b/README.md index d5e7dc5..1a8a64e 100644 --- a/README.md +++ b/README.md @@ -12,43 +12,16 @@ GitHub Release

-A Python CLI for querying and controlling Tesla vehicles via the Fleet API — built for both human operators and AI agents. - -## What It Does - -tescmd gives you full command-line access to Tesla's Fleet API: check battery and charge status, lock or unlock doors, control climate, open trunks, send navigation waypoints, manage Powerwalls, stream live telemetry, and more. It handles OAuth2 authentication, token refresh, key enrollment, command signing, and response caching so you don't have to. Every command works in both interactive (Rich tables) and scripted (JSON) modes, and an MCP server lets AI agents call any command as a tool. - -## Why tescmd? - -Tesla's Fleet API gives developers full access to vehicle data and commands, but working with it directly means juggling OAuth2 PKCE flows, token refresh, regional endpoints, key enrollment, and raw JSON responses. - -tescmd wraps all of that into a single command-line tool that handles authentication, token management, and output formatting so you can focus on what you actually want to do — check your battery, find your car, or control your vehicle. - -tescmd is designed to work as a tool that AI agents can invoke directly. Platforms like [OpenClaw](https://openclaw.ai/), [Claude Desktop](https://claude.ai), and other agent frameworks can call tescmd commands, parse the structured JSON output, and take actions on your behalf — "lock my car", "what's my battery at?", "start climate control". The deterministic JSON output, meaningful exit codes, cost-aware wake confirmation, and `--wake` opt-in flag make it safe for autonomous agent use without surprise billing. +

+ The complete Python CLI for Tesla's Fleet API — built for humans and AI agents alike. +

-## Features +

+ Check your battery. Lock your doors. Stream live telemetry. Let Claude control your car.
+ Two commands to install. One wizard to set up. Every API endpoint at your fingertips. +

-- **Vehicle state queries** — battery, range, charge status, climate, location, doors, windows, trunks, tire pressure, dashcam, sentry mode, and more -- **Vehicle commands** — charge start/stop/limit/departure scheduling, climate on/off/set temp/seats/steering wheel, lock/unlock, sentry mode, trunk/frunk, windows, HomeLink, navigation waypoints, media playback, speed limits, PIN management -- **Vehicle Command Protocol** — ECDH session management with HMAC-SHA256 signed commands via the `signed_command` endpoint; automatically used when keys are enrolled -- **Key enrollment** — `tescmd key enroll ` sends your public key to the vehicle and guides you through Tesla app approval -- **Tier enforcement** — readonly tier blocks write commands with clear guidance to upgrade -- **Energy products** — Powerwall live status, site info, backup reserve, operation mode, storm mode, time-of-use settings, charging history, calendar history, grid import/export -- **User & sharing** — account info, region, orders, feature flags, driver management, vehicle sharing invites -- **Live Dashboard** — `tescmd serve` launches a full-screen TUI showing live telemetry data, MCP server info, tunnel URL, sink count, and cache stats — all in a scrollable, interactive terminal UI powered by Textual -- **Fleet Telemetry streaming** — `tescmd serve` (or `tescmd vehicle telemetry stream`) receives push-based data from your vehicle via Tailscale Funnel — no polling, 99%+ cost reduction. Telemetry sessions produce a wide-format CSV log by default -- **OpenClaw Bridge** — `tescmd serve --openclaw ws://...` streams filtered telemetry to an OpenClaw Gateway with configurable delta+throttle filtering per field; supports bidirectional command dispatch so bots can send vehicle commands back through the gateway -- **Trigger subscriptions** — register conditions on any telemetry field (battery < 20%, speed > 80, location enters geofence) and get notified via OpenClaw push events or MCP polling; supports one-shot and persistent modes with cooldown -- **MCP Server** — `tescmd serve` (or `tescmd mcp serve`) exposes all commands as MCP tools for Claude.ai, Claude Desktop, Claude Code, and other agent frameworks via OAuth 2.1 -- **Universal response caching** — all read commands are cached with tiered TTLs (1h for specs/warranty, 5m for fleet lists, 1m standard, 30s for location-dependent); bots can call tescmd as often as needed — within the TTL window, responses are instant and free -- **Cost-aware wake** — prompts before sending billable wake API calls; `--wake` flag for scripts that accept the cost -- **Guided OAuth2 setup** — `tescmd auth login` walks you through browser-based authentication with PKCE -- **Key management** — generate EC keys, register via Tesla Developer Portal (remote) or BLE enrollment (proximity) -- **Rich terminal output** — tables, panels, spinners powered by Rich; auto-detects TTY vs pipe -- **Configurable display units** — switch between PSI/bar, °F/°C, and mi/km (defaults to US units) -- **JSON output** — structured output for scripting and agent integration -- **Multi-profile** — switch between vehicles, accounts, and regions with named profiles -- **Agent-friendly** — deterministic JSON, meaningful exit codes, `--wake` opt-in, headless auth support +--- ## Quick Start @@ -57,437 +30,209 @@ pip install tescmd tescmd setup ``` -That's it. The interactive setup wizard walks you through everything: creating a Tesla Developer app, generating an EC key pair, hosting the public key (via GitHub Pages or Tailscale Funnel), registering with the Fleet API, authenticating via OAuth2, and enrolling your key on a vehicle. Each step checks prerequisites and offers remediation if something is missing. - -After setup completes, you can start using commands: +The setup wizard handles everything — Tesla Developer app creation, key generation, public key hosting, Fleet API registration, OAuth2 authentication, and vehicle key enrollment. Then you're ready: ```bash -tescmd charge status # Check battery and charging state -tescmd vehicle info # Full vehicle data snapshot -tescmd climate on --wake # Turn on climate (wakes vehicle if asleep) +tescmd charge status # Battery and charging state +tescmd climate on --wake # Turn on climate (wakes if asleep) tescmd security lock --wake # Lock the car -tescmd serve 5YJ3... # MCP + telemetry TUI dashboard + CSV log -tescmd serve --no-mcp # Telemetry-only TUI dashboard -``` - -Every read command is cached — repeat calls within the TTL window are instant and free. - -## Prerequisites - -| Requirement | Required | What it is | Why tescmd needs it | -|---|---|---|---| -| **Python 3.11+** | Yes | The programming language runtime that runs tescmd | tescmd is a Python package — you need Python installed to use it | -| **pip** | Yes | Python's package installer (ships with Python) | Used to install tescmd and its dependencies via `pip install tescmd` | -| **Tesla account** | Yes | A [tesla.com](https://www.tesla.com) account linked to a vehicle or energy product | tescmd authenticates via OAuth2 against your Tesla account to access the Fleet API | -| **Git** | Yes | Version control tool ([git-scm.com](https://git-scm.com)) | Used during setup for key hosting via GitHub Pages | -| **GitHub CLI** (`gh`) | Recommended | GitHub's command-line tool ([cli.github.com](https://cli.github.com)) — authenticate with `gh auth login` | Auto-creates a `*.github.io` site to host your public key at the `.well-known` path Tesla requires | -| **Tailscale** | Recommended | Mesh VPN with public tunneling ([tailscale.com](https://tailscale.com)) — authenticate with `tailscale login` | Provides a public HTTPS URL for key hosting and Fleet Telemetry streaming with zero infrastructure setup | - -### Self-Hosting with Tailscale (No Domain Required) - -If you have **Tailscale** installed with Funnel enabled, you don't need a custom domain or GitHub Pages at all. Tailscale Funnel gives you a public HTTPS URL (`.tailnet.ts.net`) that serves both your public key and (optionally) Fleet Telemetry streaming — all from your local machine with zero infrastructure setup. - -```bash -# Install Tailscale, enable Funnel in your tailnet ACL, then: -tescmd setup # wizard auto-detects Tailscale and offers it as the hosting method -``` - -This is the fastest path to a working setup: Tailscale handles TLS certificates, NAT traversal, and public DNS automatically. The tradeoff is that your machine needs to be running for Tesla to reach your key and for telemetry streaming to work. For always-on key hosting with offline machines, use GitHub Pages instead. - -Without either GitHub CLI or Tailscale, you'll need to manually host your public key at the Tesla-required `.well-known` path on your own domain. - -## Installation - -### From PyPI - -```bash -pip install tescmd -``` - -### From Source - -```bash -git clone https://github.com/oceanswave/tescmd.git -cd tescmd -pip install -e ".[dev]" +tescmd nav waypoints "Home" "Work" # Multi-stop navigation +tescmd serve 5YJ3... # Launch the live dashboard ``` -## Configuration - -tescmd resolves settings in this order (highest priority first): - -1. **CLI arguments** — `--vin`, `--region`, `--format`, `--units`, etc. -2. **Environment variables** — `TESLA_VIN`, `TESLA_REGION`, etc. (`.env` files loaded automatically) -3. **Defaults** - -### Environment Variables - -Create a `.env` file in your working directory or `~/.config/tescmd/.env`: - -```dotenv -TESLA_CLIENT_ID=your-client-id -TESLA_CLIENT_SECRET=your-client-secret -TESLA_VIN=5YJ3E1EA1NF000000 -TESLA_REGION=na - -# Token storage (optional — defaults to OS keyring with file fallback) -TESLA_TOKEN_FILE=~/.config/tescmd/tokens.json - -# Cache settings (optional) -TESLA_CACHE_ENABLED=true -TESLA_CACHE_TTL=60 -TESLA_CACHE_DIR=~/.cache/tescmd - -# Command protocol: auto | signed | unsigned (optional) -TESLA_COMMAND_PROTOCOL=auto - -# Display units (optional — defaults to US units) -TESLA_TEMP_UNIT=F # F or C -TESLA_DISTANCE_UNIT=mi # mi or km -TESLA_PRESSURE_UNIT=psi # psi or bar -``` +--- -## Token Storage +## See It in Action -By default, tescmd stores OAuth tokens in the OS keyring (macOS Keychain, GNOME Keyring, Windows Credential Manager). On headless systems where no keyring daemon is available (Docker, CI, SSH sessions), tescmd automatically falls back to a file-based store at `~/.config/tescmd/tokens.json` with restricted permissions (`0600` on Unix, owner-only ACL on Windows). +### Live TUI Dashboard -You can force file-based storage by setting `TESLA_TOKEN_FILE`: +`tescmd serve` launches a full-screen terminal dashboard with real-time telemetry, MCP server status, tunnel info, and connection metrics — powered by Textual. -```bash -export TESLA_TOKEN_FILE=~/.config/tescmd/tokens.json -``` - -To transfer tokens between machines, use `auth export` and `auth import`: - -```bash -# On source machine -tescmd auth export > tokens.json +

+ tescmd serve — live TUI dashboard +

-# On target machine (Docker, CI, etc.) -tescmd auth import < tokens.json -``` +### AI Agent Integration -Check which backend is active with `tescmd status` — the output includes a `Token store` line showing `keyring` or the file path. +Every command doubles as an MCP tool. Claude Desktop, Claude Code, and other agent frameworks can query your vehicle, send commands, and react to telemetry — all through structured JSON with built-in cost protection. -> **Security note:** File-based tokens are stored as plaintext JSON. The file is created with owner-only permissions, but treat it like any other credential file. +

+ tescmd MCP server — Claude Desktop integration +

-## Commands +### Rich Terminal Output -| Group | Commands | Description | -|---|---|---| -| `setup` | *(interactive wizard)* | First-run configuration: client ID, secret, region, domain, key enrollment | -| `auth` | `login`, `logout`, `status`, `refresh`, `register`, `export`, `import` | OAuth2 authentication lifecycle | -| `vehicle` | `list`, `get`, `info`, `data`, `location`, `wake`, `rename`, `mobile-access`, `nearby-chargers`, `alerts`, `release-notes`, `service`, `drivers`, `calendar`, `subscriptions`, `upgrades`, `options`, `specs`, `warranty`, `fleet-status`, `low-power`, `accessory-power`, `telemetry {config,create,delete,errors,stream}` | Vehicle discovery, state queries, fleet telemetry streaming, power management | -| `charge` | `status`, `start`, `stop`, `limit`, `limit-max`, `limit-std`, `amps`, `port-open`, `port-close`, `schedule`, `departure`, `precondition-add`, `precondition-remove`, `add-schedule`, `remove-schedule`, `clear-schedules`, `clear-preconditions`, `managed-amps`, `managed-location`, `managed-schedule` | Charge queries, control, scheduling, and fleet management | -| `billing` | `history`, `sessions`, `invoice` | Supercharger billing history and invoices | -| `climate` | `status`, `on`, `off`, `set`, `precondition`, `seat`, `seat-cool`, `wheel-heater`, `overheat`, `bioweapon`, `keeper`, `cop-temp`, `auto-seat`, `auto-wheel`, `wheel-level` | Climate, seat, and steering wheel control | -| `security` | `status`, `lock`, `auto-secure`, `unlock`, `sentry`, `valet`, `valet-reset`, `remote-start`, `flash`, `honk`, `boombox`, `speed-limit`, `pin-to-drive`, `pin-reset`, `pin-clear-admin`, `speed-clear`, `speed-clear-admin`, `guest-mode`, `erase-data` | Security, access, and PIN management | -| `trunk` | `open`, `close`, `frunk`, `window`, `sunroof`, `tonneau-open`, `tonneau-close`, `tonneau-stop` | Trunk, frunk, sunroof, tonneau, and window control | -| `media` | `play-pause`, `next-track`, `prev-track`, `next-fav`, `prev-fav`, `volume-up`, `volume-down`, `adjust-volume` | Media playback control | -| `nav` | `send`, `gps`, `supercharger`, `homelink`, `waypoints` | Navigation and HomeLink | -| `software` | `status`, `schedule`, `cancel` | Software update management | -| `energy` | `list`, `status`, `live`, `backup`, `mode`, `storm`, `tou`, `history`, `off-grid`, `grid-config`, `telemetry`, `calendar` | Energy product (Powerwall) management | -| `user` | `me`, `region`, `orders`, `features` | User account information | -| `sharing` | `add-driver`, `remove-driver`, `create-invite`, `redeem-invite`, `revoke-invite`, `list-invites` | Vehicle sharing and driver management | -| `key` | `generate`, `deploy`, `validate`, `show`, `enroll`, `unenroll` | Key management and enrollment | -| `partner` | `public-key`, `telemetry-error-vins`, `telemetry-errors` | Partner account endpoints (require client credentials) | -| `serve` | *(unified server)* | Combined MCP + telemetry + OpenClaw TUI dashboard with trigger subscriptions | -| `openclaw` | `bridge` | Standalone OpenClaw bridge with bidirectional command dispatch | -| `mcp` | `serve` | Standalone MCP server exposing all commands as agent tools | -| `cache` | `status`, `clear` | Response cache management | -| `raw` | `get`, `post` | Arbitrary Fleet API endpoint access | - -Use `tescmd --help` for detailed usage on any command group. For API endpoints not yet covered by a dedicated command, use `raw get` or `raw post` as an escape hatch. +Formatted tables in your terminal, structured JSON when piped — tescmd auto-detects the right output for the context.

- tescmd nav waypoints — multi-stop route + tescmd nav waypoints

-### Global Flags +--- -These flags can be placed at the root level or after any subcommand: +## What You Get -| Flag | Description | -|---|---| -| `--vin VIN` | Vehicle VIN (also accepted as a positional argument) | -| `--profile NAME` | Config profile name | -| `--format {rich,json,quiet}` | Force output format | -| `--quiet` | Suppress normal output | -| `--region {na,eu,cn}` | Tesla API region | -| `--verbose` | Enable verbose logging | -| `--no-cache` / `--fresh` | Bypass response cache for this invocation | -| `--wake` | Auto-wake vehicle without confirmation (billable) | +### Query & Control -## Output Formats +Full read/write access to Tesla's Fleet API: battery, charge, climate, locks, trunks, windows, sentry, navigation, media, speed limits, PINs, Powerwalls, and more. Every read command is cached with smart TTLs — bots can call tescmd as often as they want and only pay for the first request. -tescmd auto-detects the best output format: +### Fleet Telemetry Streaming -- **Rich** (default in TTY) — formatted tables, panels, colored status indicators -- **JSON** (`--format json` or piped) — structured, parseable output -- **Quiet** (`--quiet`) — minimal output on stderr, suitable for scripts that only check exit codes +Your vehicle pushes data directly to your machine via Tailscale Funnel — no polling, no per-request charges. Choose from field presets (`driving`, `charging`, `all`) or subscribe to 120+ individual fields. Sessions produce a wide-format CSV log by default. ```bash -# Human-friendly output -tescmd vehicle list - -# JSON for scripting -tescmd vehicle list --format json - -# Pipe-friendly (auto-switches to JSON) -tescmd vehicle list | jq '.[0].vin' - -# Quiet mode (exit code only) -tescmd vehicle wake --quiet && echo "Vehicle is awake" +tescmd serve 5YJ3... --fields driving # Speed, location, power +tescmd serve 5YJ3... --fields all # Everything ``` -### Display Units +### MCP Server for AI Agents -Rich output displays values in US units by default (°F, miles, PSI). Switch to metric with a single flag: +`tescmd serve` exposes every command as an MCP tool with OAuth 2.1 authentication. Agents get deterministic JSON output, meaningful exit codes, and a `--wake` opt-in flag so they never trigger billable wake calls by accident. -```bash -tescmd --units metric charge status # All metric: °C, km, bar -tescmd --units us charge status # All US: °F, mi, psi (default) -``` +### OpenClaw Bridge -Or configure individual units via environment variables: +Stream filtered telemetry to an [OpenClaw](https://openclaw.ai/) Gateway with per-field delta and throttle filtering. Bots on the gateway can send commands back — lock doors, start charging, set climate — through bidirectional dispatch. -```dotenv -TESLA_TEMP_UNIT=C # F or C -TESLA_DISTANCE_UNIT=km # mi or km -TESLA_PRESSURE_UNIT=bar # psi or bar -``` +### Trigger Subscriptions -| Dimension | US (default) | Metric | Env Variable | -|---|---|---|---| -| Temperature | °F | °C | `TESLA_TEMP_UNIT` | -| Distance | mi | km | `TESLA_DISTANCE_UNIT` | -| Pressure | psi | bar | `TESLA_PRESSURE_UNIT` | +Register conditions on any telemetry field — battery below 20%, speed above 80, location enters a geofence — and get notified via OpenClaw push events or MCP polling. Supports one-shot and persistent modes with cooldown. -The `--units` flag overrides all three env vars at once. The Tesla API returns Celsius, miles, and bar — conversions happen in the display layer only. +### Signed Vehicle Commands -## Tesla Fleet API Costs +tescmd implements the [Vehicle Command Protocol](https://github.com/teslamotors/vehicle-command) with ECDH session management and HMAC-SHA256 signing. Once your key is enrolled, commands are signed transparently — no agent-side crypto needed. -Tesla's Fleet API is **pay-per-use** — every request returning a status code below 500 is billable, including 4xx errors like "vehicle asleep" (408) and rate limits (429). Wake requests are the most expensive category and are rate-limited to 3/min. There is no free tier (the $10/month credit is being discontinued). +--- -> **Official pricing:** [Tesla Fleet API — Billing and Limits](https://developer.tesla.com/docs/fleet-api/billing-and-limits) +## Cost Protection Built In -### Why This Matters +Tesla's Fleet API is pay-per-use. A naive polling script can generate hundreds of dollars in monthly charges from a single vehicle. tescmd implements four layers of defense: -A naive script that polls `vehicle_data` every 5 minutes generates **4-5 billable requests per check** (asleep error + wake + poll + data fetch). That's **1,000+ billable requests per day** from a single cron job. At roughly $1 per 500 data requests, monitoring one vehicle costs around $60/month before you even count wake requests (the most expensive tier). +| Layer | What it does | +|---|---| +| **Tiered caching** | Specs cached 1h, fleet lists 5m, standard queries 1m, location 30s | +| **Wake confirmation** | Prompts before billable wake calls; `--wake` flag for scripts | +| **Smart wake state** | Tracks recent wake confirmations, skips redundant attempts | +| **Write invalidation** | Write commands auto-invalidate the relevant cache scope | -### Cost Example: Battery Check +Streaming telemetry via `tescmd serve` replaces polling entirely — flat cost regardless of data volume. See [API Costs](docs/api-costs.md) for the full breakdown. -| | Without tescmd | With tescmd | -|---|---|---| -| Vehicle asleep, check battery | 408 error (billable) + wake (billable) + poll (billable) + data (billable) = **4+ requests** | Data attempt → 408 (billable) → prompt user → user wakes via Tesla app (free) → retry → data (billable) = **2 requests** | -| Check battery again 30s later | Another 4+ requests | **0 requests** (cache hit) | -| 10 checks in 1 minute | **40+ billable requests** | **1 billable request** + 9 cache hits | +--- -### How tescmd Reduces Costs +## Commands -tescmd implements four layers of cost protection: +| Group | Description | +|---|---| +| `setup` | Interactive first-run wizard | +| `auth` | OAuth2 login, logout, token management, export/import | +| `vehicle` | State queries, wake, rename, telemetry streaming, fleet status | +| `charge` | Charge control, scheduling, departure, fleet management | +| `climate` | HVAC, seats, steering wheel, bioweapon defense, overheat protection | +| `security` | Lock/unlock, sentry, valet, PINs, speed limits, remote start | +| `trunk` | Trunk, frunk, windows, sunroof, tonneau | +| `media` | Playback control, volume, favorites | +| `nav` | Send destinations, GPS coordinates, multi-stop waypoints, HomeLink | +| `software` | Update status, scheduling, cancellation | +| `energy` | Powerwall status, backup reserve, storm mode, grid config, history | +| `billing` | Supercharger billing history and invoices | +| `user` | Account info, region, orders, feature flags | +| `sharing` | Driver management, vehicle sharing invites | +| `key` | Key generation, deployment, enrollment, validation | +| `serve` | Combined MCP + telemetry + OpenClaw TUI dashboard | +| `mcp` | Standalone MCP server | +| `openclaw` | Standalone OpenClaw bridge | +| `cache` | Cache status and management | +| `raw` | Direct Fleet API endpoint access | + +Every command supports `--format json` for scripting and `--help` for detailed usage. See the [Command Reference](docs/commands.md) for the full list. + +--- -1. **Universal read-command cache** — **all** read commands are cached with tiered TTLs: static data (specs, warranty) cached for 1 hour, fleet lists for 5 minutes, standard queries for 1 minute, location-dependent data for 30 seconds. Bots can call tescmd as often as needed — within the TTL, responses are instant and free. -2. **Smart wake state** — Tracks whether the vehicle was recently confirmed online (30s TTL). Skips redundant wake attempts. -3. **Wake confirmation prompt** — Prompts before sending billable wake calls in interactive mode. JSON/piped mode returns a structured error with `--wake` guidance. -4. **Write-command invalidation** — write commands automatically invalidate the relevant cache scope (vehicle or energy site) so subsequent reads reflect the new state. +## Installation ```bash -# First call: hits API, caches response -tescmd charge status - -# Second call within 60s: instant cache hit, no API call -tescmd charge status - -# All read commands are cached — even vehicle list, user info, billing, etc. -tescmd vehicle list # cached 5 min -tescmd user me # cached 1 hour -tescmd vehicle specs # cached 1 hour -tescmd billing history # cached 1 min - -# Bypass cache when you need fresh data -tescmd charge status --fresh - -# Auto-wake without prompting (for scripts accepting the cost) -tescmd charge status --wake - -# Manage cache -tescmd cache status # entry counts, disk usage -tescmd cache clear # clear all -tescmd cache clear --vin VIN # clear for one vehicle -tescmd cache clear --site 12345 # clear for an energy site -tescmd cache clear --scope account # clear account-level entries +pip install tescmd ``` -For the full cost breakdown with more examples, savings calculations, and Fleet Telemetry streaming comparison, see [docs/api-costs.md](docs/api-costs.md). - -Configure via environment variables: - -| Variable | Default | Description | -|---|---|---| -| `TESLA_CACHE_ENABLED` | `true` | Enable/disable the cache | -| `TESLA_CACHE_TTL` | `60` | Time-to-live in seconds | -| `TESLA_CACHE_DIR` | `~/.cache/tescmd` | Cache directory path | +**Requirements:** Python 3.11+ and a [Tesla account](https://www.tesla.com) with a linked vehicle or energy product. -## Fleet Telemetry Streaming +**Recommended:** [GitHub CLI](https://cli.github.com) (`gh`) for automated key hosting via GitHub Pages, or [Tailscale](https://tailscale.com) for zero-config key hosting and telemetry streaming via Funnel. -Tesla's Fleet Telemetry lets your vehicle push real-time data directly to your server — no polling, no per-request charges. tescmd handles all the setup: +
+Install from source ```bash -# Full-screen TUI with live telemetry + MCP server -tescmd serve 5YJ3... - -# Telemetry-only mode (full-screen TUI, no MCP) -tescmd serve 5YJ3... --no-mcp - -# Select field presets -tescmd serve 5YJ3... --fields driving # Speed, location, power -tescmd serve 5YJ3... --fields charging # Battery, voltage, current -tescmd serve 5YJ3... --fields all # Everything (120+ fields) - -# Override polling interval -tescmd serve 5YJ3... --interval 5 # Every 5 seconds - -# JSONL output for scripting (non-TTY / piped) -tescmd serve 5YJ3... --no-mcp --format json | jq . - -# Disable CSV log -tescmd serve 5YJ3... --no-log - -# Legacy Rich Live dashboard -tescmd serve 5YJ3... --legacy-dashboard +git clone https://github.com/oceanswave/tescmd.git +cd tescmd +pip install -e ".[dev]" ``` -**Requires Tailscale** with Funnel enabled. The serve command starts a local WebSocket server, exposes it via Tailscale Funnel (handles TLS + NAT traversal), configures Tesla to push data to it, and renders a full-screen TUI with live telemetry data, server info (MCP URL, tunnel, sinks), unit conversion, and connection status. Press `q` to quit. - -

- tescmd serve — live TUI dashboard -

- -By default, telemetry sessions write a wide-format CSV log to `~/.config/tescmd/logs/` with one row per frame and one column per subscribed field. Disable with `--no-log`. - -`tescmd vehicle telemetry stream` is an alias for `tescmd serve --no-mcp`. +
-### Telemetry vs Polling Costs +--- -| Approach | 1 vehicle, 5-second interval, 24 hours | Monthly cost estimate | -|---|---|---| -| **Polling `vehicle_data`** | ~17,280 requests × $0.001 = **$17/day** | **$500+/month** | -| **Fleet Telemetry streaming** | 1 config create + 1 config delete = **2 requests** | **< $0.01/month** | - -Fleet Telemetry streaming is a flat-cost alternative: you pay only for the initial config setup and teardown, regardless of how much data flows. The tradeoff is that you need Tailscale running on a machine to receive the push. - -## Key Enrollment & Vehicle Command Protocol +## Configuration -Newer Tesla vehicles require commands to be signed using the [Vehicle Command Protocol](https://github.com/teslamotors/vehicle-command). tescmd handles this transparently: +tescmd resolves settings from CLI flags, environment variables (`.env` files loaded automatically), and defaults — in that order. -1. **Generate a key pair** — `tescmd key generate` creates an EC P-256 key pair -2. **Enroll on vehicle** — `tescmd key enroll ` sends the public key to the vehicle; approve in the Tesla app -3. **Commands are signed automatically** — once enrolled, tescmd uses ECDH sessions + HMAC-SHA256 to sign commands via the `signed_command` endpoint +
+Environment variables -```bash -# Generate EC key pair -tescmd key generate +```dotenv +TESLA_CLIENT_ID=your-client-id +TESLA_CLIENT_SECRET=your-client-secret +TESLA_VIN=5YJ3E1EA1NF000000 +TESLA_REGION=na # na, eu, cn -# Enroll on a vehicle (interactive approval via Tesla app) -tescmd key enroll 5YJ3E1EA1NF000000 +# Display units (optional — defaults to US) +TESLA_TEMP_UNIT=F # F or C +TESLA_DISTANCE_UNIT=mi # mi or km +TESLA_PRESSURE_UNIT=psi # psi or bar -# Commands are now signed automatically -tescmd security lock --wake +# Or switch everything at once: +# tescmd --units metric charge status ``` -The `command_protocol` setting controls routing: - -| Value | Behavior | -|---|---| -| `auto` (default) | Use signed path when keys are enrolled; fall back to unsigned | -| `signed` | Require signed commands (error if no keys) | -| `unsigned` | Force legacy REST path (skip signing) | - -Set via `TESLA_COMMAND_PROTOCOL` environment variable or in your config. - -See [docs/vehicle-command-protocol.md](docs/vehicle-command-protocol.md) for the full protocol architecture. +See [docs/commands.md](docs/commands.md) for the full environment variable reference. -## Agent Integration +
-tescmd is designed for use by AI agents and automation platforms. Agents like [Claude Code](https://github.com/anthropics/claude-code), Claude Desktop, and other LLM-powered tools can invoke tescmd commands, parse the structured JSON output, and act on your behalf. +
+Token storage -

- tescmd MCP server — Claude Desktop integration -

- -**Why tescmd works well as an agent tool:** +Tokens are stored in the OS keyring by default (macOS Keychain, GNOME Keyring, Windows Credential Manager). On headless systems, tescmd falls back to a file-based store with restricted permissions. Transfer tokens between machines with `tescmd auth export` and `tescmd auth import`. -- **Structured JSON output** — piped/non-TTY mode automatically emits parseable JSON with consistent schema -- **Cost protection** — agents won't accidentally trigger billable wake calls without `--wake`; the default behavior is safe -- **Cache-aware** — every read command is cached; repeated queries from an agent within the TTL window cost nothing -- **Meaningful exit codes** — agents can branch on success/failure without parsing output -- **Stateless invocations** — each command is self-contained; no session state to manage -- **Signed commands** — when keys are enrolled, commands are signed transparently; no agent-side crypto needed +
-**Example agent workflow:** +--- -```bash -# Agent checks battery (cache hit if recent) -tescmd charge status --format json - -# Agent decides to start charging -tescmd charge start --wake --format json - -# Agent verifies the command took effect (cache was invalidated) -tescmd charge status --format json --fresh -``` +## Documentation -See [docs/bot-integration.md](docs/bot-integration.md) for the full JSON schema, exit code reference, and headless authentication setup. +| | | +|---|---| +| [Setup Guide](docs/setup.md) | Step-by-step walkthrough of `tescmd setup` | +| [Command Reference](docs/commands.md) | Detailed usage for every command | +| [API Costs](docs/api-costs.md) | Cost breakdown, savings calculations, streaming comparison | +| [Bot Integration](docs/bot-integration.md) | JSON schema, exit codes, headless auth | +| [OpenClaw Bridge](docs/openclaw.md) | Gateway protocol, bidirectional commands, triggers, geofencing | +| [MCP Server](docs/mcp.md) | Tool reference, OAuth 2.1, custom tools, trigger polling | +| [Vehicle Command Protocol](docs/vehicle-command-protocol.md) | ECDH sessions and signed commands | +| [Authentication](docs/authentication.md) | OAuth2 PKCE flow, token storage, scopes | +| [Architecture](docs/architecture.md) | Layered design, module responsibilities | +| [FAQ](docs/faq.md) | Common questions about costs, hosting, and configuration | +| [Development](docs/development.md) | Contributing, testing, linting | + +--- ## Development ```bash -# Clone and install in dev mode -git clone https://github.com/oceanswave/tescmd.git -cd tescmd +git clone https://github.com/oceanswave/tescmd.git && cd tescmd pip install -e ".[dev]" - -# Run checks -ruff check src/ tests/ -ruff format --check src/ tests/ -mypy src/ -pytest - -# Run a specific test -pytest tests/cli/test_auth.py -v - -# Validate API coverage against Tesla Fleet API spec -python scripts/validate_fleet_api.py -``` - -### API Coverage Validation - -tescmd ships a spec-driven validation utility that compares our implementation against the Tesla Fleet API. The canonical spec lives at `spec/fleet_api_spec.json` (sourced from Tesla's docs and Go SDK), and `scripts/validate_fleet_api.py` validates all API methods, parameters, and types using AST introspection. - -```bash -python scripts/validate_fleet_api.py # Summary -python scripts/validate_fleet_api.py --verbose # All endpoints -python scripts/validate_fleet_api.py --json # Machine-readable +pytest # 1600+ tests +ruff check src/ tests/ && mypy src/ ``` -Run this periodically or after modifying API methods to catch drift. - -See [docs/development.md](docs/development.md) for detailed contribution guidelines. - -## Documentation - -- [Setup Guide](docs/setup.md) — step-by-step walkthrough of `tescmd setup` -- [FAQ](docs/faq.md) — common questions about tescmd, costs, hosting, and configuration -- [Command Reference](docs/commands.md) — detailed usage for every command -- [API Costs](docs/api-costs.md) — detailed cost breakdown and savings calculations -- [Bot Integration](docs/bot-integration.md) — JSON schema, exit codes, telemetry streaming, headless auth -- [OpenClaw Bridge](docs/openclaw.md) — gateway protocol, bidirectional commands, trigger subscriptions, geofencing -- [MCP Server](docs/mcp.md) — tool reference, authentication, custom tools, trigger polling -- [Architecture](docs/architecture.md) — layered design, module responsibilities, design decisions -- [Vehicle Command Protocol](docs/vehicle-command-protocol.md) — ECDH sessions and signed commands -- [Authentication](docs/authentication.md) — OAuth2 PKCE flow, token storage, scopes -- [Development](docs/development.md) — contribution guidelines, testing, linting +--- ## Changelog diff --git a/src/tescmd/cli/auth.py b/src/tescmd/cli/auth.py index 3e06891..cf2beda 100644 --- a/src/tescmd/cli/auth.py +++ b/src/tescmd/cli/auth.py @@ -562,8 +562,8 @@ def _interactive_setup( pem = load_public_key_pem(key_dir) _key_server = KeyServer(pem, port=0) - _key_server.start() _local_port = _key_server.server_address[1] + _key_server.start() _ts_manager = TailscaleManager() run_async(_ts_manager.start_funnel(_local_port)) diff --git a/src/tescmd/openclaw/gateway.py b/src/tescmd/openclaw/gateway.py index cc7f4e1..74d6bc6 100644 --- a/src/tescmd/openclaw/gateway.py +++ b/src/tescmd/openclaw/gateway.py @@ -280,8 +280,21 @@ async def connect(self) -> None: so gateways that enforce authentication at the transport layer accept the connection before the OpenClaw handshake begins. + If a receive loop is already running (e.g. from a previous + connection or a concurrent reconnect attempt), it is cancelled + before establishing the new connection to prevent duplicate + ``recv()`` calls on the same WebSocket. + Raises :class:`GatewayConnectionError` on failure. """ + import contextlib + + if self._recv_task is not None and not self._recv_task.done(): + self._recv_task.cancel() + with contextlib.suppress(asyncio.CancelledError): + await self._recv_task + self._recv_task = None + await self._establish_connection() if self._on_request is not None: diff --git a/tests/openclaw/test_gateway.py b/tests/openclaw/test_gateway.py index 62bb3f2..96b3add 100644 --- a/tests/openclaw/test_gateway.py +++ b/tests/openclaw/test_gateway.py @@ -458,6 +458,36 @@ async def _connect() -> None: assert call_count == 3 +class TestConnectCancelsExistingRecvLoop: + @pytest.mark.asyncio + async def test_connect_cancels_old_recv_task(self) -> None: + """Calling connect() while a receive loop is running cancels the old task.""" + gw = GatewayClient("ws://test:1234", on_request=AsyncMock()) + + # Create a real task that blocks forever (simulates running recv loop) + old_task = asyncio.create_task(asyncio.sleep(3600)) + gw._recv_task = old_task + + with patch.object(gw, "_establish_connection", new_callable=AsyncMock): + await gw.connect() + + # Old task should have been cancelled and replaced + assert old_task.cancelled() + assert gw._recv_task is not old_task + assert gw._recv_task is not None + + @pytest.mark.asyncio + async def test_connect_skips_cancel_when_no_task(self) -> None: + """connect() works fine when no previous recv task exists.""" + gw = GatewayClient("ws://test:1234", on_request=AsyncMock()) + assert gw._recv_task is None + + with patch.object(gw, "_establish_connection", new_callable=AsyncMock): + await gw.connect() + + assert gw._recv_task is not None + + class _MockWebSocket: """Minimal async-iterable WebSocket mock for receive loop tests."""