tescmd 0.1.2__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.
Files changed (81) hide show
  1. tescmd/__init__.py +3 -0
  2. tescmd/__main__.py +5 -0
  3. tescmd/_internal/__init__.py +0 -0
  4. tescmd/_internal/async_utils.py +25 -0
  5. tescmd/_internal/permissions.py +43 -0
  6. tescmd/_internal/vin.py +44 -0
  7. tescmd/api/__init__.py +1 -0
  8. tescmd/api/charging.py +102 -0
  9. tescmd/api/client.py +189 -0
  10. tescmd/api/command.py +540 -0
  11. tescmd/api/energy.py +146 -0
  12. tescmd/api/errors.py +76 -0
  13. tescmd/api/partner.py +40 -0
  14. tescmd/api/sharing.py +65 -0
  15. tescmd/api/signed_command.py +277 -0
  16. tescmd/api/user.py +38 -0
  17. tescmd/api/vehicle.py +150 -0
  18. tescmd/auth/__init__.py +1 -0
  19. tescmd/auth/oauth.py +312 -0
  20. tescmd/auth/server.py +108 -0
  21. tescmd/auth/token_store.py +273 -0
  22. tescmd/ble/__init__.py +0 -0
  23. tescmd/cache/__init__.py +6 -0
  24. tescmd/cache/keys.py +51 -0
  25. tescmd/cache/response_cache.py +213 -0
  26. tescmd/cli/__init__.py +0 -0
  27. tescmd/cli/_client.py +603 -0
  28. tescmd/cli/_options.py +126 -0
  29. tescmd/cli/auth.py +682 -0
  30. tescmd/cli/billing.py +240 -0
  31. tescmd/cli/cache.py +85 -0
  32. tescmd/cli/charge.py +610 -0
  33. tescmd/cli/climate.py +501 -0
  34. tescmd/cli/energy.py +385 -0
  35. tescmd/cli/key.py +611 -0
  36. tescmd/cli/main.py +601 -0
  37. tescmd/cli/media.py +146 -0
  38. tescmd/cli/nav.py +242 -0
  39. tescmd/cli/partner.py +112 -0
  40. tescmd/cli/raw.py +75 -0
  41. tescmd/cli/security.py +495 -0
  42. tescmd/cli/setup.py +786 -0
  43. tescmd/cli/sharing.py +188 -0
  44. tescmd/cli/software.py +81 -0
  45. tescmd/cli/status.py +106 -0
  46. tescmd/cli/trunk.py +240 -0
  47. tescmd/cli/user.py +145 -0
  48. tescmd/cli/vehicle.py +837 -0
  49. tescmd/config/__init__.py +0 -0
  50. tescmd/crypto/__init__.py +19 -0
  51. tescmd/crypto/ecdh.py +46 -0
  52. tescmd/crypto/keys.py +122 -0
  53. tescmd/deploy/__init__.py +0 -0
  54. tescmd/deploy/github_pages.py +268 -0
  55. tescmd/models/__init__.py +85 -0
  56. tescmd/models/auth.py +108 -0
  57. tescmd/models/command.py +18 -0
  58. tescmd/models/config.py +63 -0
  59. tescmd/models/energy.py +56 -0
  60. tescmd/models/sharing.py +26 -0
  61. tescmd/models/user.py +37 -0
  62. tescmd/models/vehicle.py +185 -0
  63. tescmd/output/__init__.py +5 -0
  64. tescmd/output/formatter.py +132 -0
  65. tescmd/output/json_output.py +83 -0
  66. tescmd/output/rich_output.py +809 -0
  67. tescmd/protocol/__init__.py +23 -0
  68. tescmd/protocol/commands.py +175 -0
  69. tescmd/protocol/encoder.py +122 -0
  70. tescmd/protocol/metadata.py +116 -0
  71. tescmd/protocol/payloads.py +621 -0
  72. tescmd/protocol/protobuf/__init__.py +6 -0
  73. tescmd/protocol/protobuf/messages.py +564 -0
  74. tescmd/protocol/session.py +318 -0
  75. tescmd/protocol/signer.py +84 -0
  76. tescmd/py.typed +0 -0
  77. tescmd-0.1.2.dist-info/METADATA +458 -0
  78. tescmd-0.1.2.dist-info/RECORD +81 -0
  79. tescmd-0.1.2.dist-info/WHEEL +4 -0
  80. tescmd-0.1.2.dist-info/entry_points.txt +2 -0
  81. tescmd-0.1.2.dist-info/licenses/LICENSE +21 -0
