intuned-runtime 1.2.0__tar.gz → 1.2.2__tar.gz

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 (109) hide show
  1. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/PKG-INFO +4 -2
  2. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/__init__.py +12 -2
  3. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/commands/__init__.py +1 -0
  4. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/commands/attempt_api_command.py +1 -1
  5. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/commands/attempt_authsession_check_command.py +1 -1
  6. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/commands/attempt_authsession_create_command.py +1 -1
  7. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/commands/deploy_command.py +4 -4
  8. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/commands/run_api_command.py +1 -1
  9. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/commands/run_authsession_create_command.py +1 -1
  10. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/commands/run_authsession_update_command.py +1 -1
  11. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/commands/run_authsession_validate_command.py +1 -1
  12. intuned_runtime-1.2.2/intuned_cli/commands/save_command.py +47 -0
  13. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/controller/__test__/test_api.py +0 -1
  14. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/controller/api.py +0 -1
  15. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/controller/authsession.py +0 -1
  16. intuned_runtime-1.2.2/intuned_cli/controller/deploy.py +150 -0
  17. intuned_runtime-1.2.0/intuned_cli/controller/deploy.py → intuned_runtime-1.2.2/intuned_cli/controller/save.py +46 -138
  18. intuned_runtime-1.2.2/intuned_cli/utils/backend.py +31 -0
  19. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/utils/error.py +7 -3
  20. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/commands/project/auth_session/check.py +0 -1
  21. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/commands/project/run.py +0 -2
  22. {intuned_runtime-1.2.0/runtime_helpers → intuned_runtime-1.2.2/intuned_runtime}/__init__.py +2 -1
  23. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/pyproject.toml +4 -1
  24. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/backend_functions/_call_backend_function.py +18 -2
  25. intuned_runtime-1.2.2/runtime/browser/helpers.py +43 -0
  26. intuned_runtime-1.2.2/runtime/browser/launch_browser.py +59 -0
  27. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/browser/launch_chromium.py +26 -22
  28. intuned_runtime-1.2.2/runtime/constants.py +1 -0
  29. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/context/context.py +1 -0
  30. intuned_runtime-1.2.2/runtime/env.py +31 -0
  31. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/errors/run_api_errors.py +8 -0
  32. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/helpers/__init__.py +2 -1
  33. intuned_runtime-1.2.2/runtime/helpers/attempt_store.py +14 -0
  34. intuned_runtime-1.2.2/runtime/run/playwright_context.py +147 -0
  35. intuned_runtime-1.2.2/runtime/run/playwright_tracing.py +27 -0
  36. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/run/run_api.py +71 -91
  37. intuned_runtime-1.2.2/runtime/run/setup_context_hook.py +40 -0
  38. intuned_runtime-1.2.2/runtime/run/types.py +14 -0
  39. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/types/run_types.py +0 -1
  40. {intuned_runtime-1.2.0/intuned_runtime → intuned_runtime-1.2.2/runtime_helpers}/__init__.py +2 -1
  41. intuned_runtime-1.2.0/intuned_cli/utils/backend.py +0 -5
  42. intuned_runtime-1.2.0/runtime/browser/helpers.py +0 -21
  43. intuned_runtime-1.2.0/runtime/browser/launch_browser.py +0 -31
  44. intuned_runtime-1.2.0/runtime/env.py +0 -17
  45. intuned_runtime-1.2.0/runtime/run/playwright_constructs.py +0 -20
  46. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/LICENSE +0 -0
  47. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/README.md +0 -0
  48. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/commands/attempt_authsession_command.py +0 -0
  49. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/commands/attempt_command.py +0 -0
  50. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/commands/command.py +0 -0
  51. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/commands/init_command.py +0 -0
  52. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/commands/run_authsession_command.py +0 -0
  53. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/commands/run_command.py +0 -0
  54. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/controller/__test__/__init__.py +0 -0
  55. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/controller/__test__/test_authsession.py +0 -0
  56. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/types.py +0 -0
  57. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/utils/api_helpers.py +0 -0
  58. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/utils/auth_session_helpers.py +0 -0
  59. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/utils/confirmation.py +0 -0
  60. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/utils/console.py +0 -0
  61. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/utils/exclusions.py +0 -0
  62. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/utils/get_auth_parameters.py +0 -0
  63. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/utils/import_function.py +0 -0
  64. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_cli/utils/timeout.py +0 -0
  65. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/__init__.py +0 -0
  66. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/commands/__init__.py +0 -0
  67. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/commands/ai_source/__init__.py +0 -0
  68. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/commands/ai_source/ai_source.py +0 -0
  69. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/commands/ai_source/deploy.py +0 -0
  70. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/commands/browser/__init__.py +0 -0
  71. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/commands/browser/save_state.py +0 -0
  72. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/commands/init.py +0 -0
  73. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/commands/project/__init__.py +0 -0
  74. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/commands/project/auth_session/__init__.py +0 -0
  75. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/commands/project/auth_session/create.py +0 -0
  76. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/commands/project/auth_session/load.py +0 -0
  77. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/commands/project/project.py +0 -0
  78. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/commands/project/run_interface.py +0 -0
  79. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/commands/project/type_check.py +0 -0
  80. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/commands/project/upgrade.py +0 -0
  81. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/commands/publish_packages.py +0 -0
  82. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/commands/root.py +0 -0
  83. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/logger.py +0 -0
  84. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/utils/ai_source_project.py +0 -0
  85. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/utils/code_tree.py +0 -0
  86. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/utils/run_apis.py +0 -0
  87. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/utils/setup_ide_functions_token.py +0 -0
  88. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/intuned_internal_cli/utils/unix_socket.py +0 -0
  89. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/__init__.py +0 -0
  90. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/backend_functions/__init__.py +0 -0
  91. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/backend_functions/get_auth_session_parameters.py +0 -0
  92. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/browser/__init__.py +0 -0
  93. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/browser/launch_camoufox.py +0 -0
  94. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/browser/storage_state.py +0 -0
  95. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/context/__init__.py +0 -0
  96. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/errors/__init__.py +0 -0
  97. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/errors/auth_session_errors.py +0 -0
  98. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/errors/trace_errors.py +0 -0
  99. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/helpers/extend_payload.py +0 -0
  100. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/helpers/extend_timeout.py +0 -0
  101. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/helpers/get_auth_session_parameters.py +0 -0
  102. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/py.typed +0 -0
  103. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/run/__init__.py +0 -0
  104. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/run/intuned_settings.py +0 -0
  105. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/run/pydantic_encoder.py +0 -0
  106. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/run/traces.py +0 -0
  107. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/types/__init__.py +0 -0
  108. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime/types/payload.py +0 -0
  109. {intuned_runtime-1.2.0 → intuned_runtime-1.2.2}/runtime_helpers/py.typed +0 -0
