tescmd 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tescmd-0.1.2/.claude/settings.local.json +47 -0
- tescmd-0.1.2/.gitignore +12 -0
- tescmd-0.1.2/CHANGELOG.md +69 -0
- tescmd-0.1.2/CLAUDE.md +401 -0
- tescmd-0.1.2/LICENSE +21 -0
- tescmd-0.1.2/PKG-INFO +458 -0
- tescmd-0.1.2/README.md +411 -0
- tescmd-0.1.2/docs/api-costs.md +220 -0
- tescmd-0.1.2/docs/architecture.md +251 -0
- tescmd-0.1.2/docs/authentication.md +263 -0
- tescmd-0.1.2/docs/bot-integration.md +375 -0
- tescmd-0.1.2/docs/commands.md +1192 -0
- tescmd-0.1.2/docs/development.md +464 -0
- tescmd-0.1.2/docs/plans/2025-01-29-mvp-design.md +152 -0
- tescmd-0.1.2/docs/plans/2025-01-29-mvp-implementation.md +3073 -0
- tescmd-0.1.2/docs/setup-enrollment-audit.md +171 -0
- tescmd-0.1.2/docs/vehicle-command-protocol.md +187 -0
- tescmd-0.1.2/pyproject.toml +96 -0
- tescmd-0.1.2/scripts/e2e_test.py +307 -0
- tescmd-0.1.2/scripts/validate_fleet_api.py +671 -0
- tescmd-0.1.2/spec/fleet_api_spec.json +250 -0
- tescmd-0.1.2/src/tescmd/__init__.py +3 -0
- tescmd-0.1.2/src/tescmd/__main__.py +5 -0
- tescmd-0.1.2/src/tescmd/_internal/__init__.py +0 -0
- tescmd-0.1.2/src/tescmd/_internal/async_utils.py +25 -0
- tescmd-0.1.2/src/tescmd/_internal/permissions.py +43 -0
- tescmd-0.1.2/src/tescmd/_internal/vin.py +44 -0
- tescmd-0.1.2/src/tescmd/api/__init__.py +1 -0
- tescmd-0.1.2/src/tescmd/api/charging.py +102 -0
- tescmd-0.1.2/src/tescmd/api/client.py +189 -0
- tescmd-0.1.2/src/tescmd/api/command.py +540 -0
- tescmd-0.1.2/src/tescmd/api/energy.py +146 -0
- tescmd-0.1.2/src/tescmd/api/errors.py +76 -0
- tescmd-0.1.2/src/tescmd/api/partner.py +40 -0
- tescmd-0.1.2/src/tescmd/api/sharing.py +65 -0
- tescmd-0.1.2/src/tescmd/api/signed_command.py +277 -0
- tescmd-0.1.2/src/tescmd/api/user.py +38 -0
- tescmd-0.1.2/src/tescmd/api/vehicle.py +150 -0
- tescmd-0.1.2/src/tescmd/auth/__init__.py +1 -0
- tescmd-0.1.2/src/tescmd/auth/oauth.py +312 -0
- tescmd-0.1.2/src/tescmd/auth/server.py +108 -0
- tescmd-0.1.2/src/tescmd/auth/token_store.py +273 -0
- tescmd-0.1.2/src/tescmd/ble/__init__.py +0 -0
- tescmd-0.1.2/src/tescmd/cache/__init__.py +6 -0
- tescmd-0.1.2/src/tescmd/cache/keys.py +51 -0
- tescmd-0.1.2/src/tescmd/cache/response_cache.py +213 -0
- tescmd-0.1.2/src/tescmd/cli/__init__.py +0 -0
- tescmd-0.1.2/src/tescmd/cli/_client.py +603 -0
- tescmd-0.1.2/src/tescmd/cli/_options.py +126 -0
- tescmd-0.1.2/src/tescmd/cli/auth.py +682 -0
- tescmd-0.1.2/src/tescmd/cli/billing.py +240 -0
- tescmd-0.1.2/src/tescmd/cli/cache.py +85 -0
- tescmd-0.1.2/src/tescmd/cli/charge.py +610 -0
- tescmd-0.1.2/src/tescmd/cli/climate.py +501 -0
- tescmd-0.1.2/src/tescmd/cli/energy.py +385 -0
- tescmd-0.1.2/src/tescmd/cli/key.py +611 -0
- tescmd-0.1.2/src/tescmd/cli/main.py +601 -0
- tescmd-0.1.2/src/tescmd/cli/media.py +146 -0
- tescmd-0.1.2/src/tescmd/cli/nav.py +242 -0
- tescmd-0.1.2/src/tescmd/cli/partner.py +112 -0
- tescmd-0.1.2/src/tescmd/cli/raw.py +75 -0
- tescmd-0.1.2/src/tescmd/cli/security.py +495 -0
- tescmd-0.1.2/src/tescmd/cli/setup.py +786 -0
- tescmd-0.1.2/src/tescmd/cli/sharing.py +188 -0
- tescmd-0.1.2/src/tescmd/cli/software.py +81 -0
- tescmd-0.1.2/src/tescmd/cli/status.py +106 -0
- tescmd-0.1.2/src/tescmd/cli/trunk.py +240 -0
- tescmd-0.1.2/src/tescmd/cli/user.py +145 -0
- tescmd-0.1.2/src/tescmd/cli/vehicle.py +837 -0
- tescmd-0.1.2/src/tescmd/config/__init__.py +0 -0
- tescmd-0.1.2/src/tescmd/crypto/__init__.py +19 -0
- tescmd-0.1.2/src/tescmd/crypto/ecdh.py +46 -0
- tescmd-0.1.2/src/tescmd/crypto/keys.py +122 -0
- tescmd-0.1.2/src/tescmd/deploy/__init__.py +0 -0
- tescmd-0.1.2/src/tescmd/deploy/github_pages.py +268 -0
- tescmd-0.1.2/src/tescmd/models/__init__.py +85 -0
- tescmd-0.1.2/src/tescmd/models/auth.py +108 -0
- tescmd-0.1.2/src/tescmd/models/command.py +18 -0
- tescmd-0.1.2/src/tescmd/models/config.py +63 -0
- tescmd-0.1.2/src/tescmd/models/energy.py +56 -0
- tescmd-0.1.2/src/tescmd/models/sharing.py +26 -0
- tescmd-0.1.2/src/tescmd/models/user.py +37 -0
- tescmd-0.1.2/src/tescmd/models/vehicle.py +185 -0
- tescmd-0.1.2/src/tescmd/output/__init__.py +5 -0
- tescmd-0.1.2/src/tescmd/output/formatter.py +132 -0
- tescmd-0.1.2/src/tescmd/output/json_output.py +83 -0
- tescmd-0.1.2/src/tescmd/output/rich_output.py +809 -0
- tescmd-0.1.2/src/tescmd/protocol/__init__.py +23 -0
- tescmd-0.1.2/src/tescmd/protocol/commands.py +175 -0
- tescmd-0.1.2/src/tescmd/protocol/encoder.py +122 -0
- tescmd-0.1.2/src/tescmd/protocol/metadata.py +116 -0
- tescmd-0.1.2/src/tescmd/protocol/payloads.py +621 -0
- tescmd-0.1.2/src/tescmd/protocol/protobuf/__init__.py +6 -0
- tescmd-0.1.2/src/tescmd/protocol/protobuf/messages.py +564 -0
- tescmd-0.1.2/src/tescmd/protocol/session.py +318 -0
- tescmd-0.1.2/src/tescmd/protocol/signer.py +84 -0
- tescmd-0.1.2/src/tescmd/py.typed +0 -0
- tescmd-0.1.2/tests/__init__.py +0 -0
- tescmd-0.1.2/tests/_internal/__init__.py +0 -0
- tescmd-0.1.2/tests/_internal/test_async_utils.py +32 -0
- tescmd-0.1.2/tests/_internal/test_vin.py +65 -0
- tescmd-0.1.2/tests/api/__init__.py +0 -0
- tescmd-0.1.2/tests/api/test_client.py +221 -0
- tescmd-0.1.2/tests/api/test_command_api.py +719 -0
- tescmd-0.1.2/tests/api/test_energy_api.py +441 -0
- tescmd-0.1.2/tests/api/test_partner_api.py +139 -0
- tescmd-0.1.2/tests/api/test_sharing_api.py +270 -0
- tescmd-0.1.2/tests/api/test_signed_command.py +299 -0
- tescmd-0.1.2/tests/api/test_user_api.py +252 -0
- tescmd-0.1.2/tests/api/test_vehicle_api.py +611 -0
- tescmd-0.1.2/tests/auth/__init__.py +0 -0
- tescmd-0.1.2/tests/auth/test_oauth.py +44 -0
- tescmd-0.1.2/tests/auth/test_oauth_extended.py +125 -0
- tescmd-0.1.2/tests/auth/test_server.py +52 -0
- tescmd-0.1.2/tests/auth/test_token_store.py +115 -0
- tescmd-0.1.2/tests/auth/test_token_store_fallback.py +138 -0
- tescmd-0.1.2/tests/auth/test_token_store_file.py +156 -0
- tescmd-0.1.2/tests/cache/__init__.py +0 -0
- tescmd-0.1.2/tests/cache/test_generic_cache.py +168 -0
- tescmd-0.1.2/tests/cache/test_keys.py +38 -0
- tescmd-0.1.2/tests/cache/test_response_cache.py +211 -0
- tescmd-0.1.2/tests/cli/__init__.py +0 -0
- tescmd-0.1.2/tests/cli/_helpers.py +27 -0
- tescmd-0.1.2/tests/cli/conftest.py +26 -0
- tescmd-0.1.2/tests/cli/test_auth.py +74 -0
- tescmd-0.1.2/tests/cli/test_auth_exec.py +188 -0
- tescmd-0.1.2/tests/cli/test_cache.py +100 -0
- tescmd-0.1.2/tests/cli/test_cached_api_call.py +351 -0
- tescmd-0.1.2/tests/cli/test_charge_exec.py +434 -0
- tescmd-0.1.2/tests/cli/test_cli_integration.py +189 -0
- tescmd-0.1.2/tests/cli/test_climate_exec.py +463 -0
- tescmd-0.1.2/tests/cli/test_energy.py +173 -0
- tescmd-0.1.2/tests/cli/test_energy_exec.py +831 -0
- tescmd-0.1.2/tests/cli/test_error_handlers.py +114 -0
- tescmd-0.1.2/tests/cli/test_key.py +255 -0
- tescmd-0.1.2/tests/cli/test_key_enroll.py +202 -0
- tescmd-0.1.2/tests/cli/test_key_unenroll.py +136 -0
- tescmd-0.1.2/tests/cli/test_main_errors.py +71 -0
- tescmd-0.1.2/tests/cli/test_media.py +93 -0
- tescmd-0.1.2/tests/cli/test_media_exec.py +346 -0
- tescmd-0.1.2/tests/cli/test_nav.py +101 -0
- tescmd-0.1.2/tests/cli/test_nav_exec.py +503 -0
- tescmd-0.1.2/tests/cli/test_partner.py +44 -0
- tescmd-0.1.2/tests/cli/test_raw.py +63 -0
- tescmd-0.1.2/tests/cli/test_raw_exec.py +225 -0
- tescmd-0.1.2/tests/cli/test_security_exec.py +679 -0
- tescmd-0.1.2/tests/cli/test_setup.py +561 -0
- tescmd-0.1.2/tests/cli/test_setup_scope_check.py +158 -0
- tescmd-0.1.2/tests/cli/test_sharing.py +98 -0
- tescmd-0.1.2/tests/cli/test_sharing_exec.py +484 -0
- tescmd-0.1.2/tests/cli/test_software.py +63 -0
- tescmd-0.1.2/tests/cli/test_software_exec.py +327 -0
- tescmd-0.1.2/tests/cli/test_status_exec.py +247 -0
- tescmd-0.1.2/tests/cli/test_tier_enforcement.py +143 -0
- tescmd-0.1.2/tests/cli/test_trunk_exec.py +409 -0
- tescmd-0.1.2/tests/cli/test_user.py +49 -0
- tescmd-0.1.2/tests/cli/test_user_exec.py +210 -0
- tescmd-0.1.2/tests/cli/test_vcsec_guard.py +218 -0
- tescmd-0.1.2/tests/cli/test_vehicle_exec.py +542 -0
- tescmd-0.1.2/tests/cli/test_vehicle_power_exec.py +108 -0
- tescmd-0.1.2/tests/cli/test_verbose.py +78 -0
- tescmd-0.1.2/tests/cli/test_wake_confirmation.py +157 -0
- tescmd-0.1.2/tests/conftest.py +70 -0
- tescmd-0.1.2/tests/crypto/__init__.py +0 -0
- tescmd-0.1.2/tests/crypto/test_ecdh.py +94 -0
- tescmd-0.1.2/tests/crypto/test_keys.py +132 -0
- tescmd-0.1.2/tests/deploy/__init__.py +0 -0
- tescmd-0.1.2/tests/deploy/test_github_pages.py +304 -0
- tescmd-0.1.2/tests/models/__init__.py +0 -0
- tescmd-0.1.2/tests/models/test_auth.py +89 -0
- tescmd-0.1.2/tests/models/test_config.py +87 -0
- tescmd-0.1.2/tests/models/test_energy.py +94 -0
- tescmd-0.1.2/tests/models/test_sharing.py +70 -0
- tescmd-0.1.2/tests/models/test_user_models.py +57 -0
- tescmd-0.1.2/tests/models/test_vehicle.py +155 -0
- tescmd-0.1.2/tests/output/__init__.py +0 -0
- tescmd-0.1.2/tests/output/test_formatter.py +62 -0
- tescmd-0.1.2/tests/output/test_json_output.py +138 -0
- tescmd-0.1.2/tests/output/test_rich_output.py +471 -0
- tescmd-0.1.2/tests/protocol/__init__.py +0 -0
- tescmd-0.1.2/tests/protocol/conftest.py +44 -0
- tescmd-0.1.2/tests/protocol/test_commands.py +162 -0
- tescmd-0.1.2/tests/protocol/test_encoder.py +293 -0
- tescmd-0.1.2/tests/protocol/test_metadata.py +222 -0
- tescmd-0.1.2/tests/protocol/test_session.py +332 -0
- tescmd-0.1.2/tests/protocol/test_signer.py +124 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"WebFetch(domain:developer.tesla.com)",
|
|
5
|
+
"WebFetch(domain:github.com)",
|
|
6
|
+
"WebFetch(domain:raw.githubusercontent.com)",
|
|
7
|
+
"WebFetch(domain:api.github.com)",
|
|
8
|
+
"Bash(pip install:*)",
|
|
9
|
+
"Bash(git -C /Users/oceanswave/Projects/tescmd init:*)",
|
|
10
|
+
"Bash(git -C /Users/oceanswave/Projects/tescmd add pyproject.toml src/ tests/ .gitignore CLAUDE.md README.md docs/)",
|
|
11
|
+
"Bash(git -C /Users/oceanswave/Projects/tescmd commit -m \"feat: project skeleton with pyproject.toml and package structure\")",
|
|
12
|
+
"Bash(git -C /Users/oceanswave/Projects/tescmd log --oneline)",
|
|
13
|
+
"Bash(python:*)",
|
|
14
|
+
"Bash(git -C /Users/oceanswave/Projects/tescmd log --oneline -5)",
|
|
15
|
+
"Bash(git -C /Users/oceanswave/Projects/tescmd add src/tescmd/_internal/async_utils.py src/tescmd/_internal/vin.py)",
|
|
16
|
+
"Bash(git -C /Users/oceanswave/Projects/tescmd commit -m \"$\\(cat <<''EOF''\nfeat: add async utils and VIN resolution\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
|
|
17
|
+
"Bash(git -C /Users/oceanswave/Projects/tescmd status)",
|
|
18
|
+
"Bash(pytest:*)",
|
|
19
|
+
"Bash(git add:*)",
|
|
20
|
+
"Bash(git commit:*)",
|
|
21
|
+
"Bash(ruff check:*)",
|
|
22
|
+
"Bash(ruff format:*)",
|
|
23
|
+
"Bash(mypy:*)",
|
|
24
|
+
"Bash(tescmd --help:*)",
|
|
25
|
+
"Bash(tree:*)",
|
|
26
|
+
"mcp__plugin_context7_context7__resolve-library-id",
|
|
27
|
+
"mcp__plugin_context7_context7__query-docs",
|
|
28
|
+
"WebSearch",
|
|
29
|
+
"WebFetch(domain:pypi.org)",
|
|
30
|
+
"WebFetch(domain:developer.tessie.com)",
|
|
31
|
+
"Bash(ls:*)",
|
|
32
|
+
"Bash(wc:*)",
|
|
33
|
+
"Bash(grep:*)",
|
|
34
|
+
"Bash(while read file)",
|
|
35
|
+
"Bash(do grep -h \"^from\\\\|^import\" \"$file\")",
|
|
36
|
+
"Bash(done)",
|
|
37
|
+
"Bash(test:*)",
|
|
38
|
+
"Bash(xargs:*)",
|
|
39
|
+
"Bash(curl:*)",
|
|
40
|
+
"WebFetch(domain:www.tesla.com)",
|
|
41
|
+
"Bash(find:*)",
|
|
42
|
+
"Bash(gh api:*)",
|
|
43
|
+
"mcp__plugin_playwright_playwright__browser_navigate",
|
|
44
|
+
"mcp__plugin_playwright_playwright__browser_close"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
}
|
tescmd-0.1.2/.gitignore
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.2] - 2025-01-31
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Universal read-command caching** — every read command is now transparently cached with tiered TTLs (STATIC 1h, SLOW 5m, DEFAULT 1m, FAST 30s); bots can call tescmd as often as needed — within the TTL window, responses are instant and free
|
|
13
|
+
- **Generic cache key scheme** — `generic_cache_key(scope, identifier, endpoint, params)` generates scope-aware keys (`vin`, `site`, `account`, `partner`) for any API endpoint
|
|
14
|
+
- **`cached_api_call()` helper** — unified async helper that handles cache lookup, fetch, serialisation (Pydantic/dict/list/scalar), and storage for all non-vehicle-state read commands
|
|
15
|
+
- **Site-scoped cache invalidation** — `invalidate_cache_for_site()` clears energy site entries after write commands; `invalidate_cache_for_vin()` now also clears generic vin-scoped keys
|
|
16
|
+
- **`cache clear` options** — `--site SITE_ID` and `--scope {account,partner}` flags for targeted cache clearing alongside existing `--vin`
|
|
17
|
+
- **Partner endpoints** — `partner public-key`, `partner telemetry-error-vins`, `partner telemetry-errors` for partner account data (require client credentials)
|
|
18
|
+
- **Billing endpoints** — `billing history`, `billing sessions`, `billing invoice` for Supercharger charging data
|
|
19
|
+
- **Cross-platform file permissions** — `_internal/permissions.py` provides `secure_file()` using `chmod 0600` on Unix and `icacls` on Windows
|
|
20
|
+
- **Token store file backend** — `_FileBackend` with atomic writes and restricted permissions as fallback when keyring is unavailable
|
|
21
|
+
- **Spec-driven Fleet API validation** — `scripts/validate_fleet_api.py` validates implementation against `spec/fleet_api_spec.json` using AST introspection
|
|
22
|
+
- **6 missing Fleet API commands** — added `managed_charging_set_amps`, `managed_charging_set_location`, `managed_charging_set_schedule`, `add_charge_schedule`, `remove_charge_schedule`, `clear_charge_schedules`
|
|
23
|
+
- **Configurable display units** — `--units metric` flag switches all display values to °C/km/bar; individual env vars (`TESLA_TEMP_UNIT`, `TESLA_DISTANCE_UNIT`, `TESLA_PRESSURE_UNIT`) for granular control
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- Aligned schedule/departure command parameters with Tesla Go SDK (correct param names and types)
|
|
28
|
+
- Fixed energy endpoint paths to match Fleet API spec
|
|
29
|
+
- Fixed Rich markup escaping bug in command output
|
|
30
|
+
- Aligned command parameters (3 param gaps) with Go SDK specs
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- Response cache documentation in CLAUDE.md expanded to cover universal caching, TTL tiers, and generic cache key scheme
|
|
35
|
+
|
|
36
|
+
## [0.1.1]
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- **`status` command** — `tescmd status` shows current configuration, auth, cache, and key status at a glance
|
|
41
|
+
- **Retry option in wake prompt** — when a vehicle is asleep, the interactive prompt now offers `[R] Retry` alongside `[W] Wake via API` and `[C] Cancel`, allowing users to wake the vehicle for free via the Tesla app and retry without restarting the command
|
|
42
|
+
- **Key enrollment** — `tescmd key enroll <VIN>` sends the public key to the vehicle and guides the user through Tesla app approval with interactive [C]heck/[R]esend/[Q]uit prompt, `--wait` auto-polling, and JSON mode support
|
|
43
|
+
- **Tier enforcement** — readonly tier now blocks write commands with a clear error and upgrade guidance (`tescmd setup`)
|
|
44
|
+
- **Vehicle Command Protocol** — ECDH session management, HMAC-SHA256 command signing, and protobuf RoutableMessage encoding for the `signed_command` endpoint; commands are automatically signed when keys are available (`command_protocol=auto`)
|
|
45
|
+
- **SignedCommandAPI** — composition wrapper that transparently routes signed commands through the Vehicle Command Protocol while falling back to unsigned REST for `wake_up` and unknown commands
|
|
46
|
+
- **`command_protocol` setting** — `auto` (default), `signed`, or `unsigned` to control command routing; configurable via `TESLA_COMMAND_PROTOCOL` env var
|
|
47
|
+
- **Enrollment step in setup wizard** — full-tier setup now offers to enroll the key on a vehicle after key generation
|
|
48
|
+
- **Friendly command output** — all vehicle commands now display descriptive success messages (e.g. "Climate control turned on.", "Doors locked.") instead of bare "OK"
|
|
49
|
+
- **E2E test script** — `scripts/e2e_test.py` provides interactive end-to-end command testing against a live vehicle with per-command confirmation, category filtering, and destructive-command skipping
|
|
50
|
+
|
|
51
|
+
## [0.1.0]
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
|
|
55
|
+
- OAuth2 PKCE authentication with browser-based login flow
|
|
56
|
+
- Vehicle state queries: battery, charge, climate, drive, location, doors, windows, trunks, tire pressure
|
|
57
|
+
- Vehicle commands: charge start/stop/limit/schedule, climate on/off/set/seats/wheel, lock/unlock, sentry, trunk/frunk, windows, media, navigation, software updates, HomeLink, speed limits, PIN management
|
|
58
|
+
- Energy products: Powerwall live status, site info, backup reserve, operation mode, storm mode, TOU settings, charging history, calendar history, grid config
|
|
59
|
+
- User account: profile info, region, orders, feature config
|
|
60
|
+
- Vehicle sharing: add/remove drivers, create/redeem/revoke invites
|
|
61
|
+
- Rich terminal output with tables, panels, and status indicators
|
|
62
|
+
- JSON output mode for scripting and agent integration
|
|
63
|
+
- Configurable display units (F/C, mi/km, PSI/bar)
|
|
64
|
+
- Response caching with configurable TTL for API cost reduction
|
|
65
|
+
- Cost-aware wake confirmation (interactive prompt or `--wake` flag)
|
|
66
|
+
- Multi-profile configuration support
|
|
67
|
+
- EC key generation and Tesla Developer Portal registration
|
|
68
|
+
- Raw API access (`raw get`, `raw post`) for uncovered endpoints
|
|
69
|
+
- First-run setup wizard with Fleet Telemetry cost guidance
|
tescmd-0.1.2/CLAUDE.md
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
# CLAUDE.md — Project Context for Claude Code
|
|
2
|
+
|
|
3
|
+
## Project Overview
|
|
4
|
+
|
|
5
|
+
**tescmd** is a Python CLI application that queries data from and sends commands to Tesla vehicles via the [Tesla Fleet API](https://developer.tesla.com/docs/fleet-api). The current implementation covers OAuth2 authentication, key management, vehicle state queries (battery, location, climate, drive state, tire pressure, trunks, and more), and both human-friendly (Rich TUI) and machine-friendly (JSON) output with configurable display units.
|
|
6
|
+
|
|
7
|
+
**Current scope:** auth, vehicle queries, vehicle commands (charge, climate, security, trunk, media, nav, software), energy product management (including telemetry), Supercharger charging history and invoices, user account info, vehicle sharing, partner account endpoints (public key, fleet telemetry errors), key management with enrollment and unenrollment, Vehicle Command Protocol (ECDH sessions + HMAC-signed protobuf commands), tier enforcement, initial setup, response caching with cost-aware wake confirmation, Fleet Telemetry configuration management (create/delete/errors), configuration status dashboard, and GitHub Pages key deployment.
|
|
8
|
+
|
|
9
|
+
## Tech Stack
|
|
10
|
+
|
|
11
|
+
- **Python 3.11+** (required for `tomllib`, `StrEnum`, modern typing)
|
|
12
|
+
- **pydantic v2** — request/response models, settings management
|
|
13
|
+
- **rich** — terminal tables, panels, spinners, progress bars
|
|
14
|
+
- **click** — CLI argument parsing and command routing
|
|
15
|
+
- **httpx** — async HTTP client for Fleet API calls
|
|
16
|
+
- **cryptography** — EC key generation, PEM handling, ECDH key exchange
|
|
17
|
+
- **protobuf** — protobuf serialization for Vehicle Command Protocol messages
|
|
18
|
+
- **keyring** — OS-level credential storage for tokens
|
|
19
|
+
- **python-dotenv** — `.env` file loading
|
|
20
|
+
- **bleak** — BLE communication for key enrollment (optional; portal enrollment is primary)
|
|
21
|
+
|
|
22
|
+
## Project Structure
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
src/tescmd/
|
|
26
|
+
├── __init__.py # Package version
|
|
27
|
+
├── __main__.py # Entry point (python -m tescmd)
|
|
28
|
+
├── cli/ # CLI layer (click-based)
|
|
29
|
+
│ ├── __init__.py
|
|
30
|
+
│ ├── main.py # Root Click group, dispatch, AppContext
|
|
31
|
+
│ ├── _options.py # Shared Click options/decorators
|
|
32
|
+
│ ├── _client.py # API client builders, auto_wake, cached_vehicle_data, cached_api_call, TTL tiers
|
|
33
|
+
│ ├── auth.py # auth login, logout, status, refresh, export, register, import
|
|
34
|
+
│ ├── cache.py # cache clear, cache status
|
|
35
|
+
│ ├── charge.py # charge status, start, stop, limit, limit-max, limit-std, amps, port-open, port-close, schedule, departure, precondition-add/remove, add/remove-schedule, clear-schedules, clear-preconditions, managed-amps/location/schedule
|
|
36
|
+
│ ├── billing.py # billing history, sessions, invoice (Supercharger billing data)
|
|
37
|
+
│ ├── climate.py # climate status, on, off, set, precondition, seat, seat-cool, wheel-heater, overheat, bioweapon, keeper, cop-temp, auto-seat, auto-wheel, wheel-level
|
|
38
|
+
│ ├── security.py # security status, lock, auto-secure, unlock, sentry, valet, valet-reset, remote-start, flash, honk, boombox, pin-to-drive, pin-reset, pin-clear-admin, speed-limit, speed-clear, speed-clear-admin, guest-mode, erase-data
|
|
39
|
+
│ ├── status.py # status (show config, auth, and cache status)
|
|
40
|
+
│ ├── trunk.py # trunk open, close, frunk, window, sunroof, tonneau-open/close/stop
|
|
41
|
+
│ ├── vehicle.py # 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 subgroup: config, create, delete, errors
|
|
42
|
+
│ ├── media.py # media play-pause, next/prev track, next/prev fav, volume
|
|
43
|
+
│ ├── nav.py # nav send, gps, supercharger, homelink, waypoints
|
|
44
|
+
│ ├── partner.py # partner public-key, telemetry-error-vins, telemetry-errors
|
|
45
|
+
│ ├── software.py # software status, schedule, cancel
|
|
46
|
+
│ ├── energy.py # energy list, status, live, backup, mode, storm, tou, history, off-grid, grid-config, telemetry, calendar
|
|
47
|
+
│ ├── user.py # user me, region, orders, features
|
|
48
|
+
│ ├── sharing.py # sharing add/remove driver, create/redeem/revoke/list invites
|
|
49
|
+
│ ├── raw.py # raw get, raw post (arbitrary Fleet API access)
|
|
50
|
+
│ ├── key.py # key generate, deploy, validate, show, enroll, unenroll
|
|
51
|
+
│ └── setup.py # setup (interactive first-run wizard, key enrollment, Fleet Telemetry awareness)
|
|
52
|
+
├── api/ # API client layer
|
|
53
|
+
│ ├── __init__.py
|
|
54
|
+
│ ├── client.py # TeslaFleetClient (base HTTP client)
|
|
55
|
+
│ ├── vehicle.py # VehicleAPI (vehicle data, nearby chargers, alerts, drivers, fleet telemetry, subscriptions, specs)
|
|
56
|
+
│ ├── command.py # CommandAPI (~78 vehicle commands, unsigned REST)
|
|
57
|
+
│ ├── signed_command.py # SignedCommandAPI (Vehicle Command Protocol routing)
|
|
58
|
+
│ ├── energy.py # EnergyAPI (Powerwall/energy product endpoints, telemetry)
|
|
59
|
+
│ ├── charging.py # ChargingAPI (Supercharger charging history, sessions, invoices)
|
|
60
|
+
│ ├── partner.py # PartnerAPI (public key, fleet telemetry errors — requires partner token)
|
|
61
|
+
│ ├── sharing.py # SharingAPI (driver and invite management)
|
|
62
|
+
│ ├── user.py # UserAPI (account info, region, orders, features)
|
|
63
|
+
│ └── errors.py # API error types (incl. TierError, SessionError, KeyNotEnrolledError)
|
|
64
|
+
├── cache/ # Response caching
|
|
65
|
+
│ ├── __init__.py # Re-exports ResponseCache, generic_cache_key
|
|
66
|
+
│ ├── response_cache.py # ResponseCache (file-based JSON with TTL, generic cache)
|
|
67
|
+
│ └── keys.py # Cache key generation (VIN + endpoint hash, generic_cache_key)
|
|
68
|
+
├── models/ # Pydantic models
|
|
69
|
+
│ ├── __init__.py # Re-exports all models (40 symbols)
|
|
70
|
+
│ ├── vehicle.py # Vehicle, VehicleData, DriveState, ChargeState, ClimateState, VehicleState, VehicleConfig, GuiSettings, SoftwareUpdateInfo, SuperchargerInfo, DestChargerInfo, NearbyChargingSites
|
|
71
|
+
│ ├── energy.py # LiveStatus, SiteInfo, CalendarHistory, GridImportExportConfig
|
|
72
|
+
│ ├── user.py # UserInfo, UserRegion, VehicleOrder, FeatureConfig
|
|
73
|
+
│ ├── sharing.py # ShareDriverInfo, ShareInvite
|
|
74
|
+
│ ├── auth.py # TokenData, TokenMeta, AuthConfig
|
|
75
|
+
│ ├── command.py # CommandResponse, CommandResult
|
|
76
|
+
│ └── config.py # AppSettings (pydantic-settings, incl. cache settings)
|
|
77
|
+
├── auth/ # Authentication
|
|
78
|
+
│ ├── __init__.py
|
|
79
|
+
│ ├── oauth.py # OAuth2 PKCE flow, token refresh, partner registration
|
|
80
|
+
│ ├── token_store.py # Token persistence (keyring backend + file-based fallback)
|
|
81
|
+
│ └── server.py # Local callback server for OAuth redirect
|
|
82
|
+
├── protocol/ # Vehicle Command Protocol
|
|
83
|
+
│ ├── __init__.py # Re-exports: Session, SessionManager, CommandSpec, etc.
|
|
84
|
+
│ ├── protobuf/ # Protobuf message definitions
|
|
85
|
+
│ │ ├── __init__.py
|
|
86
|
+
│ │ └── messages.py # RoutableMessage, SessionInfo, Domain, SignatureData, etc.
|
|
87
|
+
│ ├── session.py # ECDH session management (SessionManager)
|
|
88
|
+
│ ├── signer.py # HMAC-SHA256 command signing
|
|
89
|
+
│ ├── metadata.py # TLV serialization for command metadata
|
|
90
|
+
│ ├── commands.py # Command registry (name → domain + signing requirement)
|
|
91
|
+
│ ├── payloads.py # Protobuf payload builders for Vehicle Command Protocol
|
|
92
|
+
│ └── encoder.py # RoutableMessage assembly + base64 encoding
|
|
93
|
+
├── crypto/ # Key management and ECDH
|
|
94
|
+
│ ├── __init__.py
|
|
95
|
+
│ ├── keys.py # EC key generation, loading, PEM export
|
|
96
|
+
│ └── ecdh.py # ECDH key exchange, session key derivation
|
|
97
|
+
├── output/ # Output formatting
|
|
98
|
+
│ ├── __init__.py
|
|
99
|
+
│ ├── formatter.py # OutputFormatter (auto-detect TTY vs pipe)
|
|
100
|
+
│ ├── rich_output.py # Rich tables, panels, status displays, DisplayUnits
|
|
101
|
+
│ └── json_output.py # Structured JSON output
|
|
102
|
+
├── ble/ # BLE communication (stub — enrollment not yet wired)
|
|
103
|
+
│ └── __init__.py
|
|
104
|
+
├── deploy/ # Key deployment helpers
|
|
105
|
+
│ ├── __init__.py
|
|
106
|
+
│ └── github_pages.py # GitHub Pages deployment for public key hosting
|
|
107
|
+
├── config/ # Configuration (stub — settings in models/config.py)
|
|
108
|
+
│ └── __init__.py
|
|
109
|
+
└── _internal/ # Shared utilities
|
|
110
|
+
├── __init__.py
|
|
111
|
+
├── vin.py # Smart VIN resolution
|
|
112
|
+
├── async_utils.py # asyncio helpers (run_async)
|
|
113
|
+
└── permissions.py # Cross-platform file permissions (chmod 0600 / icacls)
|
|
114
|
+
|
|
115
|
+
spec/
|
|
116
|
+
└── fleet_api_spec.json # Canonical Fleet API specification (endpoints, params, types)
|
|
117
|
+
|
|
118
|
+
scripts/
|
|
119
|
+
└── validate_fleet_api.py # Spec-driven API coverage validator (AST-based)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Key Models (`models/vehicle.py`)
|
|
123
|
+
|
|
124
|
+
The Pydantic vehicle models cover an extensive set of Tesla Fleet API fields:
|
|
125
|
+
|
|
126
|
+
- **ChargeState** — battery %, range (rated/ideal/estimated), usable %, charge limit, rate, voltage, current, charger power, charger type, energy added, cable type, port latch, scheduled charging, battery heater, preconditioning
|
|
127
|
+
- **ClimateState** — inside/outside temp, driver/passenger setting, HVAC on/off, fan speed, defrost, front+rear seat heaters, steering wheel heater, cabin overheat protection, bioweapon defense mode, auto conditioning, preconditioning
|
|
128
|
+
- **VehicleState** — locked, odometer, sentry mode, firmware version, doors (4), windows (4), frunk/trunk (ft/rt), center display, dashcam, remote start, user present, homelink, TPMS tire pressure (4 wheels)
|
|
129
|
+
- **VehicleConfig** — car type, trim, color, wheels, roof color, navigation, trunk actuation, seat cooling, motorized charge port, power liftgate, EU vehicle
|
|
130
|
+
- **GuiSettings** — distance units, temperature units, charge rate units
|
|
131
|
+
|
|
132
|
+
Additional typed models:
|
|
133
|
+
|
|
134
|
+
- **SoftwareUpdateInfo** — status, version, install_perc, expected_duration_sec, scheduled_time_ms, download_perc
|
|
135
|
+
- **NearbyChargingSites** — superchargers (list of SuperchargerInfo), destination_charging (list of DestChargerInfo)
|
|
136
|
+
- **SiteInfo** — energy_site_id, site_name, resource_type, backup_reserve_percent, default_real_mode, storm_mode_enabled
|
|
137
|
+
- **CalendarHistory** — serial_number, time_series
|
|
138
|
+
- **GridImportExportConfig** — disallow_charge_from_grid_with_solar_installed, customer_preferred_export_rule
|
|
139
|
+
- **VehicleOrder** — order_id, vin, model, status
|
|
140
|
+
- **FeatureConfig** — signaling dict
|
|
141
|
+
- **ShareDriverInfo** — share_user_id, email, status, public_key
|
|
142
|
+
- **ShareInvite** — id, code, created_at, expires_at, status
|
|
143
|
+
|
|
144
|
+
All models use `extra="allow"` so unknown fields from the API are captured without validation errors.
|
|
145
|
+
|
|
146
|
+
### Display Units (`output/rich_output.py`)
|
|
147
|
+
|
|
148
|
+
Rich output supports configurable display units via `DisplayUnits`:
|
|
149
|
+
|
|
150
|
+
- **Pressure:** PSI (default) or bar
|
|
151
|
+
- **Temperature:** °F (default) or °C
|
|
152
|
+
- **Distance:** mi (default) or km
|
|
153
|
+
|
|
154
|
+
The Tesla API returns temperatures in Celsius, distances in miles, and tire pressures in bar. Conversions happen in the display layer only — models retain raw API values.
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from tescmd.output.rich_output import DisplayUnits, DistanceUnit, PressureUnit, TempUnit
|
|
158
|
+
|
|
159
|
+
# US defaults (no argument needed)
|
|
160
|
+
ro = RichOutput(console)
|
|
161
|
+
|
|
162
|
+
# Metric
|
|
163
|
+
ro = RichOutput(console, units=DisplayUnits(
|
|
164
|
+
pressure=PressureUnit.BAR,
|
|
165
|
+
temp=TempUnit.C,
|
|
166
|
+
distance=DistanceUnit.KM,
|
|
167
|
+
))
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Response Cache (`cache/response_cache.py`)
|
|
171
|
+
|
|
172
|
+
The Tesla Fleet API is pay-per-use — every call with status < 500 is billable, wake requests are the most expensive category (3/min limit), and their docs explicitly say `vehicle_data` should never be polled regularly. **All read commands** are transparently cached — bots can call tescmd as often as needed; within the TTL window, responses are instant and free.
|
|
173
|
+
|
|
174
|
+
The cache reduces API costs through four mechanisms:
|
|
175
|
+
|
|
176
|
+
1. **Universal read-command cache** — Every read command goes through `cached_api_call()` with a scope-aware TTL. Vehicle state queries additionally use `cached_vehicle_data()` with smart wake logic.
|
|
177
|
+
2. **Disk cache** — `ResponseCache` stores API responses as JSON files under `~/.cache/tescmd/`. Each entry has a TTL. Expired entries are cleaned up lazily on read.
|
|
178
|
+
3. **Wake state cache** — Tracks whether the vehicle was recently confirmed online (default 30s TTL). Skips redundant wake attempts when the vehicle is known to be awake.
|
|
179
|
+
4. **Wake confirmation prompt** — Before sending a billable wake API call, users are prompted interactively (TTY) or receive a structured error (JSON/piped) with guidance to wake via the Tesla app for free.
|
|
180
|
+
|
|
181
|
+
**TTL tiers** (defined in `cli/_client.py`):
|
|
182
|
+
|
|
183
|
+
| Tier | TTL | Use case | Example endpoints |
|
|
184
|
+
|------|-----|----------|-------------------|
|
|
185
|
+
| `TTL_STATIC` | 3600s (1h) | Rarely changes | specs, warranty, options, user.me, user.region, user.features, partner.public-key |
|
|
186
|
+
| `TTL_SLOW` | 300s (5m) | Changes infrequently | vehicle.list, fleet-status, drivers, subscriptions, energy.list, energy.status, sharing.list-invites |
|
|
187
|
+
| `TTL_DEFAULT` | 60s (1m) | Standard | vehicle.get, mobile-access, alerts, billing.history, user.orders |
|
|
188
|
+
| `TTL_FAST` | 30s | Location-dependent | nearby-chargers |
|
|
189
|
+
|
|
190
|
+
**Generic cache key scheme** (`cache/keys.py`):
|
|
191
|
+
|
|
192
|
+
`generic_cache_key(scope, identifier, endpoint, params)` generates keys in the format `{scope}_{identifier}_{sha256(endpoint+params)[:12]}`. Scopes: `vin`, `site`, `account`, `partner`. Params are sorted and hashed so different query parameters produce different cache entries.
|
|
193
|
+
|
|
194
|
+
**Cache file naming**:
|
|
195
|
+
- Legacy: `{vin}_{endpoint_hash}.json` (vehicle state data), `{vin}_wake.json`
|
|
196
|
+
- Generic: `{scope}_{identifier}_{hash}.json` (all other cached commands)
|
|
197
|
+
|
|
198
|
+
**Data flow for vehicle-state commands** (`cached_vehicle_data()` in `_client.py`):
|
|
199
|
+
|
|
200
|
+
1. Check disk cache → on hit, return `VehicleData.model_validate(cached)`
|
|
201
|
+
2. Check wake state cache → if recently online, try direct API fetch (skip wake)
|
|
202
|
+
3. If direct fetch raises `VehicleAsleepError`, fall back to `auto_wake()`
|
|
203
|
+
4. If no cached wake state, use `auto_wake()` directly
|
|
204
|
+
5. On success, cache the response and update wake state
|
|
205
|
+
|
|
206
|
+
**Data flow for all other read commands** (`cached_api_call()` in `_client.py`):
|
|
207
|
+
|
|
208
|
+
1. Compute `generic_cache_key(scope, identifier, endpoint, params)`
|
|
209
|
+
2. Check `cache.get_generic(key)` → on hit, emit cache metadata, return cached dict
|
|
210
|
+
3. On miss → call `fetch()`, serialise (Pydantic → dict), `cache.put_generic(key, data, ttl)`, return result
|
|
211
|
+
|
|
212
|
+
**Write-commands** (POST operations) do not cache responses but call `invalidate_cache_for_vin()` or `invalidate_cache_for_site()` after success to prevent stale reads.
|
|
213
|
+
|
|
214
|
+
**Intentionally NOT cached:**
|
|
215
|
+
- `energy live` — real-time power flow, stale in seconds
|
|
216
|
+
- `energy history/calendar/telemetry` — time-range parameterized, complex key management
|
|
217
|
+
- `billing invoice` — one-off document retrieval
|
|
218
|
+
- `raw get` / `raw post` — escape hatch, user controls caching
|
|
219
|
+
- `vehicle wake` — write operation
|
|
220
|
+
- Auth/key/setup commands — infrastructure, not data reads
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
from tescmd.cache import ResponseCache, generic_cache_key
|
|
224
|
+
|
|
225
|
+
cache = ResponseCache(cache_dir=Path("~/.cache/tescmd"), default_ttl=60, enabled=True)
|
|
226
|
+
|
|
227
|
+
# Legacy VIN-scoped cache (vehicle state data)
|
|
228
|
+
cache.put("VIN123", {"charge_state": {"battery_level": 72}}, endpoints=["charge_state"])
|
|
229
|
+
data = cache.get("VIN123", endpoints=["charge_state"]) # CacheResult or None
|
|
230
|
+
|
|
231
|
+
# Generic cache (any scope)
|
|
232
|
+
key = generic_cache_key("account", "global", "vehicle.list")
|
|
233
|
+
cache.put_generic(key, [{"vin": "VIN123"}], ttl=300)
|
|
234
|
+
result = cache.get_generic(key) # CacheResult or None
|
|
235
|
+
|
|
236
|
+
# Wake state
|
|
237
|
+
cache.put_wake_state("VIN123", "online", ttl=30)
|
|
238
|
+
is_online = cache.get_wake_state("VIN123") # True/False
|
|
239
|
+
|
|
240
|
+
# Clearing
|
|
241
|
+
cache.clear("VIN123") # per-VIN (legacy keys)
|
|
242
|
+
cache.clear_by_prefix("vin_VIN123_") # per-VIN (generic keys)
|
|
243
|
+
cache.clear_by_prefix("site_12345_") # per energy site
|
|
244
|
+
cache.clear_by_prefix("account_") # all account-level entries
|
|
245
|
+
cache.clear() # everything
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Wake Confirmation (`cli/_client.py`)
|
|
249
|
+
|
|
250
|
+
When a vehicle is asleep and a command needs to wake it, `auto_wake()` behaves differently based on context:
|
|
251
|
+
|
|
252
|
+
| Mode | `--wake` flag | Behavior |
|
|
253
|
+
|---|---|---|
|
|
254
|
+
| TTY (Rich) | Not set | Interactive prompt: `[W] Wake via API [R] Retry [C] Cancel` |
|
|
255
|
+
| TTY (Rich) | Set | Auto-wake without prompting |
|
|
256
|
+
| JSON / piped | Not set | Raise `VehicleAsleepError` with `--wake` guidance |
|
|
257
|
+
| JSON / piped | Set | Auto-wake without prompting |
|
|
258
|
+
|
|
259
|
+
The `[R] Retry` option allows users to wake the vehicle for free via the Tesla mobile app and then retry the command without a billable API call. If the vehicle is still asleep after retry, the prompt re-appears. The `vehicle wake` command is an explicit wake request and uses its own logic (no prompt needed). The wake state cache means the prompt only triggers when the vehicle is actually asleep — if it was recently confirmed online, the prompt is skipped entirely.
|
|
260
|
+
|
|
261
|
+
## Coding Conventions
|
|
262
|
+
|
|
263
|
+
- **Type hints everywhere** — all function signatures, all variables where non-obvious
|
|
264
|
+
- **async/await** — all API calls are async; CLI entry points use `run_async()` helper
|
|
265
|
+
- **Pydantic models** — all API request/response payloads; all configuration
|
|
266
|
+
- **src layout** — code lives under `src/tescmd/`, tests under `tests/`
|
|
267
|
+
- **No star imports** — explicit imports only
|
|
268
|
+
- **Single responsibility** — CLI modules handle args + output, API modules handle HTTP
|
|
269
|
+
- **Composition over inheritance** — `VehicleAPI` wraps `TeslaFleetClient`, doesn't extend it
|
|
270
|
+
|
|
271
|
+
## Build System
|
|
272
|
+
|
|
273
|
+
- **hatchling** via `pyproject.toml`
|
|
274
|
+
- Entry point: `tescmd = "tescmd.cli.main:main"`
|
|
275
|
+
- No `setup.py` or `setup.cfg`
|
|
276
|
+
|
|
277
|
+
## Testing
|
|
278
|
+
|
|
279
|
+
- **pytest** + **pytest-asyncio** + **pytest-httpx**
|
|
280
|
+
- Test files mirror source: `tests/cli/test_auth.py`, `tests/api/test_client.py`, etc.
|
|
281
|
+
- Use `pytest-httpx` to mock HTTP responses (no live API calls in tests)
|
|
282
|
+
- Async tests use `@pytest.mark.asyncio`
|
|
283
|
+
- Current count: ~910 tests
|
|
284
|
+
|
|
285
|
+
## Linting & Formatting
|
|
286
|
+
|
|
287
|
+
- **ruff** — linting and formatting (replaces flake8, isort, black)
|
|
288
|
+
- **mypy** — strict mode, all code fully typed
|
|
289
|
+
- Config in `pyproject.toml`
|
|
290
|
+
|
|
291
|
+
## Key Architectural Decisions
|
|
292
|
+
|
|
293
|
+
1. **Composition over inheritance** — API classes wrap `TeslaFleetClient` via constructor injection
|
|
294
|
+
2. **REST-first with portal key enrollment** — all commands go over REST; key enrollment uses Tesla Developer Portal (remote, confirmed via Tesla app); BLE enrollment is an optional alternative requiring physical proximity
|
|
295
|
+
3. **Output auto-detection** — TTY → Rich panels/tables; piped → JSON; `--quiet` → minimal stderr only
|
|
296
|
+
4. **Error output stream routing** — JSON/piped mode writes errors to **stderr** so stdout stays clean for machine-parseable data (scripts can safely `| jq`). TTY/Rich mode keeps errors on **stdout** because the user is looking at the terminal directly — splitting streams would be worse UX there. This split lives in `OutputFormatter.output_error()` and the error handlers in `cli/main.py`. Interactive prompts (wake confirmation spinner, enrollment approval) always use stdout via Rich since they are inherently TTY-only.
|
|
297
|
+
5. **Smart VIN resolution** — positional arg > `--vin` flag > profile default > interactive picker
|
|
298
|
+
6. **Settings resolution** — CLI args > env vars (`.env` loaded via python-dotenv) > `config.toml` profile > defaults
|
|
299
|
+
7. **click** — works well with command structure, async patterns
|
|
300
|
+
8. **httpx async** — clean async API, good type stubs, easily testable with pytest-httpx
|
|
301
|
+
9. **Browser-based auth** — `tescmd auth login` opens system browser for OAuth2 PKCE flow with local callback server
|
|
302
|
+
10. **Display-layer unit conversion** — models retain raw API values (Celsius, miles, bar); conversion to user-preferred units happens in `RichOutput` via `DisplayUnits`
|
|
303
|
+
11. **Universal response caching** — file-based JSON cache under `~/.cache/tescmd/` with tiered TTLs (STATIC 1h / SLOW 5m / DEFAULT 1m / FAST 30s). **All** read commands are cached via `cached_api_call()` with scope-aware keys (`vin`, `site`, `account`, `partner`). Vehicle state queries use the dedicated `cached_vehicle_data()` with smart wake logic. Write-commands invalidate via `invalidate_cache_for_vin()` / `invalidate_cache_for_site()`. No new dependencies (stdlib `json`, `hashlib`, `time`, `pathlib`).
|
|
304
|
+
12. **Cost-aware wake** — `auto_wake()` prompts before sending billable wake API calls. TTY users get an interactive choice; JSON/piped consumers get structured errors. The `--wake` flag opts in to auto-wake for scripts that accept the cost.
|
|
305
|
+
13. **Smart wake state** — wake state is cached separately (30s TTL). If the vehicle was recently confirmed online, both the prompt and the wake API call are skipped entirely.
|
|
306
|
+
14. **Vehicle Command Protocol** — `SignedCommandAPI` wraps `CommandAPI` via composition. When keys are enrolled and tier is `full`, `get_command_api()` returns `SignedCommandAPI` which transparently routes signed commands through ECDH session + HMAC path. Unsigned commands (`wake_up`) pass through to legacy REST. The `command_protocol` setting (`auto`/`signed`/`unsigned`) controls routing.
|
|
307
|
+
15. **Tier enforcement** — `execute_command()` checks `setup_tier` before running write commands. Readonly tier raises `TierError` with guidance to upgrade via `tescmd setup`.
|
|
308
|
+
16. **Key enrollment** — `tescmd key enroll <VIN>` sends the public key to the vehicle via the unsigned `add_key_request` endpoint, then guides the user through Tesla app approval with an interactive prompt or `--wait` auto-polling.
|
|
309
|
+
17. **Cross-platform token storage** — `TokenStore` uses a `_TokenBackend` protocol with two implementations: `_KeyringBackend` (OS keyring) and `_FileBackend` (JSON file with atomic writes and restricted permissions). Backend selection: explicit `TESLA_TOKEN_FILE` → file; keyring probe success → keyring; probe failure → file fallback at `{config_dir}/tokens.json` with one-time warning.
|
|
310
|
+
18. **Cross-platform file permissions** — `_internal/permissions.py` provides `secure_file()` which uses `chmod 0600` on Unix and `icacls` on Windows. Used by both token storage and key generation. Failures are silently ignored (Docker volumes, network mounts, etc.).
|
|
311
|
+
|
|
312
|
+
## Environment Variables
|
|
313
|
+
|
|
314
|
+
| Variable | Description | Default |
|
|
315
|
+
|---|---|---|
|
|
316
|
+
| `TESLA_CLIENT_ID` | OAuth2 application client ID | — |
|
|
317
|
+
| `TESLA_CLIENT_SECRET` | OAuth2 application client secret | — |
|
|
318
|
+
| `TESLA_VIN` | Default vehicle VIN | — |
|
|
319
|
+
| `TESLA_REGION` | API region (`na`, `eu`, `cn`) | `na` |
|
|
320
|
+
| `TESLA_TOKEN_FILE` | File path for token storage (skips keyring; auto-set on headless fallback) | (keyring or `{config_dir}/tokens.json`) |
|
|
321
|
+
| `TESLA_CONFIG_DIR` | Override config directory | `~/.config/tescmd` |
|
|
322
|
+
| `TESLA_OUTPUT_FORMAT` | Force output format (`rich`, `json`, `quiet`) | (auto) |
|
|
323
|
+
| `TESLA_PROFILE` | Active config profile name | `default` |
|
|
324
|
+
| `TESLA_CACHE_ENABLED` | Enable/disable response cache | `true` |
|
|
325
|
+
| `TESLA_CACHE_TTL` | Cache entry time-to-live (seconds) | `60` |
|
|
326
|
+
| `TESLA_CACHE_DIR` | Cache directory path | `~/.cache/tescmd` |
|
|
327
|
+
| `TESLA_COMMAND_PROTOCOL` | Command signing: `auto`, `signed`, `unsigned` | `auto` |
|
|
328
|
+
| `TESLA_TEMP_UNIT` | Temperature display unit: `F` or `C` | `F` |
|
|
329
|
+
| `TESLA_DISTANCE_UNIT` | Distance display unit: `mi` or `km` | `mi` |
|
|
330
|
+
| `TESLA_PRESSURE_UNIT` | Pressure display unit: `psi` or `bar` | `psi` |
|
|
331
|
+
| `TESLA_ACCESS_TOKEN` | Override access token (bypass keyring) | — |
|
|
332
|
+
| `TESLA_REFRESH_TOKEN` | Override refresh token (bypass keyring) | — |
|
|
333
|
+
| `TESLA_DOMAIN` | Domain for Fleet API key hosting | — |
|
|
334
|
+
| `TESLA_SETUP_TIER` | Setup tier (`readonly` or `full`) | — |
|
|
335
|
+
| `TESLA_GITHUB_REPO` | GitHub repo for key deployment (e.g., `user/user.github.io`) | — |
|
|
336
|
+
|
|
337
|
+
All variables can also be set in a `.env` file in the working directory or `$TESLA_CONFIG_DIR/.env`.
|
|
338
|
+
|
|
339
|
+
## CLI Flags
|
|
340
|
+
|
|
341
|
+
| Flag | Scope | Description |
|
|
342
|
+
|---|---|---|
|
|
343
|
+
| `--vin VIN` | Global | Vehicle VIN (also positional on most commands) |
|
|
344
|
+
| `--profile NAME` | Global | Config profile name |
|
|
345
|
+
| `--format {rich,json,quiet}` | Global | Force output format |
|
|
346
|
+
| `--quiet` | Global | Suppress normal output |
|
|
347
|
+
| `--region {na,eu,cn}` | Global | Tesla API region |
|
|
348
|
+
| `--verbose` | Global | Enable verbose logging |
|
|
349
|
+
| `--no-cache` / `--fresh` | Global | Bypass response cache for this invocation |
|
|
350
|
+
| `--wake` | Global | Auto-wake vehicle without confirmation (billable) |
|
|
351
|
+
| `--units {us,metric}` | Global | Display units preset (us: °F/mi/psi, metric: °C/km/bar) |
|
|
352
|
+
|
|
353
|
+
All global flags can be specified at the root level (`tescmd --wake charge status`) or after the subcommand (`tescmd charge status --wake`).
|
|
354
|
+
|
|
355
|
+
## API Coverage Validation
|
|
356
|
+
|
|
357
|
+
The project includes a spec-driven validation utility to ensure our implementation stays in sync with the Tesla Fleet API.
|
|
358
|
+
|
|
359
|
+
- **`spec/fleet_api_spec.json`** — canonical specification covering all Fleet API endpoints, params, and types (sourced from Tesla's docs + Go SDK)
|
|
360
|
+
- **`scripts/validate_fleet_api.py`** — validates the implementation against the spec using AST introspection (no imports needed)
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
# Run the validator
|
|
364
|
+
python scripts/validate_fleet_api.py # Summary output
|
|
365
|
+
python scripts/validate_fleet_api.py --verbose # Show all endpoints
|
|
366
|
+
python scripts/validate_fleet_api.py --json # Machine-readable output
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
The validator checks: vehicle commands (params + types), vehicle endpoints, energy endpoints, user endpoints, charging endpoints, partner endpoints, and protocol registry entries. Exit code 0 = all pass, 1 = errors found.
|
|
370
|
+
|
|
371
|
+
**When to run:** After adding or modifying API methods, after Tesla updates their docs, or periodically to catch drift.
|
|
372
|
+
|
|
373
|
+
## Common Commands (for reference)
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
# Dev
|
|
377
|
+
ruff check src/ tests/
|
|
378
|
+
ruff format src/ tests/
|
|
379
|
+
mypy src/
|
|
380
|
+
pytest
|
|
381
|
+
pytest tests/cli/ -k "test_auth"
|
|
382
|
+
pytest tests/cache/ # Cache-specific tests
|
|
383
|
+
|
|
384
|
+
# API coverage
|
|
385
|
+
python scripts/validate_fleet_api.py
|
|
386
|
+
|
|
387
|
+
# Build
|
|
388
|
+
python -m build
|
|
389
|
+
|
|
390
|
+
# Cache management
|
|
391
|
+
tescmd cache status # Show cache stats
|
|
392
|
+
tescmd cache clear # Clear all cached entries
|
|
393
|
+
tescmd cache clear --vin VIN # Clear cache for a specific vehicle
|
|
394
|
+
tescmd cache clear --site 12345 # Clear cache for an energy site
|
|
395
|
+
tescmd cache clear --scope account # Clear account-level entries
|
|
396
|
+
|
|
397
|
+
# Cost-optimized usage
|
|
398
|
+
tescmd charge status # Uses cache, prompts before wake
|
|
399
|
+
tescmd charge status --fresh # Bypasses cache, still prompts before wake
|
|
400
|
+
tescmd charge status --wake # Auto-wakes without prompting (billable)
|
|
401
|
+
```
|
tescmd-0.1.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sean McLellan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|