tescmd 0.2.0__py3-none-any.whl → 0.3.1__py3-none-any.whl
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/__init__.py +1 -1
- tescmd/api/client.py +41 -4
- tescmd/api/command.py +1 -1
- tescmd/api/errors.py +5 -0
- tescmd/api/signed_command.py +19 -14
- tescmd/auth/oauth.py +5 -1
- tescmd/auth/server.py +6 -1
- tescmd/auth/token_store.py +8 -1
- tescmd/cache/response_cache.py +8 -1
- tescmd/cli/_client.py +142 -20
- tescmd/cli/_options.py +2 -4
- tescmd/cli/auth.py +96 -14
- tescmd/cli/energy.py +2 -0
- tescmd/cli/main.py +27 -8
- tescmd/cli/mcp_cmd.py +153 -0
- tescmd/cli/nav.py +3 -1
- tescmd/cli/openclaw.py +169 -0
- tescmd/cli/security.py +7 -1
- tescmd/cli/serve.py +923 -0
- tescmd/cli/setup.py +18 -7
- tescmd/cli/sharing.py +2 -0
- tescmd/cli/status.py +1 -1
- tescmd/cli/trunk.py +8 -17
- tescmd/cli/user.py +16 -1
- tescmd/cli/vehicle.py +135 -462
- tescmd/deploy/github_pages.py +8 -0
- tescmd/mcp/__init__.py +7 -0
- tescmd/mcp/server.py +648 -0
- tescmd/models/auth.py +5 -2
- tescmd/openclaw/__init__.py +23 -0
- tescmd/openclaw/bridge.py +330 -0
- tescmd/openclaw/config.py +167 -0
- tescmd/openclaw/dispatcher.py +522 -0
- tescmd/openclaw/emitter.py +175 -0
- tescmd/openclaw/filters.py +123 -0
- tescmd/openclaw/gateway.py +687 -0
- tescmd/openclaw/telemetry_store.py +53 -0
- tescmd/output/rich_output.py +46 -14
- tescmd/protocol/commands.py +2 -2
- tescmd/protocol/encoder.py +16 -13
- tescmd/protocol/payloads.py +132 -11
- tescmd/protocol/session.py +8 -5
- tescmd/protocol/signer.py +3 -17
- tescmd/telemetry/__init__.py +9 -0
- tescmd/telemetry/cache_sink.py +154 -0
- tescmd/telemetry/csv_sink.py +180 -0
- tescmd/telemetry/dashboard.py +4 -4
- tescmd/telemetry/fanout.py +49 -0
- tescmd/telemetry/fields.py +308 -129
- tescmd/telemetry/mapper.py +239 -0
- tescmd/telemetry/server.py +26 -19
- tescmd/telemetry/setup.py +468 -0
- tescmd/telemetry/tui.py +1716 -0
- tescmd/triggers/__init__.py +18 -0
- tescmd/triggers/manager.py +264 -0
- tescmd/triggers/models.py +93 -0
- {tescmd-0.2.0.dist-info → tescmd-0.3.1.dist-info}/METADATA +80 -32
- {tescmd-0.2.0.dist-info → tescmd-0.3.1.dist-info}/RECORD +61 -39
- {tescmd-0.2.0.dist-info → tescmd-0.3.1.dist-info}/WHEEL +0 -0
- {tescmd-0.2.0.dist-info → tescmd-0.3.1.dist-info}/entry_points.txt +0 -0
- {tescmd-0.2.0.dist-info → tescmd-0.3.1.dist-info}/licenses/LICENSE +0 -0
tescmd/cli/setup.py
CHANGED
|
@@ -182,13 +182,22 @@ def _developer_portal_setup(
|
|
|
182
182
|
info("[bold]Phase 2: Tesla Developer Portal Setup[/bold]")
|
|
183
183
|
info("")
|
|
184
184
|
|
|
185
|
+
# Detect Tailscale hostname if domain was set to Tailscale in Phase 1
|
|
186
|
+
ts_hostname = ""
|
|
187
|
+
if settings.hosting_method == "tailscale" and settings.domain:
|
|
188
|
+
ts_hostname = settings.domain
|
|
189
|
+
|
|
185
190
|
# Delegate to the existing interactive setup wizard, passing the domain
|
|
186
191
|
# so the portal instructions show the correct Allowed Origin URL
|
|
187
|
-
|
|
192
|
+
from tescmd.models.auth import DEFAULT_PORT
|
|
193
|
+
|
|
194
|
+
port = DEFAULT_PORT
|
|
188
195
|
redirect_uri = f"http://localhost:{port}/callback"
|
|
189
196
|
from tescmd.cli.auth import _interactive_setup
|
|
190
197
|
|
|
191
|
-
return _interactive_setup(
|
|
198
|
+
return _interactive_setup(
|
|
199
|
+
formatter, port, redirect_uri, domain=domain, tailscale_hostname=ts_hostname
|
|
200
|
+
)
|
|
192
201
|
|
|
193
202
|
|
|
194
203
|
# ---------------------------------------------------------------------------
|
|
@@ -247,10 +256,10 @@ def _automated_domain_setup(formatter: OutputFormatter, settings: AppSettings) -
|
|
|
247
256
|
info(f"GitHub CLI detected. Logged in as [cyan]{username}[/cyan].")
|
|
248
257
|
info(f"Suggested domain: [cyan]{suggested_domain}[/cyan]")
|
|
249
258
|
info("")
|
|
250
|
-
info("[dim]Note: GitHub Pages provides always-on key hosting but cannot")
|
|
251
|
-
info("serve as a Fleet Telemetry server. If you plan to use telemetry")
|
|
252
|
-
info("streaming, choose Tailscale instead (install Tailscale, then")
|
|
253
|
-
info("re-run setup).[/dim]")
|
|
259
|
+
info("[dim]Note: GitHub Pages provides always-on key hosting but cannot[/dim]")
|
|
260
|
+
info("[dim]serve as a Fleet Telemetry server. If you plan to use telemetry[/dim]")
|
|
261
|
+
info("[dim]streaming, choose Tailscale instead (install Tailscale, then[/dim]")
|
|
262
|
+
info("[dim]re-run setup).[/dim]")
|
|
254
263
|
info("")
|
|
255
264
|
|
|
256
265
|
try:
|
|
@@ -924,7 +933,9 @@ async def _oauth_login_step(
|
|
|
924
933
|
info("[bold]Phase 5: OAuth Login[/bold]")
|
|
925
934
|
info("")
|
|
926
935
|
|
|
927
|
-
|
|
936
|
+
from tescmd.models.auth import DEFAULT_PORT as _DEFAULT_PORT
|
|
937
|
+
|
|
938
|
+
port = _DEFAULT_PORT
|
|
928
939
|
redirect_uri = f"http://localhost:{port}/callback"
|
|
929
940
|
|
|
930
941
|
info("Opening your browser to sign in to Tesla...")
|
tescmd/cli/sharing.py
CHANGED
|
@@ -15,6 +15,7 @@ from tescmd.cli._client import (
|
|
|
15
15
|
require_vin,
|
|
16
16
|
)
|
|
17
17
|
from tescmd.cli._options import global_options
|
|
18
|
+
from tescmd.models.sharing import ShareInvite
|
|
18
19
|
|
|
19
20
|
if TYPE_CHECKING:
|
|
20
21
|
from tescmd.cli.main import AppContext
|
|
@@ -172,6 +173,7 @@ async def _cmd_list_invites(app_ctx: AppContext, vin_positional: str | None) ->
|
|
|
172
173
|
endpoint="sharing.list-invites",
|
|
173
174
|
fetch=lambda: api.list_invites(vin),
|
|
174
175
|
ttl=TTL_SLOW,
|
|
176
|
+
model_class=ShareInvite,
|
|
175
177
|
)
|
|
176
178
|
finally:
|
|
177
179
|
await client.close()
|
tescmd/cli/status.py
CHANGED
|
@@ -36,7 +36,7 @@ def status_cmd(app_ctx: AppContext) -> None:
|
|
|
36
36
|
meta = store.metadata or {}
|
|
37
37
|
expires_at = meta.get("expires_at", 0.0)
|
|
38
38
|
expires_in = max(0, int(expires_at - time.time())) if has_token else 0
|
|
39
|
-
has_refresh = store.refresh_token
|
|
39
|
+
has_refresh = bool(store.refresh_token)
|
|
40
40
|
|
|
41
41
|
# Key info
|
|
42
42
|
key_dir = Path(settings.config_dir).expanduser() / "keys"
|
tescmd/cli/trunk.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
6
|
|
|
7
7
|
import click
|
|
8
8
|
|
|
@@ -203,22 +203,13 @@ async def _cmd_window(
|
|
|
203
203
|
try:
|
|
204
204
|
|
|
205
205
|
async def _execute_window() -> CommandResponse:
|
|
206
|
-
if vent
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
use_lat, use_lon = lat, lon
|
|
214
|
-
else:
|
|
215
|
-
vdata = await vehicle_api.get_vehicle_data(vin, endpoints=["drive_state"])
|
|
216
|
-
ds = vdata.drive_state
|
|
217
|
-
if ds and ds.latitude is not None and ds.longitude is not None:
|
|
218
|
-
use_lat, use_lon = ds.latitude, ds.longitude
|
|
219
|
-
else:
|
|
220
|
-
use_lat, use_lon = 0.0, 0.0
|
|
221
|
-
return await cmd_api.window_control(vin, command=cmd_str, lat=use_lat, lon=use_lon)
|
|
206
|
+
cmd_str = "vent" if vent else "close"
|
|
207
|
+
kwargs: dict[str, Any] = {"command": cmd_str}
|
|
208
|
+
if lat is not None:
|
|
209
|
+
kwargs["lat"] = lat
|
|
210
|
+
if lon is not None:
|
|
211
|
+
kwargs["lon"] = lon
|
|
212
|
+
return await cmd_api.window_control(vin, **kwargs)
|
|
222
213
|
|
|
223
214
|
result = await auto_wake(
|
|
224
215
|
formatter,
|
tescmd/cli/user.py
CHANGED
|
@@ -9,6 +9,7 @@ import click
|
|
|
9
9
|
from tescmd._internal.async_utils import run_async
|
|
10
10
|
from tescmd.cli._client import TTL_DEFAULT, TTL_STATIC, cached_api_call, get_user_api
|
|
11
11
|
from tescmd.cli._options import global_options
|
|
12
|
+
from tescmd.models.user import FeatureConfig, UserInfo, UserRegion, VehicleOrder
|
|
12
13
|
|
|
13
14
|
if TYPE_CHECKING:
|
|
14
15
|
from tescmd.cli.main import AppContext
|
|
@@ -34,6 +35,7 @@ async def _cmd_me(app_ctx: AppContext) -> None:
|
|
|
34
35
|
endpoint="user.me",
|
|
35
36
|
fetch=lambda: api.me(),
|
|
36
37
|
ttl=TTL_STATIC,
|
|
38
|
+
model_class=UserInfo,
|
|
37
39
|
)
|
|
38
40
|
finally:
|
|
39
41
|
await client.close()
|
|
@@ -62,6 +64,7 @@ async def _cmd_region(app_ctx: AppContext) -> None:
|
|
|
62
64
|
endpoint="user.region",
|
|
63
65
|
fetch=lambda: api.region(),
|
|
64
66
|
ttl=TTL_STATIC,
|
|
67
|
+
model_class=UserRegion,
|
|
65
68
|
)
|
|
66
69
|
finally:
|
|
67
70
|
await client.close()
|
|
@@ -90,6 +93,7 @@ async def _cmd_orders(app_ctx: AppContext) -> None:
|
|
|
90
93
|
endpoint="user.orders",
|
|
91
94
|
fetch=lambda: api.orders(),
|
|
92
95
|
ttl=TTL_DEFAULT,
|
|
96
|
+
model_class=VehicleOrder,
|
|
93
97
|
)
|
|
94
98
|
finally:
|
|
95
99
|
await client.close()
|
|
@@ -130,6 +134,7 @@ async def _cmd_features(app_ctx: AppContext) -> None:
|
|
|
130
134
|
endpoint="user.features",
|
|
131
135
|
fetch=lambda: api.feature_config(),
|
|
132
136
|
ttl=TTL_STATIC,
|
|
137
|
+
model_class=FeatureConfig,
|
|
133
138
|
)
|
|
134
139
|
finally:
|
|
135
140
|
await client.close()
|
|
@@ -139,7 +144,17 @@ async def _cmd_features(app_ctx: AppContext) -> None:
|
|
|
139
144
|
else:
|
|
140
145
|
dumped = data.model_dump(exclude_none=True) if hasattr(data, "model_dump") else data
|
|
141
146
|
if dumped:
|
|
147
|
+
from rich.table import Table
|
|
148
|
+
|
|
149
|
+
table = Table(title="Feature Flags")
|
|
150
|
+
table.add_column("Feature", style="bold")
|
|
151
|
+
table.add_column("Value")
|
|
142
152
|
for key, val in sorted(dumped.items()):
|
|
143
|
-
|
|
153
|
+
if isinstance(val, dict):
|
|
154
|
+
parts = [f"{k}={v}" for k, v in val.items()]
|
|
155
|
+
table.add_row(key, ", ".join(parts))
|
|
156
|
+
else:
|
|
157
|
+
table.add_row(key, str(val))
|
|
158
|
+
formatter.rich._con.print(table)
|
|
144
159
|
else:
|
|
145
160
|
formatter.rich.info("[dim]No feature flags available.[/dim]")
|