@@ -1,8 +1,9 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: intuned-runtime
3
- Version: 1.2.0
3
+ Version: 1.2.2
4
4
  Summary: Runtime commands for Intuned platform Python scrapers
5
5
  License: Elastic-2.0
6
+ License-File: LICENSE
6
7
  Keywords: runtime,intuned
7
8
  Author: Intuned Developers
8
9
  Author-email: engineering@intunedhq.com
@@ -15,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.10
15
16
  Classifier: Programming Language :: Python :: 3.11
16
17
  Classifier: Programming Language :: Python :: 3.12
17
18
  Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
18
20
  Requires-Dist: aiofiles (>=24.1.0,<25.0.0)
19
21
  Requires-Dist: arguably (>=1.3.0,<2.0.0)
20
22
  Requires-Dist: browserforge[all]
@@ -1,3 +1,4 @@
1
+ import os
1
2
  import sys
2
3
 
3
4
  import arguably
@@ -8,7 +9,7 @@ from intuned_cli.utils.console import console
8
9
  from intuned_cli.utils.error import CLIError
9
10
  from intuned_cli.utils.error import log_automation_error
10
11
  from runtime.context.context import IntunedContext
11
- from runtime.errors.run_api_errors import AutomationError
12
+ from runtime.errors.run_api_errors import RunApiError
12
13
 
13
14
  from . import commands
14
15
 
@@ -17,6 +18,15 @@ def run():
17
18
  dotenv = find_dotenv(usecwd=True)
18
19
  if dotenv:
19
20
  load_dotenv(dotenv, override=True)
21
+ from runtime.env import cli_env_var_key
22
+
23
+ os.environ[cli_env_var_key] = "true"
24
+ os.environ["RUN_ENVIRONMENT"] = "AUTHORING"
25
+
26
+ if not os.environ.get("FUNCTIONS_DOMAIN"):
27
+ from intuned_cli.utils.backend import get_base_url
28
+
29
+ os.environ["FUNCTIONS_DOMAIN"] = get_base_url().replace("/$", "")
20
30
  try:
21
31
  with IntunedContext():
22
32
  arguably.run(name="intuned")
@@ -26,7 +36,7 @@ def run():
26
36
  console.print(f"[bold red]{e.message}[/bold red]")
