uipath 2.1.41__py3-none-any.whl → 2.1.43__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.
@@ -0,0 +1,172 @@
1
+ import copy
2
+ from collections import defaultdict
3
+ from time import time
4
+ from typing import Dict, Generic, List, Optional, Sequence, TypeVar
5
+
6
+ from opentelemetry.sdk.trace import ReadableSpan
7
+ from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
8
+
9
+ from uipath.eval._helpers import auto_discover_entrypoint
10
+
11
+ from .._runtime._contracts import (
12
+ UiPathBaseRuntime,
13
+ UiPathRuntimeContext,
14
+ UiPathRuntimeFactory,
15
+ UiPathRuntimeResult,
16
+ UiPathRuntimeStatus,
17
+ )
18
+ from .._utils._eval_set import EvalHelpers
19
+ from ._models import EvaluationItem
20
+ from ._models._agent_execution_output import UiPathEvalRunExecutionOutput
21
+
22
+ T = TypeVar("T", bound=UiPathBaseRuntime)
23
+ C = TypeVar("C", bound=UiPathRuntimeContext)
24
+
25
+
26
+ class ExecutionSpanExporter(SpanExporter):
27
+ """Custom exporter that stores spans grouped by execution ids."""
28
+
29
+ def __init__(self):
30
+ # { execution_id -> list of spans }
31
+ self._spans: Dict[str, List[ReadableSpan]] = defaultdict(list)
32
+
33
+ def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
34
+ for span in spans:
35
+ if span.attributes is not None:
36
+ exec_id = span.attributes.get("execution.id")
37
+ if exec_id is not None and isinstance(exec_id, str):
38
+ self._spans[exec_id].append(span)
39
+
40
+ return SpanExportResult.SUCCESS
41
+
42
+ def get_spans(self, execution_id: str) -> List[ReadableSpan]:
43
+ """Retrieve spans for a given execution id."""
44
+ return self._spans.get(execution_id, [])
45
+
46
+ def clear(self, execution_id: Optional[str] = None) -> None:
47
+ """Clear stored spans for one or all executions."""
48
+ if execution_id:
49
+ self._spans.pop(execution_id, None)
50
+ else:
51
+ self._spans.clear()
52
+
53
+ def shutdown(self) -> None:
54
+ self.clear()
55
+
56
+
57
+ class UiPathEvalContext(UiPathRuntimeContext, Generic[C]):
58
+ """Context used for evaluation runs."""
59
+
60
+ runtime_context: C
61
+ no_report: bool
62
+ workers: int
63
+ eval_set: Optional[str] = None
64
+ eval_ids: Optional[List[str]] = None
65
+
66
+ def __init__(
67
+ self,
68
+ runtime_context: C,
69
+ no_report: bool,
70
+ workers: int,
71
+ eval_set: Optional[str] = None,
72
+ eval_ids: Optional[List[str]] = None,
73
+ **kwargs,
74
+ ):
75
+ super().__init__(
76
+ runtime_context=runtime_context, # type: ignore
77
+ no_report=no_report,
78
+ workers=workers,
79
+ eval_set=eval_set,
80
+ eval_ids=eval_ids,
81
+ **kwargs,
82
+ )
83
+ self._auto_discover()
84
+
85
+ def _auto_discover(self):
86
+ self.runtime_context.entrypoint = (
87
+ self.runtime_context.entrypoint or auto_discover_entrypoint()
88
+ )
89
+ self.eval_set = self.eval_set or EvalHelpers.auto_discover_eval_set()
90
+
91
+
92
+ class UiPathEvalRuntime(UiPathBaseRuntime, Generic[T, C]):
93
+ """Specialized runtime for evaluation runs, with access to the factory."""
94
+
95
+ def __init__(
96
+ self, context: "UiPathEvalContext[C]", factory: "UiPathRuntimeFactory[T, C]"
97
+ ):
98
+ super().__init__(context)
99
+ self.context: "UiPathEvalContext[C]" = context
100
+ self.factory: UiPathRuntimeFactory[T, C] = factory
101
+ self.span_exporter: ExecutionSpanExporter = ExecutionSpanExporter()
102
+ self.factory.add_span_exporter(self.span_exporter)
103
+
104
+ @classmethod
105
+ def from__eval_context(
106
+ cls,
107
+ context: "UiPathEvalContext[C]",
108
+ factory: "UiPathRuntimeFactory[T, C]",
109
+ ) -> "UiPathEvalRuntime[T, C]":
110
+ return cls(context, factory)
111
+
112
+ async def execute(self) -> Optional[UiPathRuntimeResult]:
113
+ """Evaluation logic. Can spawn other runtimes through the factory."""
114
+ if self.context.eval_set is None:
115
+ raise ValueError("eval_set must be provided for evaluation runs")
116
+
117
+ evaluation_set = EvalHelpers.load_eval_set(
118
+ self.context.eval_set, self.context.eval_ids
119
+ )
120
+ execution_output_list: list[UiPathEvalRunExecutionOutput] = []
121
+ for eval_item in evaluation_set.evaluations:
122
+ execution_output_list.append(await self.execute_agent(eval_item))
123
+
124
+ self.context.result = UiPathRuntimeResult(
125
+ output={
126
+ "results": execution_output_list,
127
+ },
128
+ status=UiPathRuntimeStatus.SUCCESSFUL,
129
+ resume=None,
130
+ )
131
+
132
+ return self.context.runtime_context.result
133
+
134
+ def _prepare_new_runtime_context(self, eval_item: EvaluationItem) -> C:
135
+ runtime_context = copy.deepcopy(self.context.runtime_context)
136
+ runtime_context.execution_id = eval_item.id
137
+ runtime_context.input_json = eval_item.inputs
138
+ # here we can pass other values from eval_item: expectedAgentBehavior, simulationInstructions etc.
139
+ return runtime_context
140
+
141
+ # TODO: this would most likely need to be ported to a public AgentEvaluator class
142
+ async def execute_agent(
143
+ self, eval_item: EvaluationItem
144
+ ) -> "UiPathEvalRunExecutionOutput":
145
+ runtime_context = self._prepare_new_runtime_context(eval_item)
146
+ start_time = time()
147
+ result = await self.factory.execute_in_root_span(
148
+ runtime_context, root_span=eval_item.name
149
+ )
150
+ end_time = time()
151
+ if runtime_context.execution_id is None:
152
+ raise ValueError("execution_id must be set for eval runs")
153
+
154
+ spans = self.span_exporter.get_spans(runtime_context.execution_id)
155
+ self.span_exporter.clear(runtime_context.execution_id)
156
+
157
+ if result is None:
158
+ raise ValueError("Execution result cannot be None for eval runs")
159
+
160
+ return UiPathEvalRunExecutionOutput(
161
+ execution_time=end_time - start_time,
162
+ spans=spans,
163
+ result=result,
164
+ )
165
+
166
+ async def cleanup(self) -> None:
167
+ """Cleanup runtime resources."""
168
+ pass
169
+
170
+ async def validate(self) -> None:
171
+ """Cleanup runtime resources."""
172
+ pass
@@ -9,6 +9,7 @@ from abc import ABC, abstractmethod
9
9
  from enum import Enum
