remotivelabs-cli 0.2.3__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.

Potentially problematic release.


This version of remotivelabs-cli might be problematic. Click here for more details.

Files changed (55) hide show
  1. cli/broker/__init__.py +36 -0
  2. cli/broker/discovery.py +43 -0
  3. cli/broker/export.py +6 -36
  4. cli/broker/files.py +12 -12
  5. cli/broker/lib/broker.py +132 -106
  6. cli/broker/lib/client.py +224 -0
  7. cli/broker/lib/helper.py +277 -0
  8. cli/broker/lib/signalcreator.py +196 -0
  9. cli/broker/license_flows.py +11 -13
  10. cli/broker/playback.py +10 -10
  11. cli/broker/record.py +4 -4
  12. cli/broker/scripting.py +6 -9
  13. cli/broker/signals.py +17 -19
  14. cli/cloud/__init__.py +17 -0
  15. cli/cloud/auth/cmd.py +74 -33
  16. cli/cloud/auth/login.py +42 -54
  17. cli/cloud/auth_tokens.py +40 -247
  18. cli/cloud/brokers.py +5 -9
  19. cli/cloud/configs.py +4 -17
  20. cli/cloud/licenses/__init__.py +0 -0
  21. cli/cloud/licenses/cmd.py +14 -0
  22. cli/cloud/organisations.py +12 -17
  23. cli/cloud/projects.py +3 -3
  24. cli/cloud/recordings.py +35 -61
  25. cli/cloud/recordings_playback.py +22 -22
  26. cli/cloud/resumable_upload.py +6 -6
  27. cli/cloud/service_account_tokens.py +4 -3
  28. cli/cloud/storage/cmd.py +2 -3
  29. cli/cloud/storage/copy.py +2 -1
  30. cli/connect/connect.py +4 -4
  31. cli/connect/protopie/protopie.py +22 -30
  32. cli/remotive.py +16 -26
  33. cli/settings/__init__.py +1 -2
  34. cli/settings/config_file.py +2 -0
  35. cli/settings/core.py +146 -146
  36. cli/settings/migration/migrate_config_file.py +13 -6
  37. cli/settings/migration/migration_tools.py +6 -4
  38. cli/settings/state_file.py +12 -4
  39. cli/tools/can/can.py +4 -7
  40. cli/topology/__init__.py +3 -0
  41. cli/topology/cmd.py +60 -83
  42. cli/topology/start_trial.py +105 -0
  43. cli/typer/typer_utils.py +3 -6
  44. cli/utils/console.py +61 -0
  45. cli/utils/rest_helper.py +33 -31
  46. cli/utils/versions.py +7 -19
  47. {remotivelabs_cli-0.2.3.dist-info → remotivelabs_cli-0.3.1.dist-info}/METADATA +3 -2
  48. remotivelabs_cli-0.3.1.dist-info/RECORD +74 -0
  49. cli/broker/brokers.py +0 -93
  50. cli/cloud/cloud_cli.py +0 -29
  51. cli/errors.py +0 -44
  52. remotivelabs_cli-0.2.3.dist-info/RECORD +0 -67
  53. {remotivelabs_cli-0.2.3.dist-info → remotivelabs_cli-0.3.1.dist-info}/LICENSE +0 -0
  54. {remotivelabs_cli-0.2.3.dist-info → remotivelabs_cli-0.3.1.dist-info}/WHEEL +0 -0
  55. {remotivelabs_cli-0.2.3.dist-info → remotivelabs_cli-0.3.1.dist-info}/entry_points.txt +0 -0
cli/topology/cmd.py CHANGED
@@ -1,102 +1,79 @@
1
1
  from __future__ import annotations
2
2
 
3
- import dataclasses
4
- import datetime
5
- from typing import Any
6
-
7
3
  import typer
8
- from rich.console import Console
9
4
 
10
- from cli.errors import ErrorPrinter
11
- from cli.settings import TokenNotFoundError, settings
5
+ from cli.topology.start_trial import (
6
+ MissingOrganizationError,
7
+ NoActiveAccountError,
8
+ NotAuthorizedError,
9
+ NotAuthorizedToStartTrialError,
10
+ NotSignedInError,
11
+ SubscriptionExpiredError,
12
+ start_trial,
13
+ )
12
14
  from cli.typer import typer_utils
13
- from cli.utils.rest_helper import RestHelper
15
+ from cli.utils.console import print_generic_error, print_generic_message, print_hint
14
16
 
15
17
  HELP = """
16
- RemotiveTopology commands
18
+ Manage RemotiveTopology resources
17
19
  """
18
- console = Console()
19
- app = typer_utils.create_typer(help=HELP)
20
-
21
-
22
- @dataclasses.dataclass
23
- class Subscription:
24
- type: str
25
- display_name: str
26
- feature: str
27
- start_date: str # TODO: add datetime
28
- end_date: str # TODO: add datetime
29
-
30
20
 