27
37
  else:
28
38
  console.print(e.message)
29
- except AutomationError as e:
39
+ except RunApiError as e:
30
40
  log_automation_error(e)
31
41
  except KeyboardInterrupt:
32
42
  console.print("[bold red]Aborted[/bold red]")
@@ -16,3 +16,4 @@ from .run_authsession_create_command import run__authsession__create as run__aut
16
16
  from .run_authsession_update_command import run__authsession__update as run__authsession__update # type: ignore
17
17
  from .run_authsession_validate_command import run__authsession__validate as run__authsession__validate # type: ignore
18
18
  from .run_command import run as run # type: ignore
19
+ from .save_command import save as save # type: ignore
@@ -25,7 +25,7 @@ async def attempt__api(
25
25
  parameters (str): Path to JSON file containing API parameters or the parameters as a JSON string.
26
26
  auth_session (str | None, optional): [-a/--auth-session]. ID of the auth session to use for the API. This is expected to be in ./auth-session-instances/<id>
27
27
  proxy (str | None, optional): [--proxy]. Proxy URL to use. Defaults to None.
28
- timeout (str, optional): [--timeout]. Timeout - milliseconds or pytimeparse-formatted string. Defaults to "10 min".
28
+ timeout (str, optional): [--timeout]. Timeout - seconds or pytimeparse-formatted string. Defaults to "10 min".
29
29
  headless (bool, optional): [--headless]. Run the API in headless mode (default: False). This will not open a browser window.
30
30
  output_file (str | None, optional): [-o/--output-file]. Output file path. Defaults to None.
31
31
  """
@@ -19,7 +19,7 @@ async def attempt__authsession__check(
19
19
  Args:
20
20
  id (str): ID of the auth session to check
21
21
  proxy (str | None, optional): [--proxy]. Proxy URL to use for the auth session command. Defaults to None.
22
- timeout (str, optional): [--timeout]. Timeout for the auth session command - milliseconds or pytimeparse-formatted string. Defaults to "10 min".
22
+ timeout (str, optional): [--timeout]. Timeout for the auth session command - seconds or pytimeparse-formatted string. Defaults to "10 min".
23
23
  headless (bool, optional): [--headless]. Run the API in headless mode (default: False). This will not open a browser window.
24
24
  """
25
25
  await assert_auth_enabled()
@@ -22,7 +22,7 @@ async def attempt__authsession__create(
22
22
  parameters (str): Parameters for the auth session command
23
23
  id (str | None, optional): [--id]. ID of the auth session to use for the command. Defaults to ./auth-session-instances/[current timestamp].json.
24
24
  proxy (str | None, optional): [--proxy]. Proxy URL to use for the auth session command. Defaults to None.
25
- timeout (str, optional): [--timeout]. Timeout for the auth session command - milliseconds or pytimeparse-formatted string. Defaults to "10 min".
25
+ timeout (str, optional): [--timeout]. Timeout for the auth session command - seconds or pytimeparse-formatted string. Defaults to "10 min".
26
26
  headless (bool, optional): [--headless]. Run the API in headless mode (default: False). This will not open a browser window.
27
27
  """
28
28
  await assert_auth_enabled()
@@ -1,9 +1,9 @@
1
1
  import arguably
2
2
 
3
3
  from intuned_cli.controller.deploy import deploy_project
4
- from intuned_cli.controller.deploy import get_intuned_api_auth_credentials
5
- from intuned_cli.controller.deploy import validate_intuned_project
6
- from intuned_cli.controller.deploy import validate_project_name
4
+ from intuned_cli.controller.save import validate_intuned_project
5
+ from intuned_cli.controller.save import validate_project_name
6
+ from intuned_cli.utils.backend import get_intuned_api_auth_credentials
7
7
  from intuned_cli.utils.error import CLIError
8
8
 
9
9
 
@@ -15,7 +15,7 @@ async def deploy(
15
15
  workspace_id: str | None = None,
16
16
  api_key: str | None = None,
17
17
  ):
18
- """Deploys the application.
18
+ """Saves and deploys the project to Intuned.
19
19
 
20
20
  Args:
21
21
  project_name (str | None, optional): The name of the project to deploy.
@@ -34,7 +34,7 @@ async def run__api(
34
34
  no_auth_session_auto_recreate (bool, optional): [--no-auth-session-auto-recreate]. Disable auto recreate for auth session. Defaults to False.
35
35
  auth_session_check_attempts (int, optional): [--auth-session-check-attempts]. Auth session check attempts. Defaults to 1.
36
36
  auth_session_create_attempts (int, optional): [--auth-session-create-attempts]. Auth session create attempts. Defaults to 1.
37
- timeout (str, optional): [--timeout]. Timeout - milliseconds or pytimeparse-formatted string. Defaults to "10 min".
37
+ timeout (str, optional): [--timeout]. Timeout - seconds or pytimeparse-formatted string. Defaults to "10 min".
38
38
  headless (bool, optional): [--headless]. Run the API in headless mode (default: False). This will not open a browser window.
39
39
  output_file (str | None, optional): [-o/--output-file]. Output file path. Defaults to None.
40
40
  """
@@ -26,7 +26,7 @@ async def run__authsession__create(
26
26
  check_attempts (int, optional): [--check-attempts]. Number of attempts to check the auth session validity. Defaults to 1.
27
27
  create_attempts (int, optional): [--create-attempts]. Number of attempts to create a new auth session if it is invalid. Defaults to 1.
28
28
  proxy (str | None, optional): [--proxy]. Proxy URL to use for the auth session command. Defaults to None.
29
- timeout (str, optional): [--timeout]. Timeout for the auth session command - milliseconds or pytimeparse-formatted string. Defaults to "10 min".
29
+ timeout (str, optional): [--timeout]. Timeout for the auth session command - seconds or pytimeparse-formatted string. Defaults to "10 min".
30
30
  headless (bool, optional): [--headless]. Run the API in headless mode (default: False). This will not open a browser window.
31
31
  """
32
32
  await assert_auth_enabled()
@@ -26,7 +26,7 @@ async def run__authsession__update(
26
26
  check_attempts (int, optional): [--check-attempts]. Number of attempts to check the auth session validity. Defaults to 1.
27
27
  create_attempts (int, optional): [--create-attempts]. Number of attempts to create a new auth session if it is invalid. Defaults to 1.
28
28
  proxy (str | None, optional): [--proxy]. Proxy URL to use for the auth session command. Defaults to None.
29
- timeout (str, optional): [--timeout]. Timeout for the auth session command - milliseconds or pytimeparse-formatted string. Defaults to "10 min".
29
+ timeout (str, optional): [--timeout]. Timeout for the auth session command - seconds or pytimeparse-formatted string. Defaults to "10 min".
30
30
  headless (bool, optional): [--headless]. Run the API in headless mode (default: False). This will not open a browser window.
31
31
  """
32
32
  await assert_auth_enabled()
@@ -24,7 +24,7 @@ async def run__authsession__validate(
24
24
  check_attempts (int, optional): [--check-attempts]. Number of attempts to check the auth session validity. Defaults to 1.
25
25
  create_attempts (int, optional): [--create-attempts]. Number of attempts to create a new auth session if it is invalid. Defaults to 1.
26
26
  proxy (str | None, optional): [--proxy]. Proxy URL to use for the auth session command. Defaults to None.
27
- timeout (str, optional): [--timeout]. Timeout for the auth session command - milliseconds or pytimeparse-formatted string. Defaults to "10 min".
27
+ timeout (str, optional): [--timeout]. Timeout for the auth session command - seconds or pytimeparse-formatted string. Defaults to "10 min".
28
28
  no_auto_recreate (bool, optional): [--no-auto-recreate]. Disable auto recreation of the auth session if it is invalid. Defaults to False.
29
29
  headless (bool, optional): [--headless]. Run the API in headless mode (default: False). This will not open a browser window.
30
30
  """
@@ -0,0 +1,47 @@
1
+ import arguably
2
+
3
+ from intuned_cli.controller.save import save_project
4
+ from intuned_cli.controller.save import validate_intuned_project
5
+ from intuned_cli.controller.save import validate_project_name
6
+ from intuned_cli.utils.backend import get_intuned_api_auth_credentials
7
+ from intuned_cli.utils.error import CLIError
8
+
9
+
10
+ @arguably.command # type: ignore
11
+ async def save(
12
+ project_name: str | None = None,
13
+ /,
14
+ *,
15
+ workspace_id: str | None = None,
16
+ api_key: str | None = None,
17
+ ):
18
+ """Saves the project to Intuned without deploying it.
19
+
20
+ Args:
21
+ project_name (str | None, optional): The name of the project to save.
22
+ workspace_id (str | None, optional): The ID of the workspace to save to.
23
+ api_key (str | None, optional): The API key to use for authentication.
24
+ """
25
+ try:
26
+ intuned_json = await validate_intuned_project()
27
+ except CLIError as e:
28
+ raise CLIError(
29
+ f"[bold red]Project to be saved is not a valid Intuned project:[/bold red][bright_red] {e}[/bright_red]\n",
30
+ auto_color=False,
31
+ ) from e
32
+
33
+ project_name = project_name or intuned_json.project_name
34
+ if not project_name:
35
+ raise CLIError("Project name is required")
36
+
37
+ validate_project_name(project_name)
38
+
39
+ workspace_id, api_key = await get_intuned_api_auth_credentials(
40
+ intuned_json=intuned_json, workspace_id=workspace_id, api_key=api_key
41
+ )
42
+
43
+ await save_project(
44
+ project_name=project_name,
45
+ workspace_id=workspace_id,
46
+ api_key=api_key,
47
+ )
@@ -187,7 +187,6 @@ class TestAttemptApi:
187
187
  assert call_args.run_options.headless is False
188
188
  assert call_args.run_options.proxy == proxy_config
189
189
  assert call_args.auth.session.state is auth
190
- assert call_args.auth.run_check is False
191
190
 
192
191
  @pytest.mark.asyncio
193
192
  async def test_attempt_api_returns_result_and_extended_payloads_if_run_api_succeeds(
@@ -197,7 +197,6 @@ async def attempt_api(
197
197
  automation_function=AutomationFunction(name=f"api/{api_name}", params=parameters),
198
198
  auth=Auth(
199
199
  session=StateSession(state=auth),
200
- run_check=False,
201
200
  )
202
201
  if auth
203
202
  else None,
@@ -276,7 +276,6 @@ async def run_check(
276
276
  session=StateSession(
277
277
  state=auth,
278
278
  ),
279
- run_check=False,
280
279
  ),
281
280
  ),
282
281
  import_function=await get_cli_import_function(),
@@ -0,0 +1,150 @@
1
+ import asyncio
2
+ import time
3
+ from itertools import cycle
4
+ from typing import Literal
5
+
6
+ import httpx
7
+ from pydantic import BaseModel
8
+
9
+ from intuned_cli.controller.save import save_project
10
+ from intuned_cli.utils.backend import get_base_url
11
+ from intuned_cli.utils.console import console
12
+ from intuned_cli.utils.error import CLIError
13
+
14
+ supported_playwright_versions = ["1.46.0", "1.52.0"]
15
+
16
+ project_deploy_timeout = 10 * 60
17
+ project_deploy_check_period = 5
18
+
19
+
20
+ class DeployStatus(BaseModel):
21
+ status: Literal["completed", "failed", "pending"]
22
+ message: str | None = None
23
+ reason: str | None = None
24
+
25
+
26
+ async def check_deploy_status(
27
+ *,
28
+ project_name: str,
29
+ workspace_id: str,
30
+ api_key: str,
31
+ ):
32
+ base_url = get_base_url()
33
+ url = f"{base_url}/api/v1/workspace/{workspace_id}/projects/create/{project_name}/result"
34
+
35
+ headers = {
36
+ "x-api-key": api_key,
37
+ "Content-Type": "application/json",
38
+ }
39
+ async with httpx.AsyncClient() as client:
40
+ response = await client.get(url, headers=headers)
41
+ if response.status_code < 200 or response.status_code >= 300:
42
+ if response.status_code == 401:
43
+ raise CLIError("Invalid API key. Please check your API key and try again.")
44
+ if response.status_code == 404:
45
+ raise CLIError(f"Project '{project_name}' not found in workspace '{workspace_id}'.")
46
+ raise CLIError(f"Failed to check deploy status for project '{project_name}': {response.text}")
47
+
48
+ data = response.json()
49
+ try:
50
+ deploy_status = DeployStatus.model_validate(data)
51
+ except Exception as e:
52
+ raise CLIError(f"Failed to parse deploy status response: {e}") from e
53
+
54
+ return deploy_status
55
+
56
+
57
+ async def deploy_project(
58
+ *,
59
+ project_name: str,
60
+ workspace_id: str,
61
+ api_key: str,
62
+ ):
63
+ await save_project(
64
+ project_name=project_name,
65
+ workspace_id=workspace_id,
66
+ api_key=api_key,
67
+ )
68
+ base_url = get_base_url()
69
+ url = f"{base_url}/api/v1/workspace/{workspace_id}/projects/{project_name}/deploy"
70
+ headers = {
71
+ "x-api-key": api_key,
72
+ "Content-Type": "application/json",
73
+ }
74
+
75
+ async with httpx.AsyncClient() as client:
76
+ response = await client.post(url, headers=headers)
77
+ if response.status_code < 200 or response.status_code >= 300:
78
+ if response.status_code == 401:
79
+ raise CLIError("Invalid API key. Please check your API key and try again.")
80
+
81
+ raise CLIError(
82
+ f"[red bold]Invalid response from server:[/red bold]\n [bright_red]{response.status_code} {response.text}[/bright_red][red bold]\nProject deployment failed.[/red bold]"
83
+ )
84
+
85
+ start_time = time.time()
86
+
87
+ async def update_console():
88
+ for spinner in cycle("⠙⠹⠸⠼⠴⠦⠧⠇"):
89
+ await asyncio.sleep(0.05)
90
+
91
+ time_elapsed_text = f"{time.time() - start_time:.1f}"
92
+ print("\r", end="", flush=True)
93
+ console.print(
94
+ f"{spinner} [cyan]Deploying[/cyan] [bright_black]({time_elapsed_text}s)[/bright_black] ", end=""
95
+ )
96
+
97
+ if console.is_terminal:
98
+ update_console_task = asyncio.create_task(update_console())
99
+ else:
100
+ update_console_task = None
101
+ console.print("[cyan]Deploying[/cyan]")
102
+
103
+ try:
104
+ while True:
105
+ await asyncio.sleep(project_deploy_check_period)
106
+ if not console.is_terminal:
107
+ time_elapsed_text = f"{time.time() - start_time:.1f}"
108
+ console.print(f"[cyan]Deploying[/cyan] [bright_black]({time_elapsed_text}s)[/bright_black]")
109
+
110
+ try:
111
+ deploy_status = await check_deploy_status(
112
+ project_name=project_name,
113
+ workspace_id=workspace_id,
114
+ api_key=api_key,
115
+ )
116
+
117
+ if deploy_status.status == "pending":
118
+ elapsed_time = time.time() - start_time
119
+ if elapsed_time > project_deploy_timeout:
120
+ raise CLIError(f"Deployment timed out after {project_deploy_timeout//60} minutes.")
121
+ continue
122
+
123
+ if deploy_status.status == "completed":
124
+ if update_console_task:
125
+ update_console_task.cancel()
126
+ if console.is_terminal:
127
+ print("\r", " " * 100)
128
+ console.print("[green][bold]Project deployed successfully![/bold][/green]")
129
+ console.print(
130
+ f"[bold]You can check your project on the platform:[/bold] [cyan underline]{get_base_url()}/projects/{project_name}/details[/cyan underline]"
131
+ )
132
+ return
133
+
134
+ error_message = (
135
+ f"[red bold]Project deployment failed:[/bold red]\n{deploy_status.message or 'Unknown error'}\n"
136
+ )
137
+ if deploy_status.reason:
138
+ error_message += f"Reason: {deploy_status.reason}\n"
139
+ error_message += "[red bold]Project deployment failed[/red bold]"
140
+ raise CLIError(
141
+ error_message,
142
+ auto_color=False,
143
+ )
144
+ except Exception:
145
+ if console.is_terminal:
146
+ print("\r", " " * 100)
147
+ raise
148
+ finally:
149
+ if update_console_task:
150
+ update_console_task.cancel()
@@ -1,35 +1,33 @@
1
- import asyncio
1
+ import io
2
2
  import json
3
- import os
4
3
  import re
5
- import time
6
4
  import uuid
7
- from itertools import cycle
8
5
  from typing import Any
9
- from typing import Literal
10
6
 
11
7
  import httpx
12
8
  import pathspec
13
9
  import toml
14
10
  from anyio import Path
11
+ from dotenv.main import DotEnv
15
12
  from pydantic import BaseModel
13
+ from pydantic import ValidationError
16
14
 
17
15
  from intuned_cli.types import DirectoryNode
18
16
  from intuned_cli.types import FileNode
19
17
  from intuned_cli.types import FileNodeContent
20
18
  from intuned_cli.types import FileSystemTree
21
- from intuned_cli.types import IntunedJson
22
19
  from intuned_cli.utils.api_helpers import load_intuned_json
23
20
  from intuned_cli.utils.backend import get_base_url
24
21
  from intuned_cli.utils.console import console
25
22
  from intuned_cli.utils.error import CLIError
26
23
  from intuned_cli.utils.exclusions import exclusions
24
+ from runtime.constants import api_key_header_name
25
+ from runtime.env import api_key_env_var_key
26
+ from runtime.env import project_env_var_key
27
+ from runtime.env import workspace_env_var_key
27
28
 
28
29
  supported_playwright_versions = ["1.46.0", "1.52.0"]
29
30
 
30
- project_deploy_timeout = 10 * 60
31
- project_deploy_check_period = 5
32
-
33
31
 
34
32
  class IntunedPyprojectToml(BaseModel):
35
33
  class _Tool(BaseModel):
@@ -96,29 +94,6 @@ def validate_project_name(project_name: str):
96
94
  pass
97
95
 
98
96
 
99
- async def get_intuned_api_auth_credentials(
100
- *, intuned_json: IntunedJson, workspace_id: str | None, api_key: str | None
101
- ) -> tuple[str, str]:
102
- """
103
- Retrieves the Intuned API authentication credentials from environment variables.
104
-
105
- Returns:
106
- tuple: A tuple containing the workspace ID and API key.
107
- """
108
- workspace_id = workspace_id or intuned_json.workspace_id
109
- api_key = api_key or os.environ.get("INTUNED_API_KEY")
110
-
111
- if not workspace_id:
112
- raise CLIError("Workspace ID is required. Please provide it via command line options or Intuned.json")
113
-
114
- if not api_key:
115
- raise CLIError(
116
- "API key is required. Please provide it via command line options or INTUNED_API_KEY environment variable."
117
- )
118
-
119
- return workspace_id, api_key
120
-
121
-
122
97
  async def get_file_tree_from_project(path: Path, *, exclude: list[str] | None = None):
123
98
  # Create pathspec object for gitignore-style pattern matching
124
99
  spec = None
@@ -214,53 +189,21 @@ def mapFileTreeToIdeFileTree(file_tree: FileSystemTree):
214
189
  file_tree.root["____testParameters"] = test_parameters
215
190
 
216
191
 
217
- class DeployStatus(BaseModel):
218
- status: Literal["completed", "failed", "pending"]
219
- message: str | None = None
220
- reason: str | None = None
221
-
222
-
223
- async def check_deploy_status(
224
- *,
225
- project_name: str,
226
- workspace_id: str,
227
- api_key: str,
228
- ):
229
- base_url = get_base_url()
230
- url = f"{base_url}/api/v1/workspace/{workspace_id}/projects/create/{project_name}/result"
231
-
232
- headers = {
233
- "x-api-key": api_key,
234
- "Content-Type": "application/json",
235
- }
236
- async with httpx.AsyncClient() as client:
237
- response = await client.get(url, headers=headers)
238
- if response.status_code < 200 or response.status_code >= 300:
239
- if response.status_code == 401:
240
- raise CLIError("Invalid API key. Please check your API key and try again.")
241
- if response.status_code == 404:
242
- raise CLIError(f"Project '{project_name}' not found in workspace '{workspace_id}'.")
243
- raise CLIError(f"Failed to check deploy status for project '{project_name}': {response.text}")
244
-
245
- data = response.json()
246
- try:
247
- deploy_status = DeployStatus.model_validate(data)
248
- except Exception as e:
249
- raise CLIError(f"Failed to parse deploy status response: {e}") from e
250
-
251
- return deploy_status
192
+ class SaveProjectResponse(BaseModel):
193
+ id: str
252
194
 
253
195
 
254
- async def deploy_project(
196
+ async def save_project(
255
197
  *,
256
198
  project_name: str,
257
199
  workspace_id: str,
258
200
  api_key: str,
259
201
  ):
260
202
  base_url = get_base_url()
261
- url = f"{base_url}/api/v1/workspace/{workspace_id}/projects/create"
203
+ url = f"{base_url}/api/v1/workspace/{workspace_id}/projects/{project_name}"
204
+ print(f"calling {url}")
262
205
  headers = {
263
- "x-api-key": api_key,
206
+ api_key_header_name: api_key,
264
207
  "Content-Type": "application/json",
265
208
  }
266
209
  cwd = await Path().resolve()
@@ -268,85 +211,50 @@ async def deploy_project(
268
211
  mapFileTreeToIdeFileTree(file_tree)
269
212
 
270
213
  payload: dict[str, Any] = {
271
- "name": project_name,
272
214
  "codeTree": file_tree.model_dump(mode="json"),
273
- "isCli": True,
215
+ "platformType": "CLI",
274
216
  "language": "python",
275
217
  }
276
218
 
277
219
  async with httpx.AsyncClient() as client:
278
- response = await client.post(url, headers=headers, json=payload)
220
+ response = await client.put(url, headers=headers, json=payload)
279
221
  if response.status_code < 200 or response.status_code >= 300:
280
222
  if response.status_code == 401:
281
223
  raise CLIError("Invalid API key. Please check your API key and try again.")
282
224
 
283
225
  raise CLIError(
284
- f"[red bold]Invalid response from server:[/red bold]\n [bright_red]{response.status_code} {response.text}[/bright_red][red bold]\nProject deployment failed.[/red bold]"
226
+ f"[red bold]Invalid response from server:[/red bold]\n [bright_red]{response.status_code} {response.text}[/bright_red][red bold]\nProject save failed.[/red bold]"
285
227
  )
286
228
 
287
- start_time = time.time()
288
-
289
- async def update_console():
290
- for spinner in cycle("⠙⠹⠸⠼⠴⠦⠧⠇"):
291
- await asyncio.sleep(0.05)
292
-
293
- time_elapsed_text = f"{time.time() - start_time:.1f}"
294
- print("\r", end="", flush=True)
295
- console.print(
296
- f"{spinner} [cyan]Deploying[/cyan] [bright_black]({time_elapsed_text}s)[/bright_black] ", end=""
297
- )
229
+ console.print("[green]Project saved successfully.[/green]")
230
+ try:
231
+ response = SaveProjectResponse.model_validate(response.json())
232
+ except ValidationError:
233
+ console.print(f"[yellow]Could not parse response:[/yellow]\n {response.text}")
234
+ return
298
235
 
299
- if console.is_terminal:
300
- update_console_task = asyncio.create_task(update_console())
301
- else:
302
- update_console_task = None
303
- console.print("[cyan]Deploying[/cyan]")
236
+ dotenv_path = cwd / ".env"
237
+ if not await dotenv_path.exists():
238
+ content_to_write = f"""{workspace_env_var_key}={workspace_id}
239
+ {project_env_var_key}={response.id}
240
+ {api_key_env_var_key}={api_key}
241
+ """
242
+ await dotenv_path.write_text(content_to_write)
243
+ console.print("[green]Created .env with project credentials.[/green]")
244
+ return
304
245
 
305
- try:
306
- while True:
307
- await asyncio.sleep(project_deploy_check_period)
308
- if not console.is_terminal:
309
- time_elapsed_text = f"{time.time() - start_time:.1f}"
310
- console.print(f"[cyan]Deploying[/cyan] [bright_black]({time_elapsed_text}s)[/bright_black]")
311
-
312
- try:
313
- deploy_status = await check_deploy_status(
314
- project_name=project_name,
315
- workspace_id=workspace_id,
316
- api_key=api_key,
317
- )
318
-
319
- if deploy_status.status == "pending":
320
- elapsed_time = time.time() - start_time
321
- if elapsed_time > project_deploy_timeout:
322
- raise CLIError(f"Deployment timed out after {project_deploy_timeout//60} minutes.")
323
- continue
324
-
325
- if deploy_status.status == "completed":
326
- if update_console_task:
327
- update_console_task.cancel()
328
- if console.is_terminal:
329
- print("\r", " " * 100)
330
- console.print("[green][bold]Project deployed successfully![/bold][/green]")
331
- console.print(
332
- f"[bold]You can check your project on the platform:[/bold] [cyan underline]{get_base_url()}/projects/{project_name}/details[/cyan underline]"
333
- )
334
- return
335
-
336
- error_message = (
337
- f"[red bold]Project deployment failed:[/bold red]\n{deploy_status.message or 'Unknown error'}\n"
338
- )
339
- if deploy_status.reason:
340
- error_message += f"Reason: {deploy_status.reason}\n"
341
- error_message += "[red bold]Project deployment failed[/red bold]"
342
- raise CLIError(
343
- error_message,
344
- auto_color=False,
345
- )
346
- except Exception:
347
- if console.is_terminal:
348
- print("\r", " " * 100)
349
- raise
350
- finally:
351
- if update_console_task:
352
- update_console_task.cancel()
246
+ dotenv_content = await dotenv_path.read_text()
247
+ dotenv = DotEnv(
248
+ dotenv_path=None,
249
+ stream=io.StringIO(dotenv_content),
250
+ ).dict()
251
+ content_to_append = ""
252
+ if dotenv.get(project_env_var_key) is None or dotenv.get(project_env_var_key) != response.id:
253
+ content_to_append += f"{project_env_var_key}={response.id}"
254
+ if dotenv.get(workspace_env_var_key) is None:
255
+ content_to_append += f"\n{workspace_env_var_key}={workspace_id}"
256
+ if dotenv.get(api_key_env_var_key) is None:
257
+ content_to_append += f"\n{api_key_env_var_key}={api_key}"
258
+
259
+ await dotenv_path.write_text(f"{dotenv_content}\n{content_to_append}\n")
260
+ console.print("[green]Updated .env with project credentials.[/green]")