intuned-runtime 1.3.0rc0__py3-none-any.whl → 1.3.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 (45) hide show
  1. intuned_cli/__init__.py +15 -24
  2. intuned_cli/commands/__init__.py +6 -1
  3. intuned_cli/commands/attempt_api_command.py +8 -0
  4. intuned_cli/commands/attempt_authsession_check_command.py +8 -0
  5. intuned_cli/commands/attempt_authsession_command.py +4 -4
  6. intuned_cli/commands/attempt_authsession_create_command.py +9 -1
  7. intuned_cli/commands/attempt_command.py +4 -4
  8. intuned_cli/commands/authsession_command.py +12 -0
  9. intuned_cli/commands/authsession_record_command.py +54 -0
  10. intuned_cli/commands/command.py +6 -4
  11. intuned_cli/commands/deploy_command.py +2 -0
  12. intuned_cli/commands/init_command.py +2 -0
  13. intuned_cli/commands/run_api_command.py +9 -1
  14. intuned_cli/commands/run_authsession_command.py +4 -4
  15. intuned_cli/commands/run_authsession_create_command.py +34 -4
  16. intuned_cli/commands/run_authsession_update_command.py +33 -4
  17. intuned_cli/commands/run_authsession_validate_command.py +32 -3
  18. intuned_cli/commands/run_command.py +4 -4
  19. intuned_cli/commands/save_command.py +2 -0
  20. intuned_cli/controller/__test__/test_api.py +159 -18
  21. intuned_cli/controller/__test__/test_authsession.py +497 -6
  22. intuned_cli/controller/api.py +40 -39
  23. intuned_cli/controller/authsession.py +213 -110
  24. intuned_cli/controller/deploy.py +3 -3
  25. intuned_cli/controller/save.py +47 -48
  26. intuned_cli/types.py +14 -0
  27. intuned_cli/utils/__test__/test_browser.py +132 -0
  28. intuned_cli/utils/__test__/test_traces.py +27 -0
  29. intuned_cli/utils/api_helpers.py +54 -5
  30. intuned_cli/utils/auth_session_helpers.py +42 -7
  31. intuned_cli/utils/backend.py +4 -1
  32. intuned_cli/utils/browser.py +63 -0
  33. intuned_cli/utils/error.py +14 -0
  34. intuned_cli/utils/exclusions.py +1 -0
  35. intuned_cli/utils/help.py +9 -0
  36. intuned_cli/utils/traces.py +31 -0
  37. intuned_cli/utils/wrapper.py +58 -0
  38. intuned_internal_cli/__init__.py +7 -0
  39. {intuned_runtime-1.3.0rc0.dist-info → intuned_runtime-1.3.2.dist-info}/METADATA +4 -2
  40. {intuned_runtime-1.3.0rc0.dist-info → intuned_runtime-1.3.2.dist-info}/RECORD +45 -37
  41. runtime/browser/launch_chromium.py +19 -8
  42. runtime/types/settings_types.py +13 -4
  43. {intuned_runtime-1.3.0rc0.dist-info → intuned_runtime-1.3.2.dist-info}/WHEEL +0 -0
  44. {intuned_runtime-1.3.0rc0.dist-info → intuned_runtime-1.3.2.dist-info}/entry_points.txt +0 -0
  45. {intuned_runtime-1.3.0rc0.dist-info → intuned_runtime-1.3.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,27 +1,29 @@
1
1
  import json
2
- import os
3
2
  from typing import Any
3
+ from typing import Unpack
4
4
 
5
5
  from anyio import Path
6
6
  from pydantic import BaseModel
7
7
 
8
8
  from intuned_cli.controller.authsession import execute_run_validate_auth_session_cli
9
9
  from intuned_cli.controller.authsession import load_auth_session_instance
10
+ from intuned_cli.types import BaseExecuteCommandOptionsWithoutTrace
10
11
  from intuned_cli.utils.api_helpers import assert_api_file_exists
12
+ from intuned_cli.utils.browser import get_cli_run_options
11
13
  from intuned_cli.utils.console import console
12
14
  from intuned_cli.utils.error import CLIError
13
15
  from intuned_cli.utils.error import log_automation_error
14
16
  from intuned_cli.utils.get_auth_parameters import register_get_auth_session_parameters
17
+ from intuned_cli.utils.import_function import get_cli_import_function
15
18
  from intuned_cli.utils.timeout import extendable_timeout
19
+ from intuned_cli.utils.traces import cli_trace
16
20
  from runtime.errors.run_api_errors import AutomationError
17
- from runtime.run.run_api import import_function_from_api_dir
18
21
  from runtime.run.run_api import run_api
19
22
  from runtime.types.run_types import Auth
20
23
  from runtime.types.run_types import AutomationFunction
21
24
  from runtime.types.run_types import PayloadToAppend
22
25
  from runtime.types.run_types import ProxyConfig
23
26
  from runtime.types.run_types import RunApiParameters
24
- from runtime.types.run_types import StandaloneRunOptions
25
27
  from runtime.types.run_types import StateSession
26
28
  from runtime.types.run_types import StorageState
27
29
 
@@ -40,9 +42,8 @@ async def execute_run_api_cli(
40
42
  retries: int,
41
43
  auth_session: AuthSessionInput | None = None,
42
44
  output_file: str | None = None,
43
- headless: bool,
44
- timeout: float,
45
- proxy: str | None = None,
45
+ trace: bool,
46
+ **kwargs: Unpack[BaseExecuteCommandOptionsWithoutTrace],
46
47
  ):
47
48
  """
48
49
  Execute API with retries and optional auth session validation.
@@ -63,9 +64,8 @@ async def execute_run_api_cli(
63
64
  auto_recreate=auth_session.auto_recreate,
64
65
  check_retries=auth_session.check_retries,
65
66
  create_retries=auth_session.create_retries,
66
- headless=headless,
67
- timeout=timeout,
68
- proxy=proxy,
67
+ trace=trace,
68
+ **kwargs,
69
69
  )
70
70
 
71
71
  try:
@@ -73,9 +73,8 @@ async def execute_run_api_cli(
73
73
  api_name=api_name,
74
74
  parameters=input_data,
75
75
  auth=auth_session_instance,
76
- headless=headless,
77
- timeout=timeout,
78
- proxy=proxy,
76
+ trace_id=f"{api_name}-attempt-{i + 1}" if trace else None,
77
+ **kwargs,
79
78
  )
80
79
 
81
80
  return await handle_api_result(
@@ -101,9 +100,8 @@ async def execute_attempt_api_cli(
101
100
  input_data: Any | None,
102
101
  auth_session_id: str | None = None,
103
102
  output_file: str | None = None,
104
- headless: bool,
105
- timeout: float,
106
- proxy: str | None = None,
103
+ trace: bool,
104
+ **kwargs: Unpack[BaseExecuteCommandOptionsWithoutTrace],
107
105
  ):
108
106
  """
109
107
  Execute a single API attempt with optional auth session.
@@ -122,9 +120,8 @@ async def execute_attempt_api_cli(
122
120
  api_name=api_name,
123
121
  parameters=input_data,
124
122
  auth=auth_session_instance,
125
- headless=headless,
126
- timeout=timeout,
127
- proxy=proxy,
123
+ trace_id=f"{api_name}-attempt" if trace else None,
124
+ **kwargs,
128
125
  )
129
126
 
130
127
  return await handle_api_result(
@@ -186,26 +183,30 @@ async def attempt_api(
186
183
  api_name: str,
187
184
  parameters: Any | None,
188
185
  auth: StorageState | None = None,
189
- headless: bool,
190
- timeout: float,
191
- proxy: str | None = None,
186
+ trace_id: str | None = None,
187
+ **kwargs: Unpack[BaseExecuteCommandOptionsWithoutTrace],
192
188
  ):
193
- cwd = await Path().resolve()
194
- async with extendable_timeout(timeout):
195
- result = await run_api(
196
- RunApiParameters(
197
- automation_function=AutomationFunction(name=f"api/{api_name}", params=parameters),
198
- auth=Auth(
199
- session=StateSession(state=auth),
200
- )
201
- if auth
202
- else None,
203
- run_options=StandaloneRunOptions(
204
- headless=headless, proxy=ProxyConfig.parse_from_str(proxy) if proxy else None
189
+ timeout = kwargs.get("timeout")
190
+ headless = kwargs.get("headless")
191
+ proxy = kwargs.get("proxy")
192
+ keep_browser_open = kwargs.get("keep_browser_open")
193
+ with cli_trace(trace_id) as tracing:
194
+ async with extendable_timeout(timeout):
195
+ result = await run_api(
196
+ RunApiParameters(
197
+ automation_function=AutomationFunction(name=f"api/{api_name}", params=parameters),
198
+ auth=Auth(
199
+ session=StateSession(state=auth),
200
+ )
201
+ if auth
202
+ else None,
203
+ run_options=await get_cli_run_options(
204
+ headless=headless,
205
+ proxy=ProxyConfig.parse_from_str(proxy) if proxy else None,
206
+ keep_browser_open=keep_browser_open,
207
+ ),
208
+ tracing=tracing,
205
209
  ),
206
- ),
207
- import_function=lambda file_path, name=None: import_function_from_api_dir(
208
- file_path=file_path, automation_function_name=name, base_dir=os.fspath(cwd)
209
- ),
210
- )
211
- return result.result, result.payload_to_append
210
+ import_function=await get_cli_import_function(),
211
+ )
212
+ return result.result, result.payload_to_append
@@ -1,31 +1,41 @@
1
+ import asyncio
1
2
  import datetime
2
3
  import json
3
4
  import time
4
5
  from typing import Any
5
6
  from typing import Literal
7
+ from typing import TYPE_CHECKING
8
+ from typing import Unpack
6
9
 
7
10
  from anyio import Path
8
11
  from pydantic import BaseModel
9
12
  from pydantic import Field
10
13
  from pydantic import ValidationError
11
14
 
15
+ from intuned_cli.types import BaseExecuteCommandOptions
16
+ from intuned_cli.types import BaseExecuteCommandOptionsWithoutTrace
12
17
  from intuned_cli.utils.api_helpers import assert_api_file_exists
18
+ from intuned_cli.utils.browser import get_cli_run_options
13
19
  from intuned_cli.utils.console import console
14
20
  from intuned_cli.utils.error import CLIError
15
21
  from intuned_cli.utils.error import log_automation_error
16
22
  from intuned_cli.utils.get_auth_parameters import register_get_auth_session_parameters
17
23
  from intuned_cli.utils.import_function import get_cli_import_function
18
24
  from intuned_cli.utils.timeout import extendable_timeout
25
+ from intuned_cli.utils.traces import cli_trace
26
+ from runtime.browser.storage_state import get_storage_state
19
27
  from runtime.errors.run_api_errors import AutomationError
20
28
  from runtime.run.run_api import run_api
21
29
  from runtime.types.run_types import Auth
22
30
  from runtime.types.run_types import AutomationFunction
23
31
  from runtime.types.run_types import ProxyConfig
24
32
  from runtime.types.run_types import RunApiParameters
25
- from runtime.types.run_types import StandaloneRunOptions
26
33
  from runtime.types.run_types import StateSession
27
34
  from runtime.types.run_types import StorageState
28
35
 
36
+ if TYPE_CHECKING:
37
+ from playwright.async_api import ProxySettings
38
+
29
39
  auth_session_instances_dirname = "auth-session-instances"
30
40
 
31
41
 
@@ -45,9 +55,7 @@ async def execute_run_validate_auth_session_cli(
45
55
  auto_recreate: bool,
46
56
  check_retries: int,
47
57
  create_retries: int,
48
- headless: bool,
49
- timeout: float,
50
- proxy: str | None = None,
58
+ **kwargs: Unpack[BaseExecuteCommandOptions],
51
59
  ) -> StorageState:
52
60
  """
53
61
  Validate auth session with optional auto-recreation.
@@ -64,13 +72,13 @@ async def execute_run_validate_auth_session_cli(
64
72
  check_result = await run_check_with_retries(
65
73
  auth=instance,
66
74
  retries=check_retries,
67
- headless=headless,
68
- timeout=timeout,
69
- proxy=proxy,
75
+ **kwargs,
70
76
  )
71
77
 
72
78
  if not check_result:
73
79
  if not auto_recreate:
80
+ if metadata and metadata.auth_session_type == "MANUAL":
81
+ raise CLIError("Auth session validation failed")
74
82
  raise CLIError("Auto recreate is disabled, please provide a new auth session or update it manually")
75
83
 
76
84
  if metadata and metadata.auth_session_type == "MANUAL":
@@ -86,19 +94,15 @@ async def execute_run_validate_auth_session_cli(
86
94
  auth_session_id=id,
87
95
  auth_session_input=auth_session_input,
88
96
  retries=create_retries,
89
- headless=headless,
90
- timeout=timeout,
91
- proxy=proxy,
92
97
  metadata=metadata,
98
+ **kwargs,
93
99
  )
94
100
 
95
101
  # Rerun check after refresh
96
102
  check_result = await run_check_with_retries(
97
103
  auth=instance,
98
104
  retries=check_retries,
99
- headless=headless,
100
- timeout=timeout,
101
- proxy=proxy,
105
+ **kwargs,
102
106
  )
103
107
  if not check_result:
104
108
  raise CLIError("Failed to re-create auth session")
@@ -114,16 +118,14 @@ async def execute_run_create_auth_session_cli(
114
118
  check_retries: int,
115
119
  create_retries: int,
116
120
  log: bool = True,
117
- headless: bool,
118
- timeout: float,
119
- proxy: str | None = None,
120
121
  metadata: AuthSessionMetadata | None = None,
122
+ **kwargs: Unpack[BaseExecuteCommandOptions],
121
123
  ):
122
124
  """
123
125
  Create a new auth session.
124
126
  """
125
127
  if id is None:
126
- id = f"auth-session-{int(time.time() * 1000)}"
128
+ id = generate_auth_session_id()
127
129
 
128
130
  if log:
129
131
  console.print(f"[bold]Creating auth session with id [cyan]{id}[/cyan][/bold]")
@@ -135,18 +137,14 @@ async def execute_run_create_auth_session_cli(
135
137
  auth_session_id=id,
136
138
  auth_session_input=input_data,
137
139
  retries=create_retries,
138
- headless=headless,
139
- timeout=timeout,
140
- proxy=proxy,
141
140
  metadata=metadata,
141
+ **kwargs,
142
142
  )
143
143
 
144
144
  check_result = await run_check_with_retries(
145
145
  auth=instance,
146
146
  retries=check_retries,
147
- headless=headless,
148
- timeout=timeout,
149
- proxy=proxy,
147
+ **kwargs,
150
148
  )
151
149
  if not check_result:
152
150
  raise CLIError("Failed to create auth session")
@@ -161,9 +159,7 @@ async def execute_run_update_auth_session_cli(
161
159
  input_data: Any | None = None,
162
160
  check_retries: int,
163
161
  create_retries: int,
164
- headless: bool,
165
- timeout: float,
166
- proxy: str | None = None,
162
+ **kwargs: Unpack[BaseExecuteCommandOptions],
167
163
  ):
168
164
  """
169
165
  Update an existing auth session.
@@ -186,10 +182,8 @@ async def execute_run_update_auth_session_cli(
186
182
  check_retries=check_retries,
187
183
  create_retries=create_retries,
188
184
  log=False,
189
- headless=headless,
190
- timeout=timeout,
191
- proxy=proxy,
192
185
  metadata=metadata,
186
+ **kwargs,
193
187
  )
194
188
 
195
189
  console.print("[bold][green]Auth session updated successfully[/green][/bold]")
@@ -199,35 +193,25 @@ async def execute_attempt_create_auth_session_cli(
199
193
  *,
200
194
  id: str | None = None,
201
195
  input_data: Any,
202
- headless: bool,
203
- timeout: float,
204
- proxy: str | None = None,
196
+ **kwargs: Unpack[BaseExecuteCommandOptions],
205
197
  ):
206
198
  """
207
199
  Execute a single attempt to create auth session.
208
200
  """
209
201
  if id is None:
210
- id = f"auth-session-attempt-{int(time.time() * 1000)}"
202
+ id = generate_auth_session_id(is_attempt=True)
211
203
 
212
204
  console.print(f"[bold]Executing create auth session attempt with id [cyan]{id}[/cyan][/bold]")
213
205
  await assert_api_file_exists("auth-sessions", "create")
214
206
  await run_create_with_retries(
215
207
  auth_session_id=id,
216
208
  auth_session_input=input_data,
217
- retries=1,
218
- headless=headless,
219
- timeout=timeout,
220
- proxy=proxy,
209
+ retries=None,
210
+ **kwargs,
221
211
  )
222
212
 
223
213
 
224
- async def execute_attempt_check_auth_session_cli(
225
- *,
226
- id: str,
227
- headless: bool,
228
- timeout: float,
229
- proxy: str | None = None,
230
- ):
214
+ async def execute_attempt_check_auth_session_cli(*, id: str, **kwargs: Unpack[BaseExecuteCommandOptions]):
231
215
  """
232
216
  Execute a single attempt to check auth session.
233
217
  """
@@ -240,10 +224,8 @@ async def execute_attempt_check_auth_session_cli(
240
224
 
241
225
  check_result = await run_check_with_retries(
242
226
  auth=instance,
243
- retries=1,
244
- headless=headless,
245
- timeout=timeout,
246
- proxy=proxy,
227
+ retries=None,
228
+ **kwargs,
247
229
  )
248
230
 
249
231
  if not check_result:
@@ -253,90 +235,105 @@ async def execute_attempt_check_auth_session_cli(
253
235
 
254
236
 
255
237
  async def run_check(
256
- *,
257
- auth: StorageState,
258
- headless: bool,
259
- timeout: float,
260
- proxy: str | None = None,
238
+ *, auth: StorageState, trace_id: str | None = None, **kwargs: Unpack[BaseExecuteCommandOptionsWithoutTrace]
261
239
  ) -> bool:
262
240
  """
263
241
  Run auth session check.
264
242
  """
265
- async with extendable_timeout(timeout):
266
- result = await run_api(
267
- RunApiParameters(
268
- automation_function=AutomationFunction(
269
- name="auth-sessions/check",
270
- params=None,
271
- ),
272
- run_options=StandaloneRunOptions(
273
- headless=headless, proxy=ProxyConfig.parse_from_str(proxy) if proxy else None
274
- ),
275
- auth=Auth(
276
- session=StateSession(
277
- state=auth,
243
+ timeout = kwargs.get("timeout")
244
+ headless = kwargs.get("headless")
245
+ proxy = kwargs.get("proxy")
246
+ keep_browser_open = kwargs.get("keep_browser_open")
247
+ with cli_trace(trace_id) as tracing:
248
+ async with extendable_timeout(timeout):
249
+ result = await run_api(
250
+ RunApiParameters(
251
+ automation_function=AutomationFunction(
252
+ name="auth-sessions/check",
253
+ params=None,
254
+ ),
255
+ run_options=await get_cli_run_options(
256
+ headless=headless,
257
+ proxy=ProxyConfig.parse_from_str(proxy) if proxy else None,
258
+ keep_browser_open=keep_browser_open,
278
259
  ),
260
+ auth=Auth(
261
+ session=StateSession(
262
+ state=auth,
263
+ ),
264
+ ),
265
+ tracing=tracing,
279
266
  ),
280
- ),
281
- import_function=await get_cli_import_function(),
282
- )
267
+ import_function=await get_cli_import_function(),
268
+ )
283
269
 
284
- if not result.result:
285
- return False
270
+ if not result.result:
271
+ return False
286
272
 
287
- return bool(result.result)
273
+ return bool(result.result)
288
274
 
289
275
 
290
276
  async def run_create(
291
277
  *,
292
278
  auth_session_input: dict[str, Any],
293
- headless: bool,
294
- timeout: float,
295
- proxy: str | None = None,
279
+ trace_id: str | None = None,
280
+ **kwargs: Unpack[BaseExecuteCommandOptionsWithoutTrace],
296
281
  ) -> StorageState:
297
282
  """
298
283
  Run auth session create.
299
284
  """
300
- async with extendable_timeout(timeout):
301
- result = await run_api(
302
- RunApiParameters(
303
- automation_function=AutomationFunction(
304
- name="auth-sessions/create",
305
- params=auth_session_input,
306
- ),
307
- run_options=StandaloneRunOptions(
308
- headless=headless, proxy=ProxyConfig.parse_from_str(proxy) if proxy else None
285
+
286
+ timeout = kwargs.get("timeout")
287
+ headless = kwargs.get("headless")
288
+ proxy = kwargs.get("proxy")
289
+ keep_browser_open = kwargs.get("keep_browser_open")
290
+ with cli_trace(trace_id) as tracing:
291
+ async with extendable_timeout(timeout):
292
+ result = await run_api(
293
+ RunApiParameters(
294
+ automation_function=AutomationFunction(
295
+ name="auth-sessions/create",
296
+ params=auth_session_input,
297
+ ),
298
+ run_options=await get_cli_run_options(
299
+ headless=headless,
300
+ proxy=ProxyConfig.parse_from_str(proxy) if proxy else None,
301
+ keep_browser_open=keep_browser_open,
302
+ ),
303
+ retrieve_session=True,
304
+ tracing=tracing,
309
305
  ),
310
- retrieve_session=True,
311
- ),
312
- import_function=await get_cli_import_function(),
313
- )
314
- if not result.session:
315
- raise Exception("Auth session create did not return a session")
316
- return result.session
306
+ import_function=await get_cli_import_function(),
307
+ )
308
+ if not result.session:
309
+ raise Exception("Auth session create did not return a session")
310
+ return result.session
317
311
 
318
312
 
319
313
  async def run_check_with_retries(
320
314
  *,
321
315
  auth: StorageState,
322
- retries: int,
323
- headless: bool,
324
- timeout: float,
325
- proxy: str | None = None,
316
+ retries: int | None,
317
+ trace: bool,
318
+ **kwargs: Unpack[BaseExecuteCommandOptionsWithoutTrace],
326
319
  ) -> bool:
327
320
  """
328
321
  Run auth session check with retries.
329
322
  """
330
- for i in range(retries):
323
+ for i in range(retries or 1):
331
324
  attempt_text = "" if i == 0 else f" [italic](Attempt {i + 1})[/italic]"
332
325
  console.print(f"\n[bold]Running [cyan]auth session check[/cyan]{attempt_text}...[/bold]\n")
333
326
 
334
327
  try:
328
+ trace_id: str | None = None
329
+ if trace:
330
+ trace_id = "authsession-check-attempt"
331
+ if retries is not None:
332
+ trace_id += f"-{i+1}"
335
333
  check_result = await run_check(
336
334
  auth=auth,
337
- headless=headless,
338
- timeout=timeout,
339
- proxy=proxy,
335
+ trace_id=trace_id,
336
+ **kwargs,
340
337
  )
341
338
 
342
339
  if check_result:
@@ -354,27 +351,31 @@ async def run_create_with_retries(
354
351
  *,
355
352
  auth_session_id: str,
356
353
  auth_session_input: dict[str, Any],
357
- retries: int,
358
- headless: bool,
359
- timeout: float,
360
- proxy: str | None = None,
354
+ retries: int | None,
361
355
  metadata: AuthSessionMetadata | None = None,
356
+ trace: bool,
357
+ **kwargs: Unpack[BaseExecuteCommandOptionsWithoutTrace],
362
358
  ):
363
359
  """
364
360
  Run auth session create with retries.
365
361
  """
366
362
  new_auth_session_instance: StorageState | None = None
367
363
 
368
- for i in range(retries):
364
+ for i in range(retries or 1):
369
365
  attempt_text = "" if i == 0 else f" [italic](Attempt {i + 1})[/italic]"
370
366
  console.print(f"\n[bold]Running [cyan]auth session create[/cyan]{attempt_text}...[/bold]\n")
371
367
 
372
368
  try:
369
+ trace_id: str | None = None
370
+ if trace:
371
+ trace_id = "authsession-create-attempt"
372
+ if retries is not None:
373
+ trace_id += f"-{i+1}"
374
+
373
375
  new_auth_session_instance = await run_create(
374
376
  auth_session_input=auth_session_input,
375
- headless=headless,
376
- timeout=timeout,
377
- proxy=proxy,
377
+ trace_id=trace_id,
378
+ **kwargs,
378
379
  )
379
380
  console.print("[bold][green]Auth session create succeeded[/green][/bold]")
380
381
  break
@@ -424,7 +425,7 @@ async def load_auth_session_instance(auth_session_id: str) -> tuple[StorageState
424
425
  async def store_auth_session_instance(
425
426
  auth_session_instance: StorageState,
426
427
  auth_session_id: str,
427
- auth_session_input: dict[str, Any],
428
+ auth_session_input: dict[str, Any] | None = None,
428
429
  metadata: AuthSessionMetadata | None = None,
429
430
  ):
430
431
  """
@@ -445,13 +446,115 @@ async def store_auth_session_instance(
445
446
  updatedAt=datetime.datetime.now().isoformat(),
446
447
  authSessionId=auth_session_id,
447
448
  authSessionInput=auth_session_input,
448
- authSessionType="API",
449
+ authSessionType=metadata.auth_session_type if metadata else "API",
449
450
  )
450
451
  metadata_file_path = auth_session_path / "metadata.json"
451
452
 
452
- metadata_json = json.dumps(metadata.model_dump(by_alias=True), indent=2)
453
+ metadata_json = json.dumps(metadata.model_dump(by_alias=True, exclude_none=True), indent=2)
453
454
  await metadata_file_path.write_text(metadata_json)
454
455
 
455
456
 
456
457
  def get_auth_session_path(auth_session_id: str):
457
458
  return Path(auth_session_instances_dirname) / auth_session_id
459
+
460
+
461
+ async def execute_record_auth_session_cli(
462
+ *,
463
+ start_url: str,
464
+ finish_url: str,
465
+ id: str | None = None,
466
+ check_retries: int = 1,
467
+ **kwargs: Unpack[BaseExecuteCommandOptions],
468
+ ):
469
+ """
470
+ Record a new auth session using a browser.
471
+ """
472
+
473
+ from playwright.async_api import ProxySettings
474
+
475
+ if id is None:
476
+ id = generate_auth_session_id()
477
+
478
+ console.print(f"[bold]Recording auth session with id [cyan]{id}[/cyan][/bold]")
479
+ proxy = kwargs.get("proxy")
480
+
481
+ proxy_settings: ProxySettings | None = None
482
+ if proxy:
483
+ proxy_settings = ProxySettings(server=proxy)
484
+
485
+ try:
486
+ auth_session = await record_auth_session(
487
+ start_url=start_url,
488
+ finish_url=finish_url,
489
+ timeout=kwargs.get("timeout"),
490
+ proxy=proxy_settings,
491
+ )
492
+ except CLIError as e:
493
+ raise CLIError(f"Failed to record auth session: {e}") from e
494
+
495
+ await store_auth_session_instance(
496
+ auth_session_instance=auth_session,
497
+ auth_session_id=id,
498
+ metadata=AuthSessionMetadata(
499
+ createdAt=datetime.datetime.now().isoformat(),
500
+ updatedAt=datetime.datetime.now().isoformat(),
501
+ authSessionId=id,
502
+ authSessionInput=None,
503
+ authSessionType="MANUAL",
504
+ recorderStartUrl=start_url,
505
+ recorderEndUrl=finish_url,
506
+ ),
507
+ )
508
+
509
+ await execute_run_validate_auth_session_cli(
510
+ id=id,
511
+ create_retries=1,
512
+ check_retries=check_retries,
513
+ auto_recreate=False,
514
+ **kwargs,
515
+ )
516
+
517
+ console.print(f"[bold][green]Auth session [cyan]{id}[/cyan] recorded successfully[/green][/bold]")
518
+
519
+
520
+ async def record_auth_session(
521
+ *,
522
+ start_url: str,
523
+ finish_url: str,
524
+ timeout: float = 300,
525
+ proxy: "ProxySettings | None" = None,
526
+ ):
527
+ from runtime.browser import launch_chromium
528
+
529
+ async with launch_chromium(
530
+ proxy=proxy,
531
+ headless=False,
532
+ app_mode_initial_url=start_url,
533
+ ) as (context, page):
534
+ if not page.url.startswith(start_url):
535
+ await page.goto(start_url)
536
+ console.print(f"[bold]Navigated to[/bold] [underline]{start_url}[/underline]")
537
+ console.print(f"[bold]Waiting for[/bold] [underline]{finish_url}[/underline]...")
538
+
539
+ try:
540
+ async with asyncio.timeout(timeout):
541
+ while True:
542
+ if len(context.pages) == 0:
543
+ raise CLIError("Browser was closed before reaching the finish URL")
544
+ if context.pages[0].url.startswith(finish_url):
545
+ break
546
+ await asyncio.sleep(1)
547
+
548
+ console.print("[bold]Finish URL reached, capturing auth session...[/bold]")
549
+ await page.wait_for_load_state("load")
550
+ auth_session = await get_storage_state(context)
551
+ return auth_session
552
+ except asyncio.TimeoutError as e:
553
+ raise CLIError("Timeout waiting for finish URL") from e
554
+
555
+
556
+ def generate_auth_session_id(is_attempt: bool = False) -> str:
557
+ timestamp = int(time.time() * 1000)
558
+ if is_attempt:
559
+ return f"auth-session-attempt-{timestamp}"
560
+ return f"auth-session-{timestamp}"