31
- def _print_current_subscription(subscription_info: dict[str, Any]) -> None:
32
- subscription_type = subscription_info.get("subscriptionType")
33
- end_date_str = subscription_info.get("endDate")
34
- now = datetime.datetime.now()
35
-
36
- def parse_date(date_str: str | None) -> datetime.datetime | None:
37
- return datetime.datetime.fromisoformat(date_str) if date_str else None
38
-
39
- expires = parse_date(end_date_str)
40
-
41
- if subscription_type == "trial":
42
- if expires and expires < now:
43
- console.print(f"Your Topology trial expired {end_date_str}, please contact support@remotivelabs.com")
44
- else:
45
- console.print(f"You already have an active topology trial, it expires {end_date_str}")
46
-
47
- elif subscription_type == "paid":
48
- if expires and expires < now:
49
- console.print(f"Topology subscription has ended, expired {end_date_str}")
50
- else:
51
- console.print(f"You already have an active topology subscription, it expires {end_date_str or 'Never'}")
52
-
53
- else:
54
- ErrorPrinter.print_generic_error("Unexpected exception, please contact support@remotivelabs.com")
55
- raise typer.Exit(1)
21
+ app = typer_utils.create_typer(rich_markup_mode="rich", help=HELP)
56
22
 
57
23
 
58
24
  @app.command("start-trial")
