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
@@ -7,14 +7,12 @@ from typing import Union
7
7
  import grpc
8
8
  import requests
9
9
  import typer
10
- from rich.console import Console
11
10
  from rich.progress import Progress, SpinnerColumn, TextColumn
12
11
 
13
12
  from cli.broker.lib.broker import Broker, LicenseInfo
13
+ from cli.utils.console import print_unformatted_to_stderr
14
14
  from cli.utils.rest_helper import RestHelper as Rest
15
15
 
16
- console = Console(stderr=True)
17
-
18
16
 
19
17
  class LicenseFlow:
20
18
  def describe_with_url(self, url: str) -> LicenseInfo:
@@ -35,7 +33,7 @@ class LicenseFlow:
35
33
  return b.get_license()
36
34
 
37
35
  def request_with_url_with_internet(self, url: str) -> None:
38
- console.print("This requires internet connection from your computer during the entire licensing process")
36
+ print_unformatted_to_stderr("This requires internet connection from your computer during the entire licensing process")
39
37
 
40
38
  email = LicenseFlow.__try_authenticate_and_get_email_from_cloud()
41
39
 
@@ -52,7 +50,7 @@ class LicenseFlow:
52
50
  if not apply:
53
51
  return
54
52
 
55
- console.print(
53
+ print_unformatted_to_stderr(
56
54
  ":point_right: [bold yellow]This will request a new license or use an existing license if "
57
55
  "this hardware has already been licensed[/bold yellow]"
58
56
  )
@@ -66,7 +64,7 @@ class LicenseFlow:
66
64
  with use_progress("Applying license to broker..."):
67
65
  new_license = broker_to_license.apply_license(license_data_b64)
68
66
  if new_license.valid:
69
- console.print(f":thumbsup: Successfully applied license, it remains valid until {new_license.expires}")
67
+ print_unformatted_to_stderr(f":thumbsup: Successfully applied license, it remains valid until {new_license.expires}")
70
68
 
71
69
  def request_with_hotspot(self, url: Union[str, None] = "http://192.168.4.1:50051") -> None:
72
70
  """
@@ -76,7 +74,7 @@ class LicenseFlow:
76
74
  if url is None:
77
75
  url = "http://192.168.4.1:50051"
78
76
 
79
- console.print(
77
+ print_unformatted_to_stderr(
80
78
  "Licensing a broker over its wifi hotspot will require you to switch between internet connectivity "
81
79
  "and remotivelabs-xxx wifi hotspot"
82
80
  )
@@ -95,7 +93,7 @@ class LicenseFlow:
95
93
  if not apply:
96
94
  return
97
95
 
98
- console.print(
96
+ print_unformatted_to_stderr(
99
97
  ":point_right: [bold yellow]This will request a new license or use an existing license if "
100
98
  "this hardware has already been licensed[/bold yellow]"
101
99
  )
@@ -110,7 +108,7 @@ class LicenseFlow:
110
108
  progress_label="Applying license to broker... Make sure to switch back to remotivelabs-xxx wifi hotspot",
111
109
  )
112
110
  new_license = broker_to_license.apply_license(license_data_b64)
113
- console.print(f":thumbsup: Successfully applied license, expires {new_license.expires}")
111
+ print_unformatted_to_stderr(f":thumbsup: Successfully applied license, expires {new_license.expires}")
114
112
 
115
113
  @staticmethod
116
114
  def __try_connect_to_broker(url: str, progress_label: str, on_error_progress_label: Union[str, None] = None) -> Broker:
@@ -127,9 +125,9 @@ class LicenseFlow:
127
125
  )
128
126
  time.sleep(1)
129
127
  if on_error_progress_label is None:
130
- console.print(f":white_check_mark: {progress_label}")
128
+ print_unformatted_to_stderr(f":white_check_mark: {progress_label}")
131
129
  else:
132
- console.print(f":white_check_mark: {on_error_progress_label}")
130
+ print_unformatted_to_stderr(f":white_check_mark: {on_error_progress_label}")
133
131
  return broker_to_license
134
132
 
135
133
  @staticmethod
@@ -141,7 +139,7 @@ class LicenseFlow:
141
139
  break
142
140
  except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
143
141
  time.sleep(1)
144
- console.print(":white_check_mark: Fetching user info from cloud... Make sure you are connected to internet")
142
+ print_unformatted_to_stderr(":white_check_mark: Fetching user info from cloud... Make sure you are connected to internet")
145
143
  if r is None:
146
144
  return ""
147
145
  return str(r.json()["email"])
@@ -159,7 +157,7 @@ class LicenseFlow:
159
157
  break
160
158
  except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
161
159
  time.sleep(1)
162
- console.print(f":white_check_mark: {progress_label}")
160
+ print_unformatted_to_stderr(f":white_check_mark: {progress_label}")
163
161
  return bytes(license_data_b64)
164
162
 
165
163
 
cli/broker/playback.py CHANGED
@@ -5,8 +5,8 @@ from typing import Dict, List
5
5
  import grpc
6
6
  import typer
7
7
 
8
- from cli.errors import ErrorPrinter
9
8
  from cli.typer import typer_utils
9
+ from cli.utils.console import print_generic_error, print_generic_message, print_grpc_error, print_success
10
10
 
11
11
  from .lib.broker import Broker
12
12
 
@@ -16,7 +16,7 @@ app = typer_utils.create_typer(help=help)
16
16
  def recording_and_namespace(recording: str) -> Dict[str, str]:
17
17
  splitted = recording.split("::")
18
18
  if len(splitted) != 2:
19
- print("Invalid --recording option, expected file_name::namespace")
19
+ print_generic_error("Invalid --recording option, expected file_name::namespace")
20
20
  raise typer.Exit(1)
21
21
  return {"recording": splitted[0], "namespace": splitted[1]}
22
22
 
@@ -39,10 +39,10 @@ def play(
39
39
  try:
40
40
  broker = Broker(url, api_key)
41
41
  status = broker.play(rec)
42
- print(status)
42
+ # TODO: use log instead of print for debug information?
43
+ print_generic_message(str(status))
43
44
  except grpc.RpcError as err:
44
- ErrorPrinter.print_grpc_error(err)
45
- # print(status)
45
+ print_grpc_error(err)
46
46
 
47
47
 
48
48
  @app.command()
@@ -64,9 +64,9 @@ def stop(
64
64
  try:
65
65
  broker = Broker(url, api_key)
66
66
  broker.stop_play(rec)
67
- print("Successfully stopped recording")
67
+ print_success("Recording stopped")
68
68
  except grpc.RpcError as err:
69
- ErrorPrinter.print_grpc_error(err)
69
+ print_grpc_error(err)
70
70
 
71
71
 
72
72
  @app.command()
@@ -87,9 +87,9 @@ def pause(
87
87
  try:
88
88
  broker = Broker(url, api_key)
89
89
  broker.pause_play(rec)
90
- print("Successfully paused recording")
90
+ print_success("Recording paused")
91
91
  except grpc.RpcError as err:
92
- ErrorPrinter.print_grpc_error(err)
92
+ print_grpc_error(err)
93
93
 
94
94
 
95
95
  @app.command()
@@ -115,4 +115,4 @@ def seek(
115
115
  broker = Broker(url, api_key)
116
116
  broker.seek(rec, int(seconds * 1000000))
117
117
  except grpc.RpcError as err:
118
- ErrorPrinter.print_grpc_error(err)
118
+ print_grpc_error(err)
cli/broker/record.py CHANGED
@@ -5,8 +5,8 @@ from typing import List
5
5
  import grpc
6
6
  import typer
7
7
 
8
- from cli.errors import ErrorPrinter
9
8
  from cli.typer import typer_utils
9
+ from cli.utils.console import print_grpc_error, print_success
10
10
 
11
11
  from .lib.broker import Broker
12
12
 
@@ -24,7 +24,7 @@ def start(
24
24
  broker = Broker(url, api_key)
25
25
  broker.record_multiple(namespace, filename)
26
26
  except grpc.RpcError as rpc_error:
27
- ErrorPrinter.print_grpc_error(rpc_error)
27
+ print_grpc_error(rpc_error)
28
28
 
29
29
 
30
30
  @app.command()
@@ -37,6 +37,6 @@ def stop(
37
37
  try:
38
38
  broker = Broker(url, api_key)
39
39
  broker.stop_multiple(namespace, filename)
40
- print("Successfully stopped recording")
40
+ print_success("Recording stopped")
41
41
  except grpc.RpcError as rpc_error:
42
- ErrorPrinter.print_grpc_error(rpc_error)
42
+ print_grpc_error(rpc_error)
cli/broker/scripting.py CHANGED
@@ -5,10 +5,9 @@ from string import Template
5
5
  from typing import List
6
6
 
7
7
  import typer
8
- from rich import print as rich_print
9
8
 
10
- from cli.errors import ErrorPrinter
11
9
  from cli.typer import typer_utils
10
+ from cli.utils.console import print_generic_message, print_hint
12
11
 
13
12
  app = typer_utils.create_typer(
14
13
  rich_markup_mode="rich",
@@ -20,10 +19,9 @@ app = typer_utils.create_typer(
20
19
 
21
20
  def write(signal_name: str, script: str) -> None:
22
21
  path = f"{signal_name}.lua"
23
- f = open(path, "w")
24
- f.write(script)
25
- f.close()
26
- print(f"Secret token written to {path}")
22
+ with open(path, "w") as f:
23
+ f.write(script)
24
+ print_generic_message(f"Secret token written to {path}")
27
25
 
28
26
 
29
27
  @app.command("new-script")
@@ -35,7 +33,7 @@ def new_script(
35
33
  def to_subscribable_signal(sig: str) -> tuple[str, str]:
36
34
  arr = sig.split(":")
37
35
  if len(arr) != 2:
38
- ErrorPrinter.print_hint(f"--input-signal must have format namespace:signal ({sig})")
36
+ print_hint(f"--input-signal must have format namespace:signal ({sig})")
39
37
  sys.exit(1)
40
38
  return arr[0], arr[1]
41
39
 
@@ -121,7 +119,6 @@ end
121
119
 
122
120
  script = template.substitute(
123
121
  local_signals=local_signals,
124
- # signle_input_signal=signals_to_subscribe_to[0][1], # Take one signal and use signal name in tuple
125
122
  subscribe_pattern=subscribe_pattern,
126
123
  output_signal=output_signal,
127
124
  )
@@ -129,4 +126,4 @@ end
129
126
  if save:
130
127
  write(output_signal, script)
131
128
  else:
132
- rich_print(script)
129
+ print_generic_message(script)
cli/broker/signals.py CHANGED
@@ -12,10 +12,9 @@ from typing import Any, Dict, Iterable, List, TypedDict, Union
12
12
  import grpc
13
13
  import plotext as plt # type: ignore
14
14
  import typer
15
- from rich import print as rich_rprint
16
15
 
17
- from cli.errors import ErrorPrinter
18
16
  from cli.typer import typer_utils
17
+ from cli.utils.console import print_generic_error, print_generic_message, print_grpc_error, print_hint
19
18
 
20
19
  from .lib.broker import Broker, SubscribableSignal
21
20
 
@@ -23,8 +22,6 @@ app = typer_utils.create_typer(help=help)
23
22
 
24
23
  DEFAULT_GRPC_URL = "http://localhost:50051"
25
24
 
26
- # signal_values:list = list()
27
-
28
25
 
29
26
  class Signals(TypedDict):
30
27
  name: str
@@ -49,18 +46,18 @@ def list_signals(
49
46
  try:
50
47
  broker = Broker(url, api_key)
51
48
  available_signals = broker.list_signal_names(prefix=name_starts_with, suffix=name_ends_with)
52
- print(json.dumps(available_signals))
49
+ print_generic_message(json.dumps(available_signals))
53
50
  except grpc.RpcError as rpc_error:
54
- ErrorPrinter.print_grpc_error(rpc_error)
51
+ print_grpc_error(rpc_error)
55
52
 
56
53
 
57
54
  def read_scripted_code_file(file_path: Path) -> bytes:
58
55
  try:
59
- print(file_path)
56
+ print_generic_message(str(file_path))
60
57
  with open(file_path, "rb") as file:
61
58
  return file.read()
62
59
  except FileNotFoundError:
63
- print("File not found. Please check your file path.")
60
+ print_generic_error("File not found. Please check your file path.")
64
61
  sys.exit(1)
65
62
 
66
63
 
@@ -100,12 +97,12 @@ def subscribe( # noqa: C901, PLR0913, PLR0915
100
97
 
101
98
  if script is None:
102
99
  if len(signal) == 0:
103
- ErrorPrinter.print_generic_error("You must use --signal or use --script when subscribing")
100
+ print_generic_error("You must use --signal or use --script when subscribing")
104
101
  sys.exit(1)
105
102
 
106
103
  if script is not None:
107
104
  if len(signal) > 0:
108
- ErrorPrinter.print_generic_error("You must must not specify --signal when using --script")
105
+ print_generic_error("You must must not specify --signal when using --script")
109
106
  sys.exit(1)
110
107
 
111
108
  plt.title("Signals")
@@ -153,7 +150,8 @@ def subscribe( # noqa: C901, PLR0913, PLR0915
153
150
  plt.show()
154
151
 
155
152
  def on_frame_print(x: Iterable[Any]) -> None:
156
- rich_rprint(json.dumps(list(x)))
153
+ # TODO: use log instead of print for debug information?
154
+ print_generic_message(json.dumps(list(x)))
157
155
 
158
156
  os_signal.signal(os_signal.SIGINT, exit_on_ctrlc)
159
157
 
@@ -172,7 +170,7 @@ def subscribe( # noqa: C901, PLR0913, PLR0915
172
170
  def to_subscribable_signal(sig: str):
173
171
  arr = sig.split(":")
174
172
  if len(arr) != 2:
175
- ErrorPrinter.print_hint(f"--signal must have format namespace:signal ({sig})")
173
+ print_hint(f"--signal must have format namespace:signal ({sig})")
176
174
  sys.exit(1)
177
175
  return SubscribableSignal(namespace=arr[0], name=arr[1])
178
176
 
@@ -180,9 +178,9 @@ def subscribe( # noqa: C901, PLR0913, PLR0915
180
178
 
181
179
  broker = Broker(url, api_key)
182
180
  broker.long_name_subscribe(signals_to_subscribe_to, on_frame_func, on_change_only)
183
- print("Subscribing to signals, press Ctrl+C to exit")
181
+ print_generic_message("Subscribing to signals, press Ctrl+C to exit")
184
182
  except grpc.RpcError as rpc_error:
185
- ErrorPrinter.print_grpc_error(rpc_error)
183
+ print_grpc_error(rpc_error)
186
184
 
187
185
 
188
186
  @app.command(help="List namespaces on broker")
@@ -193,9 +191,9 @@ def namespaces(
193
191
  try:
194
192
  broker = Broker(url, api_key)
195
193
  namespaces_json = broker.list_namespaces()
196
- print(json.dumps(namespaces_json))
194
+ print_generic_message(json.dumps(namespaces_json))
197
195
  except grpc.RpcError as rpc_error:
198
- ErrorPrinter.print_grpc_error(rpc_error)
196
+ print_grpc_error(rpc_error)
199
197
 
200
198
 
201
199
  @app.command()
@@ -214,11 +212,11 @@ def frame_distribution(
214
212
  timestamp: str = datetime.now().strftime("%H:%M:%S")
215
213
  distribution = data["countsByFrameId"]
216
214
  if len(distribution) == 0:
217
- ErrorPrinter.print_hint(f"{timestamp} - No frames available")
215
+ print_hint(f"{timestamp} - No frames available")
218
216
  else:
219
217
  for d in distribution:
220
- ErrorPrinter.print_generic_message(f"{timestamp}: {d}")
218
+ print_generic_message(f"{timestamp}: {d}")
221
219
 
222
220
  broker.listen_on_frame_distribution(namespace, on_data)
223
221
  except grpc.RpcError as rpc_error:
224
- ErrorPrinter.print_grpc_error(rpc_error)
222
+ print_grpc_error(rpc_error)
cli/cloud/__init__.py CHANGED
@@ -0,0 +1,17 @@
1
+ from cli.cloud import auth, brokers, configs, organisations, projects, recordings, sample_recordings, service_accounts, storage
2
+ from cli.cloud.licenses.cmd import licenses
3
+ from cli.typer import typer_utils
4
+
5
+ app = typer_utils.create_typer()
6
+
7
+ app.command(name="licenses", help="List licenses for an organization")(licenses)
8
+
9
+ app.add_typer(organisations.app, name="organizations", help="Manage organizations")
10
+ app.add_typer(projects.app, name="projects", help="Manage projects")
11
+ app.add_typer(auth.app, name="auth")
12
+ app.add_typer(brokers.app, name="brokers", help="Manage cloud broker lifecycle")
13
+ app.add_typer(recordings.app, name="recordings", help="Manage recordings")
14
+ app.add_typer(configs.app, name="signal-databases", help="Manage signal databases")
15
+ app.add_typer(storage.app, name="storage")
16
+ app.add_typer(service_accounts.app, name="service-accounts", help="Manage project service account keys")
17
+ app.add_typer(sample_recordings.app, name="samples", help="Manage sample recordings")
cli/cloud/auth/cmd.py CHANGED
@@ -1,22 +1,26 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import os
3
4
  import sys
4
5
 
5
6
  import typer
7
+ from rich.console import Console
8
+ from rich.table import Table
6
9
 
7
10
  from cli.cloud.auth.login import login as do_login
8
- from cli.errors import ErrorPrinter
9
- from cli.settings import TokenNotFoundError, settings
11
+ from cli.settings import settings
10
12
  from cli.typer import typer_utils
13
+ from cli.utils.console import print_generic_error, print_generic_message, print_success, print_unformatted
11
14
  from cli.utils.rest_helper import RestHelper as Rest
12
15
 
13
16
  from .. import auth_tokens
14
17
 
18
+ console = Console(stderr=False)
19
+
15
20
  HELP = """
16
21
  Manage how you authenticate with our cloud platform
17
22
  """
18
23
  app = typer_utils.create_typer(help=HELP)
19
- # app.add_typer(auth_tokens.app, name="credentials", help="Manage account credentials")
20
24
 
21
25
 
22
26
  @app.command(name="login")
@@ -36,11 +40,7 @@ def whoami() -> None:
36
40
  """
37
41
  Validates authentication and fetches your account information
38
42
  """
39
- try:
40
- Rest.handle_get("/api/whoami")
41
- except TokenNotFoundError as e:
42
- ErrorPrinter.print_hint(str(e))
43
- sys.exit(1)
43
+ Rest.handle_get("/api/whoami")
44
44
 
45
45
 
46
46
  @app.command()
@@ -50,39 +50,80 @@ def print_access_token(
50
50
  """
51
51
  Print current active access token or the token for the specified account
52
52
  """
53
- if account is None:
54
- try:
55
- print(settings.get_active_token())
56
- except TokenNotFoundError:
57
- ErrorPrinter.print_generic_error("You have no active account", exit_code=1)
58
- else:
59
- config = settings.get_cli_config()
60
- if account in config.accounts:
61
- token_file_name = config.accounts[account].credentials_file
62
- try:
63
- print(settings.get_token_file(token_file_name).token)
64
- except TokenNotFoundError:
65
- ErrorPrinter.print_generic_error(f"Token file for {account} could not be found", exit_code=1)
66
- else:
67
- ErrorPrinter.print_generic_error(f"No account for {account} was found", exit_code=1)
53
+ if not account:
54
+ active_token = settings.get_active_token() or os.getenv("REMOTIVE_CLOUD_ACCESS_TOKEN", None)
55
+ if not active_token:
56
+ print_generic_error("You have no active account")
57
+ sys.exit(1)
58
+
59
+ print_generic_message(active_token)
60
+ return
61
+
62
+ accounts = settings.list_accounts()
63
+ if account not in accounts:
64
+ print_generic_error(f"No account for {account} was found")
65
+ sys.exit(1)
66
+
67
+ token_file_name = accounts[account].credentials_file
68
+ token_file = settings.get_token_file(token_file_name)
69
+ if not token_file:
70
+ print_generic_error(f"Token file for {account} could not be found")
71
+ sys.exit(1)
72
+
73
+ print_generic_message(token_file.token)
68
74
 
69
75
 
70
76
  def print_access_token_file() -> None:
71
77
  """
72
78
  Print current active token and its metadata
73
79
  """
74
- try:
75
- print(settings.get_active_token_file())
76
- except TokenNotFoundError as e:
77
- ErrorPrinter.print_hint(str(e))
80
+ active_token_file = settings.get_active_token_file()
81
+ if not active_token_file:
82
+ print_generic_error("You have no active account")
78
83
  sys.exit(1)
79
84
 
85
+ print_generic_message(str(active_token_file))
86
+
87
+
88
+ @app.command(name="deactivate")
89
+ def deactivate() -> None:
90
+ """
91
+ Clears active account
92
+ """
93
+ settings.clear_active_account()
94
+ print_success("Account no longer active")
95
+
96
+
97
+ @app.command("activate")
98
+ def activate(token_name: str = typer.Argument(None, help="Name, filename or path to a credentials file")) -> None:
99
+ """
100
+ Set the active account
101
+ """
102
+ auth_tokens.do_activate(token_name)
80
103
 
81
- # @app.command(help="Clears active credentials")
82
- def logout() -> None:
83
- settings.clear_active_token()
84
- print("Access token removed")
85
104
 
105
+ @app.command(name="list")
106
+ def list() -> None:
107
+ """
108
+ Lists available credential files on filesystem
86
109
 
87
- app.command("activate")(auth_tokens.select_personal_token)
88
- app.command("list")(auth_tokens.list_pats_files)
110
+ TODO: Support output format
111
+ """
112
+ accounts = settings.list_accounts()
113
+
114
+ table = Table("#", "Active", "Type", "Token", "Account", "Organization", "Created", "Expires")
115
+ for idx, (email, account) in enumerate(accounts.items(), start=1):
116
+ token_file = settings.get_token_file_by_email(email)
117
+ is_active = settings.is_active_account(email)
118
+
119
+ table.add_row(
120
+ f"[yellow]{idx}",
121
+ ":white_check_mark:" if is_active else "",
122
+ "unknown" if not token_file else "user" if token_file.type == "authorized_user" else "sa",
123
+ token_file.name if token_file else "",
124
+ f"[bold]{email}[/bold]",
125
+ account.default_organization if account.default_organization else "",
126
+ str(token_file.created) if token_file else "",
127
+ str(token_file.expires) if token_file else "",
128
+ )
129
+ print_unformatted(table)