tescmd/cli/user.py ADDED
@@ -0,0 +1,145 @@
1
+ """CLI commands for user account operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ import click
8
+
9
+ from tescmd._internal.async_utils import run_async
10
+ from tescmd.cli._client import TTL_DEFAULT, TTL_STATIC, cached_api_call, get_user_api
11
+ from tescmd.cli._options import global_options
12
+
13
+ if TYPE_CHECKING:
14
+ from tescmd.cli.main import AppContext
15
+
16
+ user_group = click.Group("user", help="User account commands")
17
+
18
+
19
+ @user_group.command("me")
20
+ @global_options
21
+ def me_cmd(app_ctx: AppContext) -> None:
22
+ """Show account information."""
23
+ run_async(_cmd_me(app_ctx))
24
+
25
+
26
+ async def _cmd_me(app_ctx: AppContext) -> None:
27
+ formatter = app_ctx.formatter
28
+ client, api = get_user_api(app_ctx)
29
+ try:
30
+ data = await cached_api_call(
31
+ app_ctx,
32
+ scope="account",
33
+ identifier="global",
34
+ endpoint="user.me",
35
+ fetch=lambda: api.me(),
36
+ ttl=TTL_STATIC,
37
+ )
38
+ finally:
39
+ await client.close()
40
+
41
+ if formatter.format == "json":
42
+ formatter.output(data, command="user.me")
43
+ else:
44
+ formatter.rich.user_info(data)
45
+
46
+
47
+ @user_group.command("region")
48
+ @global_options
49
+ def region_cmd(app_ctx: AppContext) -> None:
50
+ """Show regional Fleet API endpoint."""
51
+ run_async(_cmd_region(app_ctx))
52
+
53
+
54
+ async def _cmd_region(app_ctx: AppContext) -> None:
55
+ formatter = app_ctx.formatter
56
+ client, api = get_user_api(app_ctx)
57
+ try:
58
+ data = await cached_api_call(
59
+ app_ctx,
60
+ scope="account",
61
+ identifier="global",
62
+ endpoint="user.region",
63
+ fetch=lambda: api.region(),
64
+ ttl=TTL_STATIC,
65
+ )
66
+ finally:
67
+ await client.close()
68
+
69
+ if formatter.format == "json":
70
+ formatter.output(data, command="user.region")
71
+ else:
72
+ formatter.rich.user_region(data)
73
+
74
+
75
+ @user_group.command("orders")
76
+ @global_options
77
+ def orders_cmd(app_ctx: AppContext) -> None:
78
+ """Show vehicle orders."""
79
+ run_async(_cmd_orders(app_ctx))
80
+
81
+
82
+ async def _cmd_orders(app_ctx: AppContext) -> None:
83
+ formatter = app_ctx.formatter
84
+ client, api = get_user_api(app_ctx)
85
+ try:
86
+ data = await cached_api_call(
87
+ app_ctx,
88
+ scope="account",
89
+ identifier="global",
90
+ endpoint="user.orders",
91
+ fetch=lambda: api.orders(),
92
+ ttl=TTL_DEFAULT,
93
+ )
94
+ finally:
95
+ await client.close()
96
+
97
+ if formatter.format == "json":
98
+ formatter.output(data, command="user.orders")
99
+ else:
100
+ if data:
101
+ for order in data:
102
+ if isinstance(order, dict):
103
+ oid = order.get("order_id") or "?"
104
+ model = order.get("model") or ""
105
+ status = order.get("status") or ""
106
+ else:
107
+ oid = order.order_id or "?"
108
+ model = order.model or ""
109
+ status = order.status or ""
110
+ formatter.rich.info(f" {oid}: {model} [{status}]")
111
+ else:
112
+ formatter.rich.info("[dim]No orders found.[/dim]")
113
+
114
+
115
+ @user_group.command("features")
116
+ @global_options
117
+ def features_cmd(app_ctx: AppContext) -> None:
118
+ """Show feature flags."""
119
+ run_async(_cmd_features(app_ctx))
120
+
121
+
122
+ async def _cmd_features(app_ctx: AppContext) -> None:
123
+ formatter = app_ctx.formatter
124
+ client, api = get_user_api(app_ctx)
125
+ try:
126
+ data = await cached_api_call(
127
+ app_ctx,
128
+ scope="account",
129
+ identifier="global",
130
+ endpoint="user.features",
131
+ fetch=lambda: api.feature_config(),
132
+ ttl=TTL_STATIC,
133
+ )
134
+ finally:
135
+ await client.close()
136
+
137
+ if formatter.format == "json":
138
+ formatter.output(data, command="user.features")
139
+ else:
140
+ dumped = data.model_dump(exclude_none=True) if hasattr(data, "model_dump") else data
141
+ if dumped:
142
+ for key, val in sorted(dumped.items()):
143
+ formatter.rich.info(f" {key}: {val}")
144
+ else:
145
+ formatter.rich.info("[dim]No feature flags available.[/dim]")