10
10
  from functools import cached_property
11
11
  from typing import Any, Callable, Dict, Generic, List, Optional, Type, TypeVar, Union
12
+ from uuid import uuid4
12
13
 
13
14
  from opentelemetry import context as context_api
14
15
  from opentelemetry import trace
@@ -23,7 +24,7 @@ from opentelemetry.trace import Tracer
23
24
  from pydantic import BaseModel, Field
24
25
 
25
26
  from uipath.agent.conversation import UiPathConversationEvent, UiPathConversationMessage
26
- from uipath.tracing import TracingManager
27
+ from uipath.tracing import LlmOpsHttpExporter, TracingManager
27
28
 
28
29
  from ._logging import LogsInterceptor
29
30
 
@@ -161,6 +162,128 @@ class UiPathTraceContext(BaseModel):
161
162
  reference_id: Optional[str] = None
162
163
 
163
164
 
165
+ class UiPathRuntimeContextBuilder:
166
+ """Builder class for UiPathRuntimeContext following the builder pattern."""
167
+
168
+ def __init__(self):
169
+ self._kwargs = {}
170
+
171
+ def with_defaults(
172
+ self, config_path: Optional[str] = None, **kwargs
173
+ ) -> "UiPathRuntimeContextBuilder":
174
+ """Apply default configuration similar to UiPathRuntimeContext.with_defaults().
175
+
176
+ Args:
177
+ config_path: Path to the configuration file (defaults to UIPATH_CONFIG_PATH env var or "uipath.json")
178
+ **kwargs: Additional keyword arguments to pass to with_defaults
179
+
180
+ Returns:
181
+ Self for method chaining
182
+ """
183
+ from os import environ as env
184
+
185
+ resolved_config_path = config_path or env.get(
186
+ "UIPATH_CONFIG_PATH", "uipath.json"
187
+ )
188
+ self._kwargs["config_path"] = resolved_config_path
189
+
190
+ self._kwargs.update(
191
+ {
192
+ "job_id": env.get("UIPATH_JOB_KEY"),
193
+ "trace_id": env.get("UIPATH_TRACE_ID"),
194
+ "tracing_enabled": env.get("UIPATH_TRACING_ENABLED", True),
195
+ "logs_min_level": env.get("LOG_LEVEL", "INFO"),
196
+ **kwargs, # Allow overriding defaults with provided kwargs
197
+ }
198
+ )
199
+
200
+ self._kwargs["trace_context"] = UiPathTraceContext(
201
+ trace_id=env.get("UIPATH_TRACE_ID"),
202
+ parent_span_id=env.get("UIPATH_PARENT_SPAN_ID"),
203
+ root_span_id=env.get("UIPATH_ROOT_SPAN_ID"),
204
+ enabled=env.get("UIPATH_TRACING_ENABLED", True),
205
+ job_id=env.get("UIPATH_JOB_KEY"),
206
+ org_id=env.get("UIPATH_ORGANIZATION_ID"),
207
+ tenant_id=env.get("UIPATH_TENANT_ID"),
208
+ process_key=env.get("UIPATH_PROCESS_UUID"),
209
+ folder_key=env.get("UIPATH_FOLDER_KEY"),
210
+ reference_id=env.get("UIPATH_JOB_KEY") or str(uuid4()),
211
+ )
212
+
213
+ return self
214
+
215
+ def with_entrypoint(self, entrypoint: str) -> "UiPathRuntimeContextBuilder":
216
+ """Set the entrypoint for the runtime context.
217
+
218
+ Args:
219
+ entrypoint: The entrypoint to execute
220
+
221
+ Returns:
222
+ Self for method chaining
223
+ """
224
+ self._kwargs["entrypoint"] = entrypoint
225
+ return self
226
+
227
+ def with_input(
228
+ self, input_data: Optional[str] = None, input_file: Optional[str] = None
229
+ ) -> "UiPathRuntimeContextBuilder":
230
+ """Set the input data for the runtime context.
231
+
232
+ Args:
233
+ input_data: The input data as a string
234
+ input_file: Path to the input file
235
+
236
+ Returns:
237
+ Self for method chaining
238
+ """
239
+ if input_data is not None:
240
+ self._kwargs["input"] = input_data
241
+ if input_file is not None:
242
+ self._kwargs["input_file"] = input_file
243
+ return self
244
+
245
+ def with_resume(self, enable: bool = True) -> "UiPathRuntimeContextBuilder":
246
+ """Enable or disable resume mode for the runtime context.
247
+
248
+ Args:
249
+ enable: Whether to enable resume mode (defaults to True)
250
+
251
+ Returns:
252
+ Self for method chaining
253
+ """
254
+ self._kwargs["resume"] = enable
255
+ return self
256
+
257
+ def mark_eval_run(self, enable: bool = True) -> "UiPathRuntimeContextBuilder":
258
+ """Mark this as an evaluation run.
259
+
260
+ Args:
261
+ enable: Whether this is an eval run (defaults to True)
262
+
263
+ Returns:
264
+ Self for method chaining
265
+ """
266
+ self._kwargs["is_eval_run"] = enable
267
+ return self
268
+
269
+ def build(self) -> "UiPathRuntimeContext":
270
+ """Build and return the UiPathRuntimeContext instance.
271
+
272
+ Returns:
273
+ A configured UiPathRuntimeContext instance
274
+ """
275
+ config_path = self._kwargs.pop("config_path", None)
276
+ if config_path:
277
+ # Create context from config first, then update with any additional kwargs
278
+ context = UiPathRuntimeContext.from_config(config_path)
279
+ for key, value in self._kwargs.items():
280
+ if hasattr(context, key):
281
+ setattr(context, key, value)
282
+ return context
283
+ else:
284
+ return UiPathRuntimeContext(**self._kwargs)
285
+
286
+
164
287
  class UiPathRuntimeContext(BaseModel):