59
- def start_trial(
25
+ def start_trial_cmd( # noqa: C901
60
26
  organization: str = typer.Option(None, help="Organization to start trial for", envvar="REMOTIVE_CLOUD_ORGANIZATION"),
61
27
  ) -> None:
62
28
  """
63
- Allows you ta start a 30 day trial subscription for running RemotiveTopology, you can read more at https://docs.remotivelabs.com/docs/remotive-topology.
29
+ Allows you ta start a 30 day trial subscription for running RemotiveTopology.
64
30
 
31
+ You can read more at https://docs.remotivelabs.com/docs/remotive-topology.
65
32
  """
66
- RestHelper.use_progress("Checking access tokens...", transient=True)
67
33
  try:
68
- _ = settings.get_active_token_file()
69
- except TokenNotFoundError:
70
- if len(settings.list_personal_token_files()) == 0:
71
- console.print(
72
- "You must first sign in to RemotiveCloud, please use [bold]remotive cloud auth login[/bold] to sign-in"
73
- "This requires a RemotiveCloud account, if you do not have an account you can sign-up at https://cloud.remotivelabs.com"
74
- )
75
- else:
76
- console.print(
77
- "You have not active account, please run [bold]remotive cloud auth activate[/bold] to choose an account"
78
- "or [bold]remotive cloud auth login[/bold] to sign-in"
79
- )
80
- return
81
-
82
- has_access = RestHelper.has_access("/api/whoami")
83
- if not has_access:
84
- ErrorPrinter.print_generic_message("Your current active credentials are not valid")
85
- raise typer.Exit(1)
86
-
87
- active_account = settings.get_cli_config().get_active_account()
88
- if active_account and not organization and not active_account.default_organization:
89
- ErrorPrinter.print_hint("You have not specified any organization and no default organization is set")
90
- raise typer.Exit(1)
91
-
92
- sub = RestHelper.handle_get(f"/api/bu/{organization}/features/topology", return_response=True, allow_status_codes=[404, 403])
93
- if sub.status_code == 404:
94
- created = RestHelper.handle_post(f"/api/bu/{organization}/features/topology", return_response=True)
95
- console.print(f"Topology trial started, it expires {created.json()['endDate']}")
96
- elif sub.status_code == 403:
97
- ErrorPrinter.print_generic_error(f"You are not allowed to start-trial topology in organization {organization}")
34
+ subscription = start_trial(organization)
35
+ if subscription.type == "trial":
36
+ print_generic_message(f"topology trial active, expires {subscription.end_date}")
37
+ elif subscription.type == "paid":
38
+ print_generic_message(f"you already have a topology subscription, expires {subscription.end_date or 'Never'}")
39
+
40
+ except NotSignedInError:
41
+ print_generic_error(
42
+ "You must first sign in to RemotiveCloud, please use [bold]remotive cloud auth login[/bold] to sign-in"
43
+ "This requires a RemotiveCloud account, if you do not have an account you can sign-up at https://cloud.remotivelabs.com"
44
+ )
45
+ raise typer.Exit(2)
46
+
47
+ except NoActiveAccountError:
48
+ print_hint(
49
+ "You have not actived your account, please run [bold]remotive cloud auth activate[/bold] to choose an account"
50
+ "or [bold]remotive cloud auth login[/bold] to sign-in"
51
+ )
52
+ raise typer.Exit(3)
53
+
54
+ except NotAuthorizedError:
55
+ print_hint(
56
+ "Your current active credentials are not valid, please run [bold]remotive cloud auth login[/bold] to sign-in again."
57
+ "This requires a RemotiveCloud account, if you do not have an account you can sign-up at https://cloud.remotivelabs.com"
58
+ )
59
+ raise typer.Exit(4)
60
+
61
+ except MissingOrganizationError:
62
+ print_hint("You have not specified any organization and no default organization is set")
63
+ raise typer.Exit(5)
64
+
65
+ except NotAuthorizedToStartTrialError as e:
66
+ print_generic_error(f"You are not allowed to start-trial topology in organization {e.organization}")
67
+ raise typer.Exit(6)
68
+
69
+ except SubscriptionExpiredError as e:
70
+ if e.subscription.type == "trial":
71
+ print_generic_error(f"Your Topology trial expired {e.subscription.end_date}, please contact support@remotivelabs.com")
72
+ raise typer.Exit(7)
73
+
74
+ print_generic_error(f"Your Topology subscription has expired {e.subscription.end_date}, please contact support@remotivelabs.com")
75
+ raise typer.Exit(7)
76
+
77
+ except Exception as e:
78
+ print_generic_error(f"Unexpected error: {e}")
98
79
  raise typer.Exit(1)
99
- else:
100
- subscription_info = sub.json()
101
- _print_current_subscription(subscription_info)
102
- return
@@ -0,0 +1,105 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ from dataclasses import dataclass
5
+ from datetime import date
6
+ from typing import Any
7
+
8
+ from cli.settings import settings
9
+ from cli.settings.config_file import Account
10
+ from cli.utils.rest_helper import RestHelper
11
+ from cli.utils.time import parse_date
12
+
13
+
14
+ class NoActiveAccountError(Exception):
15
+ """Raised when the user has no active account, but there are available accounts to activate"""
16
+
17
+
18
+ class NotSignedInError(Exception):
19
+ """Raised when the user has no active account, and no available accounts to activate"""
20
+
21
+
22
+ class NotAuthorizedError(Exception):
23
+ """Raised when the user is not authorized"""
24
+
25
+ def __init__(self, account: Account):
26
+ self.account = account
27
+
28
+
29
+ class MissingOrganizationError(Exception):
30
+ """Raised when the user has not specified an organization and no default organization is set"""
31
+
32
+
33
+ class NotAuthorizedToStartTrialError(Exception):
34
+ """Raised when the user is not authorized to start a topology trial"""
35
+
36
+ def __init__(self, account: Account, organization: str):
37
+ self.account = account
38
+ self.organization = organization
39
+
40
+
41
+ class SubscriptionExpiredError(Exception):
42
+ """Raised when the subscription has expired"""
43
+
44
+ def __init__(self, subscription: Subscription):
45
+ self.subscription = subscription
46
+
47
+
48
+ @dataclass
49
+ class Subscription:
50
+ type: str
51
+ display_name: str
52
+ feature: str
53
+ start_date: date
54
+ end_date: date
55
+
56
+ @staticmethod
57
+ def from_dict(data: Any) -> Subscription:
58
+ if not isinstance(data, dict):
59
+ raise ValueError(f"Invalid subscription data {data}")
60
+
61
+ return Subscription(
62
+ type=data["subscriptionType"],
63
+ display_name=data["displayName"],
64
+ feature=data["feature"],
65
+ start_date=parse_date(data["startDate"]),
66
+ end_date=parse_date(data["endDate"]),
67
+ )
68
+
69
+
70
+ def start_trial(organization: str | None = None) -> Subscription:
71
+ """
72
+ Start a 30 day trial subscription for running RemotiveTopology.
73
+
74
+ # TODO: move authentication (and basic authorization) to a logged_in decorator
75
+ """
76
+ active_account = settings.get_active_account()
77
+ active_token_file = settings.get_active_token_file()
78
+
79
+ if not active_account or not active_token_file:
80
+ if len(settings.list_accounts()) == 0:
81
+ raise NotSignedInError()
82
+ raise NoActiveAccountError()
83
+
84
+ if not RestHelper.has_access("/api/whoami"):
85
+ raise NotAuthorizedError(account=active_account)
86
+
87
+ valid_org = organization or active_account.default_organization
88
+ if not valid_org:
89
+ raise MissingOrganizationError()
90
+
91
+ res = RestHelper.handle_get(f"/api/bu/{valid_org}/features/topology", return_response=True, allow_status_codes=[403, 404])
92
+ if res.status_code == 403:
93
+ raise NotAuthorizedToStartTrialError(account=active_account, organization=valid_org)
94
+ if res.status_code == 404:
95
+ created = RestHelper.handle_post(f"/api/bu/{valid_org}/features/topology", return_response=True)
96
+ subscription = Subscription.from_dict(created.json())
97
+ else:
98
+ # 200 OK means we already have a valid subscription
99
+ subscription = Subscription.from_dict(res.json())
100
+
101
+ # check subscription validity
102
+ if subscription.end_date < datetime.datetime.now().date():
103
+ raise SubscriptionExpiredError(subscription=subscription)
104
+
105
+ return subscription
cli/typer/typer_utils.py CHANGED
@@ -2,24 +2,21 @@ from typing import Any
2
2
 
3
3
  import typer
4
4
  from click import Context
5
- from rich.console import Console
6
5
  from typer.core import TyperGroup
7
6
 
7
+ from cli.utils.console import print_generic_message
8
+
8
9
 
9
10
  class OrderCommands(TyperGroup):
10
11
  def list_commands(self, _ctx: Context): # type: ignore
11
12
  return list(self.commands)
12
13
 
13
14
 
14
- console = Console()
15
-
16
-
17
15
  def create_typer(**kwargs: Any) -> typer.Typer:
18
16
  """Create a Typer instance with default settings."""
19
- # return typer.Typer(no_args_is_help=True, **kwargs)
20
17
  return typer.Typer(cls=OrderCommands, no_args_is_help=True, invoke_without_command=True, **kwargs)
21
18
 
22
19
 
23
20
  def print_padded(label: str, right_text: str, length: int = 30) -> None:
24
21
  padded_label = label.ljust(length) # pad to 30 characters
25
- console.print(f"{padded_label} {right_text}")
22
+ print_generic_message(f"{padded_label} {right_text}")
cli/utils/console.py ADDED
@@ -0,0 +1,61 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import sys
5
+ from typing import Any
6
+
7
+ import grpc
8
+ from rich.console import Console
9
+
10
+ console = Console()
11
+ err_console = Console(stderr=True)
12
+
13
+
14
+ def print_grpc_error(error: grpc.RpcError) -> None:
15
+ if error.code() == grpc.StatusCode.UNAUTHENTICATED:
16
+ is_access_token = os.environ["ACCESS_TOKEN"]
17
+ if is_access_token is not None and is_access_token == "true":
18
+ err_console.print(f":boom: [bold red]Authentication failed[/bold red]: {error.details()}")
19
+ err_console.print("Please login again")
20
+ else:
21
+ err_console.print(":boom: [bold red]Authentication failed[/bold red]")
22
+ err_console.print("Failed to verify api-key")
23
+ else:
24
+ err_console.print(f":boom: [bold red]Unexpected error, status code[/bold red]: {error.code()}")
25
+ err_console.print(error.details())
26
+ sys.exit(1)
27
+
28
+
29
+ def print_hint(message: str) -> None:
30
+ err_console.print(f":point_right: [bold]{message}[/bold]")
31
+
32
+
33
+ def print_generic_error(message: str) -> None:
34
+ err_console.print(f":boom: [bold red]Failed[/bold red]: {message}")
35
+
36
+
37
+ def print_success(message: str) -> None:
38
+ console.print(f"[bold green]Success![/bold green] {message}")
39
+
40
+
41
+ def print_generic_message(message: str) -> None:
42
+ console.print(f"[bold]{message}[/bold]")
43
+
44
+
45
+ def print_newline() -> None:
46
+ """TODO: is this needed?"""
47
+ console.print("\n")
48
+
49
+
50
+ def print_url(url: str) -> None:
51
+ console.print(url, style="bold", soft_wrap=True)
52
+
53
+
54
+ def print_unformatted(message: Any) -> None:
55
+ """TODO: should we allow this?"""
56
+ console.print(message)
57
+
58
+
59
+ def print_unformatted_to_stderr(message: Any) -> None:
60
+ """TODO: should we allow this?"""
61
+ err_console.print(message)
cli/utils/rest_helper.py CHANGED
@@ -12,14 +12,11 @@ from typing import Any, BinaryIO, Dict, List, Optional, Union, cast
12
12
 
13
13
  import requests
14
14
  from requests.exceptions import JSONDecodeError
15
- from rich.console import Console
16
15
  from rich.progress import Progress, SpinnerColumn, TextColumn, wrap_file
17
16
 
18
- from cli.errors import ErrorPrinter
19
- from cli.settings import TokenNotFoundError, settings
17
+ from cli.settings import settings
20
18
  from cli.utils import versions
21
-
22
- err_console = Console(stderr=True)
19
+ from cli.utils.console import print_generic_error, print_generic_message, print_hint, print_unformatted, print_unformatted_to_stderr
23
20
 
24
21
  if "REMOTIVE_CLOUD_HTTP_LOGGING" in os.environ:
25
22
  logging.basicConfig()
@@ -84,29 +81,27 @@ class RestHelper:
84
81
 
85
82
  @staticmethod
86
83
  def ensure_auth_token(quiet: bool = False, access_token: Optional[str] = None) -> None:
87
- # TODO: remove this? We already set the default organization as env in remotive.py
84
+ """
85
+ TODO: remove setting org, as we already set the default organization as env in remotive.py?
86
+ TODO: don't sys.exit, raise error instead
87
+ """
88
88
  if "REMOTIVE_CLOUD_ORGANIZATION" not in os.environ:
89
- active_account = settings.get_cli_config().get_active_account()
89
+ active_account = settings.get_active_account()
90
90
  if active_account:
91
91
  org = active_account.default_organization
92
92
  if org:
93
93
  os.environ["REMOTIVE_CLOUD_ORGANIZATION"] = org
94
94
 
95
- token = None
96
-
97
- if access_token is None:
98
- if "REMOTIVE_CLOUD_ACCESS_TOKEN" in os.environ:
99
- token = os.environ["REMOTIVE_CLOUD_ACCESS_TOKEN"]
100
- else:
101
- try:
102
- token = settings.get_active_token()
103
- except TokenNotFoundError:
104
- if quiet:
105
- return
106
- ErrorPrinter.print_hint("you are not logged in, please login using [green]remotive cloud auth login[/green]")
107
- sys.exit(1)
95
+ token = access_token
96
+ if not token:
97
+ token = os.environ.get("REMOTIVE_CLOUD_ACCESS_TOKEN", settings.get_active_token())
98
+ if not token:
99
+ if quiet:
100
+ return
101
+ print_hint("you are not logged in, please login using [green]remotive cloud auth login[/green]")
102
+ sys.exit(1)
108
103
 
109
- RestHelper.__headers["Authorization"] = f"Bearer {token.strip() if token is not None else access_token}"
104
+ RestHelper.__headers["Authorization"] = f"Bearer {token.strip()}"
110
105
 
111
106
  @staticmethod
112
107
  def handle_get( # noqa: PLR0913
@@ -160,38 +155,45 @@ class RestHelper:
160
155
 
161
156
  @staticmethod
162
157
  def check_api_result(response: requests.Response, allow_status_codes: List[int] | None = None) -> None:
158
+ """
159
+ TODO: don't sys.exit, raise error instead
160
+ """
163
161
  if response.status_code == 426: # CLI upgrade
164
- ErrorPrinter.print_hint(response.text)
162
+ print_hint(response.text)
165
163
  sys.exit(1)
166
164
  if response.status_code > 299:
167
165
  if allow_status_codes is not None and response.status_code in allow_status_codes:
168
166
  return
169
- err_console.print(f":boom: [bold red]Got status code[/bold red]: {response.status_code}")
167
+ print_generic_error(f"Got status code: {response.status_code}")
170
168
  if response.status_code == 401:
171
- err_console.print("Your token is not valid or has expired, please login again or activate another account")
169
+ print_generic_message("Your token is not valid or has expired, please login again or activate another account")
172
170
  else:
173
- err_console.print(response.text)
171
+ print_unformatted_to_stderr(response.text)
174
172
  sys.exit(1)
175
173
 
176
174
  @staticmethod
177
175
  def print_api_result(response: requests.Response) -> None:
176
+ """
177
+ TODO: don't sys.exit, raise error instead
178
+ TODO: dont print from here, return and let caller print instead
179
+ """
178
180
  if response.status_code == 426: # CLI upgrade
179
- ErrorPrinter.print_hint(response.text)
181
+ print_hint(response.text)
180
182
  sys.exit(1)
181
183
 
182
184
  if response.status_code >= 200 and response.status_code < 300:
183
185
  if len(response.content) >= 2:
184
186
  try:
185
- print(json.dumps(response.json()))
187
+ print_unformatted(json.dumps(response.json()))
186
188
  except JSONDecodeError:
187
- err_console.print(":boom: [bold red]Json parse error[/bold red]: Please try again and report if the error persists")
189
+ print_generic_error("Json parse error: Please try again and report if the error persists")
188
190
  sys.exit(0)
189
191
  else:
190
- err_console.print(f":boom: [bold red]Got status code[/bold red]: {response.status_code}")
192
+ print_generic_error(f"Got status code: {response.status_code}")
191
193
  if response.status_code == 401:
192
- err_console.print("Your token is not valid or has expired, please login again or activate another account")
194
+ print_generic_message("Your token is not valid or has expired, please login again or activate another account")
193
195
  else:
194
- err_console.print(response.text)
196
+ print_unformatted_to_stderr(response.text)
195
197
  sys.exit(1)
196
198
 
197
199
  @staticmethod
cli/utils/versions.py CHANGED
@@ -5,14 +5,13 @@ import json
5
5
  import os
6
6
  import platform
7
7
  import urllib.request
8
- from datetime import timedelta
9
8
  from importlib import metadata as importlib_metadata
10
9
  from importlib.metadata import version as python_project_version
11
10
 
12
11
  from packaging.version import InvalidVersion, Version
13
12
 
14
- from cli.errors import ErrorPrinter
15
13
  from cli.settings import Settings
14
+ from cli.utils.console import print_hint
16
15
 
17
16
 
18
17
  def cli_version() -> str:
@@ -85,26 +84,17 @@ def check_for_update(settings: Settings) -> None:
85
84
  # Make it possible to disable update check, i.e in CI
86
85
  if os.environ.get("PYTHON_DISABLE_UPDATE_CHECK"):
87
86
  return
88
- project = "remotivelabs-cli"
87
+
88
+ # Check if we are allowed to perform an update check
89
+ if not settings.should_perform_update_check():
90
+ return
89
91
 
90
92
  # Determine current version
93
+ project = "remotivelabs-cli"
91
94
  cur = cli_version() or _installed_version(project)
92
95
  if not cur:
93
96
  return # unknown version → skip silently
94
97
 
95
- state = settings.get_state_file()
96
- if not state.last_update_check_time:
97
- if os.environ.get("RUNS_IN_DOCKER"):
98
- # To prevent that we always check update in docker due to ephemeral disks we write an "old" check if the state
99
- # is missing. If no disk is mounted we will never get the update check but if its mounted properly we will get
100
- # it on the second attempt. This is good enough
101
- last_update_check_time = (datetime.datetime.now() - timedelta(hours=10)).isoformat()
102
- settings.set_last_update_check_time(last_update_check_time)
103
- return
104
-
105
- elif not state.should_perform_update_check():
106
- return
107
-
108
98
  # We end up here if last_update_check_time is None or should_perform_update_check is true
109
99
  include_prereleases = Version(cur).is_prerelease or Version(cur).is_devrelease
110
100
 
@@ -127,6 +117,4 @@ def _print_update_info(cur: str, latest: str) -> None:
127
117
  else "upgrade with: pipx install -U remotivelabs-cli"
128
118
  )
129
119
 
130
- ErrorPrinter.print_hint(
131
- f"Update available: remotivelabs-cli {cur} → {latest} , ({instructions}) we always recommend to use latest version"
132
- )
120
+ print_hint(f"Update available: remotivelabs-cli {cur} → {latest} , ({instructions}) we always recommend to use latest version")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: remotivelabs-cli
3
- Version: 0.2.3
3
+ Version: 0.3.1
4
4
  Summary: CLI for operating RemotiveCloud and RemotiveBroker
5
5
  Author: Johan Rask
6
6
  Author-email: johan.rask@remotivelabs.com
@@ -20,7 +20,8 @@ Requires-Dist: pydantic (>=2.11.7,<3.0.0)
20
20
  Requires-Dist: pyjwt (>=2.6,<3.0)
21
21
  Requires-Dist: python-can (>=4.3.1)
22
22
  Requires-Dist: python-socketio (>=4.6.1)
23
- Requires-Dist: remotivelabs-broker (>=0.1.17,<0.2.0)
23
+ Requires-Dist: remotivelabs-broker (>=0.9.1,<0.10.0)
24
+ Requires-Dist: requests (>=2.32.4,<3.0.0)
24
25
  Requires-Dist: rich (>=13.7.0,<13.8.0)
25
26
  Requires-Dist: trogon (>=0.5.0)
26
27
  Requires-Dist: typer (==0.12.5)
@@ -0,0 +1,74 @@
1
+ cli/.DS_Store,sha256=7HTaExsH9zU3sluA0MFtZuyzNmSnUmH2Sh09uoek84E,8196
2
+ cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ cli/api/cloud/tokens.py,sha256=3UKfVM3NvZX_ynpPAXZ_cnrPAIIgg0vA1FuOzj8TV2o,1631
4
+ cli/broker/__init__.py,sha256=8Xxn02p9VTKEZ7JYrDhioLT5eREMrFnDwapja45prL8,1348
5
+ cli/broker/discovery.py,sha256=oQcNhdc17SUZre9WYgPKG41-YDP8FrDhRrF5tk6eaRM,1522
6
+ cli/broker/export.py,sha256=29n_Nuvvv1FM7uUkOPs-DaNWULI4IaC5yufNxLy1FPQ,3519
7
+ cli/broker/files.py,sha256=o-mi7GtGn8vfO6r1hSRY97rDhMQAWfse2lh8Y78llN8,4229
8
+ cli/broker/lib/__about__.py,sha256=xnZ5V6ZcHW9dhWLWdMzVjYJbEnMKpeXm0_S_mbNzypE,141
9
+ cli/broker/lib/broker.py,sha256=ncVRHDS5C_D2hW_LQXxl58EbzT49v85NWE7iNGIg9Fo,26293
10
+ cli/broker/lib/client.py,sha256=2Nwrd_PY1gqQNqkWc9QTJVFDMAZebAzVnCFUTqiArwE,8540
11
+ cli/broker/lib/helper.py,sha256=nNQy0nXD5lDg2mzslRbBPmhzAgjMDJuRBYHj5pG2ZZw,9283
12
+ cli/broker/lib/signalcreator.py,sha256=CDInuSAmNrOlEya_br8CGZR1VNAXY7CXkF9s3mQUpHk,8201
13
+ cli/broker/license_flows.py,sha256=L2PeitmcJPqbHEzb9YBJpsrNDXn92hu2JKdcYXYVJKk,7349
14
+ cli/broker/licenses.py,sha256=jIuLB2qBGflzSjm952CBnErpzs7iIkmEgx9L8GDAPNc,4021
15
+ cli/broker/playback.py,sha256=1N8jTTgd2rw3VRbpyzP3I-06ritceeYxc_uEKst-8j8,4110
16
+ cli/broker/record.py,sha256=FjpeCbd1pUcDcpgF7QGLuBpecv4x4BFOGwELO8De3go,1440
17
+ cli/broker/scripting.py,sha256=lD8GuoVa8Ku4E2BUAq-USfMqG4utZMRgr61jePogykM,3747
18
+ cli/broker/signals.py,sha256=1xldsY3SVcVp-EEsrd6_uoBjrTRMSEONHpOA6SmWxJ4,8245
19
+ cli/cloud/__init__.py,sha256=xFaDg2zho_glvDBDB0_8HRNICVwHeYFEz-yMhzTcCjI,987
20
+ cli/cloud/auth/__init__.py,sha256=MtQ01-n8CgZb9Y_SvxwZUgj44Yo0dFAU3_XwhQiUYtw,54
21
+ cli/cloud/auth/cmd.py,sha256=lv9D0Q7G1RgI2IO1vkz_VVBA_STCPmdJax3QkeiMwg4,3968
22
+ cli/cloud/auth/login.py,sha256=3kc9L6-HOBwzf4Fxis2SBm_q5ObTuzSn4Ic_GkLF0Jk,11121
23
+ cli/cloud/auth_tokens.py,sha256=ExIsCmzoM_MCaYQ5aL4j0Cdyq81IFJ4HJAt-zn1H2L4,5061
24
+ cli/cloud/brokers.py,sha256=cXYKm_-CYJZH0muXAPwUMKIiqBiW6Y9WG5kmkQb3FuU,3849
25
+ cli/cloud/configs.py,sha256=atInBsh16woPmjC0dpnRTpbY4WRgseBOWe8VwpqwO_o,3745
26
+ cli/cloud/licenses/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
+ cli/cloud/licenses/cmd.py,sha256=zq-Cc5OdftDyUR4dDqGnCmeNF62XR_kYC3mYXEKFZCw,477
28
+ cli/cloud/organisations.py,sha256=zFjqOnbO0_RGatzRXeHfvxi_T65-9wM0jTrtnSyn3_c,4033
29
+ cli/cloud/projects.py,sha256=90EAB8aXGah90brCfJpV6TzIeNy0x9y0F-eETUBtQhs,1597
30
+ cli/cloud/recordings.py,sha256=af3F2z1oLXbalB1UyAgq3B5ZAb0RwRlv69fpw6_GnrY,23728
31
+ cli/cloud/recordings_playback.py,sha256=UpLwbjl0ZGXBGLtSlfQcOKqG5uIdJzUxR688ZpQTEIA,11449
32
+ cli/cloud/resumable_upload.py,sha256=stdjvM3ARXUN5CEuVkRW-xoHmeYXIu0mw5VXUb_50FM,3721
33
+ cli/cloud/sample_recordings.py,sha256=RmuT-a2iMwGj3LXVcPkV5l66uFcf7nyWyJciUjnYkk4,721
34
+ cli/cloud/service_account_tokens.py,sha256=o2VJn9YxUjtSDvRUB8ezT8gKKWqKJNFs0lUD9-e0MPc,2870
35
+ cli/cloud/service_accounts.py,sha256=AiktZW5QTbT6sAPJi4ubETOqbBDAIt4LOE-TZmIiIkk,2586
36
+ cli/cloud/storage/__init__.py,sha256=ijl9WwU5D4oASbwrFKJurYsBUyzwZCOhcdTQYj-ZSeM,159
37
+ cli/cloud/storage/cmd.py,sha256=pF5NP_TIssHK-AQX3df-aYjXWrhjtoaT4GWWQKp2TAI,2961
38
+ cli/cloud/storage/copy.py,sha256=Na0FrBrCJ2xfuFfelmz_p-_yM-ZMyORRF5FHbd7I_W8,3289
39
+ cli/cloud/storage/uri_or_path.py,sha256=DLlyr0RAV-DRlL2C36U-jvUqwiLIlkw7c3mJ7SSGMdI,1158
40
+ cli/cloud/uri.py,sha256=QZCus--KJQlVwGCOzZqiglvj8VvSRKxfVvN33Pilgyg,3616
41
+ cli/connect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
+ cli/connect/connect.py,sha256=4bsDIcToIyqjuD2HCMWK6sMnpykdHOz5axL-aGFZyXM,4197
43
+ cli/connect/protopie/protopie.py,sha256=0S-GCNiWpTvXzJEsFSht9HoziSzu_6hXZMvTp4xAP40,6470
44
+ cli/remotive.py,sha256=EwgjtTjR5EmGo39PFpbF74v2nm3K2E9A1kon89R2KiE,3854
45
+ cli/settings/__init__.py,sha256=JsMr0E_hsM6IRHYeJUrlLBGyKnPdR4cDJd08-TjX274,665
46
+ cli/settings/config_file.py,sha256=QwsrVGB7JTqFNXlLkbWVcRSveW0HsKzU6Jl8mHqjdO8,3586
47
+ cli/settings/core.py,sha256=_UNs3B0t1AhuwciMNWsHFWZMV9kTEkoSlmow8QlkeXw,10999
48
+ cli/settings/migration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
+ cli/settings/migration/migrate_all_token_files.py,sha256=xoVvAqn_tGskEW148uf3xZx1mpJKUnERMTcBo0nkCnI,3010
50
+ cli/settings/migration/migrate_config_file.py,sha256=S8kyn3ZXbkej2TRLPcVDcYpvk2iW6kGo38OIutEZayo,2217
51
+ cli/settings/migration/migrate_legacy_dirs.py,sha256=N0t2io3bT_ub8BcVPw1CeQ4eeexRUiu3jXq3DL018OE,1819
52
+ cli/settings/migration/migrate_token_file.py,sha256=Fp7Z_lNqSdoWY05TYwFW2QH8q9QhmB2TYSok6hV1Mic,1530
53
+ cli/settings/migration/migration_tools.py,sha256=D00cQ3RsXkCPgGBGOvpx1_Ul8GfUEqfRbvbv70WN81s,1386
54
+ cli/settings/state_file.py,sha256=rN6JNZP9ULZsuossk3M7jPhoIUgogAAcQGs4SJ-rI-4,2162
55
+ cli/settings/token_file.py,sha256=9GZh44eaXtn30EA06mzQ-69yBT06ig03doqfUHisAw0,3921
56
+ cli/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
+ cli/tools/can/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
58
+ cli/tools/can/can.py,sha256=ovTqzsjJP-z9H5RiJ3f_OXMpELqM3d09JY4JfPpT3-Q,2171
59
+ cli/tools/tools.py,sha256=jhLfrFDqkmWV3eBAzNwBf6WgDGrz7sOhgVCia36Twn8,232
60
+ cli/topology/__init__.py,sha256=MyorYLmC2wAd_z5GdQENEYLgoCiNYgwQ16BOX93IBtI,52
61
+ cli/topology/cmd.py,sha256=vtAw-vIJxf2dogVaqqUm8jRzUJrC7fVuov3sSV7l0sc,3101
62
+ cli/topology/start_trial.py,sha256=GNB2E-Ia18UpXQZ4fViGq5-pRq69vinDBLwzImsz2GY,3414
63
+ cli/typer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
64
+ cli/typer/typer_utils.py,sha256=eUrVOEOKY_MnfiJMAxvEurP1Q79z9pq83mJjocP8lhI,686
65
+ cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
66
+ cli/utils/console.py,sha256=-IczptBi42GdgNRQAYbgFvSV-lIgVF749H9xNgSMgOo,1740
67
+ cli/utils/rest_helper.py,sha256=ZHc7TMNVa07kJWDQ_OEoeaGhzYu8BinSXJJX8R_wX_8,14135
68
+ cli/utils/time.py,sha256=TEKcNZ-pQoJ7cZ6hQmVD0sTRwRm2rBy51-MuDNdO4S4,296
69
+ cli/utils/versions.py,sha256=mHs_BCk0-wwPfCHIIQBlpFkgipSWkw9jUaZ-USH8ODY,3824
70
+ remotivelabs_cli-0.3.1.dist-info/LICENSE,sha256=qDPP_yfuv1fF-u7EfexN-cN3M8aFgGVndGhGLovLKz0,608
71
+ remotivelabs_cli-0.3.1.dist-info/METADATA,sha256=8RommjeI-h_CqdlUXy3oSdYCPZFUCRhP2xciLRgddFk,1521
72
+ remotivelabs_cli-0.3.1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
73
+ remotivelabs_cli-0.3.1.dist-info/entry_points.txt,sha256=lvDhPgagLqW_KTnLPCwKSqfYlEp-1uYVosRiPjsVj10,45
74
+ remotivelabs_cli-0.3.1.dist-info/RECORD,,