165
288
  """Context information passed throughout the runtime execution."""
166
289
 
@@ -422,8 +545,8 @@ class UiPathBaseRuntime(ABC):
422
545
  content = execution_result.to_dict()
423
546
  logger.debug(content)
424
547
 
425
- # Always write output file at runtime
426
- if self.context.job_id:
548
+ # Always write output file at runtime, except evaluation runs
549
+ if self.context.job_id and not self.context.is_eval_run:
427
550
  with open(self.output_file_path, "w") as f:
428
551
  json.dump(content, f, indent=2, default=str)
429
552
 
@@ -521,6 +644,9 @@ class UiPathRuntimeFactory(Generic[T, C]):
521
644
  self.tracer_span_processors: List[SpanProcessor] = []
522
645
  trace.set_tracer_provider(self.tracer_provider)
523
646
 
647
+ if os.getenv("UIPATH_JOB_KEY"):
648
+ self.add_span_exporter(LlmOpsHttpExporter())
649
+
524
650
  def add_span_exporter(
525
651
  self,
526
652
  span_exporter: SpanExporter,
@@ -591,9 +717,12 @@ class UiPathExecutionTraceProcessorMixin:
591
717
  parent_span = trace.get_current_span()
592
718
 
593
719
  if parent_span and parent_span.is_recording():
594
- run_id = parent_span.attributes.get("execution.id") # type: ignore[attr-defined]
595
- if run_id:
596
- span.set_attribute("execution.id", run_id)
720
+ execution_id = parent_span.attributes.get("execution.id") # type: ignore[attr-defined]
721
+ if execution_id:
722
+ span.set_attribute("execution.id", execution_id)
723
+ evaluation_id = parent_span.attributes.get("evaluation.id") # type: ignore[attr-defined]
724
+ if evaluation_id:
725
+ span.set_attribute("evaluation.id", evaluation_id)
597
726
 
598
727
 
599
728
  class UiPathExecutionBatchTraceProcessor(
@@ -0,0 +1,84 @@
1
+ import json
2
+ from pathlib import Path
3
+ from typing import List, Optional
4
+
5
+ import click
6
+
7
+ from uipath._cli._evals._models import EvaluationSet
8
+ from uipath._cli._utils._console import ConsoleLogger
9
+
10
+ console = ConsoleLogger()
11
+
12
+
13
+ class EvalHelpers:
14
+ @staticmethod
15
+ def auto_discover_eval_set() -> str:
16
+ """Auto-discover evaluation set from evals/eval-sets directory.
17
+
18
+ Returns:
19
+ Path to the evaluation set file
20
+
21
+ Raises:
22
+ ValueError: If no eval set found or multiple eval sets exist
23
+ """
24
+ eval_sets_dir = Path("evals/eval-sets")
25
+
26
+ if not eval_sets_dir.exists():
27
+ raise ValueError(
28
+ "No 'evals/eval-sets' directory found. "
29
+ "Please set 'UIPATH_PROJECT_ID' env var and run 'uipath pull'."
30
+ )
31
+
32
+ eval_set_files = list(eval_sets_dir.glob("*.json"))
33
+
34
+ if not eval_set_files:
35
+ raise ValueError(
36
+ "No evaluation set files found in 'evals/eval-sets' directory. "
37
+ )
38
+
39
+ if len(eval_set_files) > 1:
40
+ file_names = [f.name for f in eval_set_files]
41
+ raise ValueError(
42
+ f"Multiple evaluation sets found: {file_names}. "
43
+ f"Please specify which evaluation set to use: 'uipath eval [entrypoint] <eval_set_path>'"
44
+ )
45
+
46
+ eval_set_path = str(eval_set_files[0])
47
+ console.info(
48
+ f"Auto-discovered evaluation set: {click.style(eval_set_path, fg='cyan')}"
49
+ )
50
+
51
+ eval_set_path_obj = Path(eval_set_path)
52
+ if not eval_set_path_obj.is_file() or eval_set_path_obj.suffix != ".json":
53
+ raise ValueError("Evaluation set must be a JSON file")
54
+
55
+ return eval_set_path
56
+
57
+ @staticmethod
58
+ def load_eval_set(
59
+ eval_set_path: str, eval_ids: Optional[List[str]] = None
60
+ ) -> EvaluationSet:
61
+ """Load the evaluation set from file.
62
+
63
+ Returns:
64
+ The loaded evaluation set as EvaluationSet model
65
+ """
66
+ try:
67
+ with open(eval_set_path, "r", encoding="utf-8") as f:
68
+ data = json.load(f)
69
+ except json.JSONDecodeError as e:
70
+ raise ValueError(
71
+ f"Invalid JSON in evaluation set file '{eval_set_path}': {str(e)}. "
72
+ f"Please check the file for syntax errors."
73
+ ) from e
74
+
75
+ try:
76
+ eval_set = EvaluationSet(**data)
77
+ except (TypeError, ValueError) as e:
78
+ raise ValueError(
79
+ f"Invalid evaluation set format in '{eval_set_path}': {str(e)}. "
80
+ f"Please verify the evaluation set structure."
81
+ ) from e
82
+ if eval_ids:
83
+ eval_set.extract_selected_evals(eval_ids)
84
+ return eval_set
uipath/_cli/cli_auth.py CHANGED
@@ -1,61 +1,15 @@
1
- import asyncio
2
- import json
3
- import os
4
- import socket
5
- import webbrowser
6
1
  from typing import Optional
7
- from urllib.parse import urlparse
8
2
 
9
3
  import click
10
4
 
11
5
  from ..telemetry import track
12
- from ._auth._auth_server import HTTPServer
13
- from ._auth._client_credentials import ClientCredentialsService
14
- from ._auth._oidc_utils import get_auth_config, get_auth_url
15
- from ._auth._portal_service import PortalService, select_tenant
16
- from ._auth._utils import update_auth_file, update_env_file
6
+ from ._auth._auth_service import AuthService
17
7
  from ._utils._common import environment_options
18
8
  from ._utils._console import ConsoleLogger
19
9
 
20
10
  console = ConsoleLogger()
21
11
 
22
12
 
23
- def is_port_in_use(port: int) -> bool:
24
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
25
- try:
26
- s.bind(("localhost", port))
27
- s.close()
28
- return False
29
- except socket.error:
30
- return True
31
-
32
-
33
- def set_port():
34
- auth_config = get_auth_config()
35
- port = int(auth_config.get("port", 8104))
36
- port_option_one = int(auth_config.get("portOptionOne", 8104)) # type: ignore
37
- port_option_two = int(auth_config.get("portOptionTwo", 8055)) # type: ignore
38
- port_option_three = int(auth_config.get("portOptionThree", 42042)) # type: ignore
39
- if is_port_in_use(port):
40
- if is_port_in_use(port_option_one):
41
- if is_port_in_use(port_option_two):
42
- if is_port_in_use(port_option_three):
43
- console.error(
44
- "All configured ports are in use. Please close applications using ports or configure different ports."
45
- )
46
- else:
47
- port = port_option_three
48
- else:
49
- port = port_option_two
50
- else:
51
- port = port_option_one
52
- auth_config["port"] = port
53
- with open(
54
- os.path.join(os.path.dirname(__file__), "..", "auth_config.json"), "w"
55
- ) as f:
56
- json.dump(auth_config, f)
57
-
58
-
59
13
  @click.command()
60
14
  @environment_options
61
15
  @click.option(
@@ -63,6 +17,7 @@ def set_port():
63
17
  "--force",
64
18
  is_flag=True,
65
19
  required=False,
20
+ default=False,
66
21
  help="Force new token",
67
22
  )
68
23
  @click.option(
@@ -89,7 +44,7 @@ def set_port():
89
44
  @track
90
45
  def auth(
91
46
  domain,
92
- force: Optional[bool] = False,
47
+ force: bool = False,
93
48
  client_id: Optional[str] = None,
94
49
  client_secret: Optional[str] = None,
95
50
  base_url: Optional[str] = None,
@@ -108,109 +63,21 @@ def auth(
108
63
  - Set REQUESTS_CA_BUNDLE to specify a custom CA bundle for SSL verification
109
64
  - Set UIPATH_DISABLE_SSL_VERIFY to disable SSL verification (not recommended)
110
65
  """
111
- uipath_url = os.getenv("UIPATH_URL")
112
- if uipath_url and domain == "cloud": # "cloud" is the default
113
- parsed_url = urlparse(uipath_url)
114
- if parsed_url.scheme and parsed_url.netloc:
115
- domain = f"{parsed_url.scheme}://{parsed_url.netloc}"
116
- else:
117
- console.error(
118
- f"Malformed UIPATH_URL: '{uipath_url}'. Please ensure it includes both scheme and netloc (e.g., 'https://cloud.uipath.com')."
66
+ auth_service = AuthService(
67
+ domain,
68
+ force=force,
69
+ client_id=client_id,
70
+ client_secret=client_secret,
71
+ base_url=base_url,
72
+ scope=scope,
73
+ )
74
+ with console.spinner("Authenticating with UiPath ..."):
75
+ try:
76
+ auth_service.authenticate()
77
+ console.success(
78
+ "Authentication successful.",
119
79
  )
120
- return
121
-
122
- # Check if client credentials are provided for unattended authentication
123
- if client_id and client_secret:
124
- if not base_url:
80
+ except KeyboardInterrupt:
125
81
  console.error(
126
- "--base-url is required when using client credentials authentication."
127
- )
128
- return
129
-
130
- with console.spinner("Authenticating with client credentials ..."):
131
- credentials_service = ClientCredentialsService(domain)
132
-
133
- # If base_url is provided, extract domain from it to override the CLI domain parameter
134
- if base_url:
135
- extracted_domain = credentials_service.extract_domain_from_base_url(
136
- base_url
137
- )
138
- credentials_service.domain = extracted_domain
139
-
140
- token_data = credentials_service.authenticate(
141
- client_id, client_secret, scope
142
- )
143
-
144
- if token_data:
145
- credentials_service.setup_environment(token_data, base_url)
146
- console.success(
147
- "Client credentials authentication successful.",
148
- )
149
- else:
150
- console.error(
151
- "Client credentials authentication failed. Please check your credentials.",
152
- )
153
- return
154
-
155
- # Interactive authentication flow (existing logic)
156
- with console.spinner("Authenticating with UiPath ..."):
157
- with PortalService(domain) as portal_service:
158
- if not force:
159
- if (
160
- os.getenv("UIPATH_URL")
161
- and os.getenv("UIPATH_TENANT_ID")
162
- and os.getenv("UIPATH_ORGANIZATION_ID")
163
- ):
164
- try:
165
- portal_service.ensure_valid_token()
166
- console.success(
167
- "Authentication successful.",
168
- )
169
- return
170
- except Exception:
171
- console.info(
172
- "Authentication token is invalid. Please reauthenticate.",
173
- )
174
-
175
- auth_url, code_verifier, state = get_auth_url(domain)
176
-
177
- webbrowser.open(auth_url, 1)
178
- auth_config = get_auth_config()
179
-
180
- console.link(
181
- "If a browser window did not open, please open the following URL in your browser:",
182
- auth_url,
82
+ "Authentication cancelled by user.",
183
83
  )
184
-
185
- try:
186
- server = HTTPServer(port=auth_config["port"])
187
- token_data = asyncio.run(server.start(state, code_verifier, domain))
188
-
189
- if not token_data:
190
- console.error(
191
- "Authentication failed. Please try again.",
192
- )
193
- return
194
-
195
- portal_service.update_token_data(token_data)
196
- update_auth_file(token_data)
197
- access_token = token_data["access_token"]
198
- update_env_file({"UIPATH_ACCESS_TOKEN": access_token})
199
-
200
- tenants_and_organizations = (
201
- portal_service.get_tenants_and_organizations()
202
- )
203
- base_url = select_tenant(domain, tenants_and_organizations)
204
- try:
205
- portal_service.post_auth(base_url)
206
- console.success(
207
- "Authentication successful.",
208
- )
209
- except Exception:
210
- console.error(
211
- "Could not prepare the environment. Please try again.",
212
- )
213
- except KeyboardInterrupt:
214
- console.error(
215
- "Authentication cancelled by user.",
216
- )