intuned-runtime 1.1.2__py3-none-any.whl → 1.2.0__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.
- intuned_cli/commands/init_command.py +11 -11
- intuned_cli/utils/backend.py +1 -1
- intuned_internal_cli/__init__.py +2 -0
- intuned_internal_cli/utils/setup_ide_functions_token.py +10 -0
- intuned_runtime/__init__.py +5 -0
- {intuned_runtime-1.1.2.dist-info → intuned_runtime-1.2.0.dist-info}/METADATA +1 -1
- {intuned_runtime-1.1.2.dist-info → intuned_runtime-1.2.0.dist-info}/RECORD +13 -14
- {intuned_runtime-1.1.2.dist-info → intuned_runtime-1.2.0.dist-info}/WHEEL +1 -1
- runtime/backend_functions/_call_backend_function.py +0 -3
- runtime/errors/run_api_errors.py +16 -0
- runtime/run/run_api.py +22 -1
- intuned_cli/constants/__init__.py +0 -1
- intuned_cli/constants/readme.py +0 -134
- intuned_cli/controller/init.py +0 -97
- {intuned_runtime-1.1.2.dist-info → intuned_runtime-1.2.0.dist-info}/LICENSE +0 -0
- {intuned_runtime-1.1.2.dist-info → intuned_runtime-1.2.0.dist-info}/entry_points.txt +0 -0
@@ -1,21 +1,21 @@
|
|
1
|
+
from typing import Any
|
2
|
+
|
1
3
|
import arguably
|
2
4
|
|
3
|
-
from intuned_cli.
|
4
|
-
from intuned_cli.controller.init import python_template_name
|
5
|
-
from intuned_cli.controller.init import write_project_from_file_tree
|
5
|
+
from intuned_cli.utils.error import CLIError
|
6
6
|
|
7
7
|
|
8
8
|
@arguably.command # type: ignore
|
9
|
-
async def init(
|
9
|
+
async def init(
|
10
|
+
*args: Any,
|
11
|
+
):
|
10
12
|
"""
|
11
|
-
|
12
|
-
|
13
|
-
Args:
|
14
|
-
template_name (str | None): Name of the template to use.
|
13
|
+
Deprecated: Initialize a new Intuned project in the current directory.
|
15
14
|
|
16
15
|
Returns:
|
17
16
|
None
|
18
17
|
"""
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
raise CLIError(
|
19
|
+
"[red bold]The init command has been deprecated. Please use[/red bold] [cyan italic]npx create-intuned-project[/cyan italic] [red bold]instead.[/red bold]",
|
20
|
+
auto_color=False,
|
21
|
+
)
|
intuned_cli/utils/backend.py
CHANGED
intuned_internal_cli/__init__.py
CHANGED
@@ -7,6 +7,7 @@ from dotenv import load_dotenv
|
|
7
7
|
from more_termcolor import bold # type: ignore
|
8
8
|
from more_termcolor import red # type: ignore
|
9
9
|
|
10
|
+
from intuned_internal_cli.utils.setup_ide_functions_token import setup_ide_functions_token
|
10
11
|
from runtime.context.context import IntunedContext
|
11
12
|
|
12
13
|
from . import commands
|
@@ -18,6 +19,7 @@ def run():
|
|
18
19
|
load_dotenv(dotenv, override=True)
|
19
20
|
try:
|
20
21
|
with IntunedContext():
|
22
|
+
setup_ide_functions_token()
|
21
23
|
arguably.run(name="intuned-internal")
|
22
24
|
except ValueError as e:
|
23
25
|
print(bold(red(str(e))))
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import os
|
2
|
+
|
3
|
+
from runtime.context.context import IntunedContext
|
4
|
+
|
5
|
+
|
6
|
+
def setup_ide_functions_token():
|
7
|
+
ide_functions_token = os.getenv("INTUNED_AUTHORING_SESSION_BACKEND_FUNCTIONS_TOKEN")
|
8
|
+
if ide_functions_token is None:
|
9
|
+
return
|
10
|
+
IntunedContext.current().functions_token = ide_functions_token
|
@@ -7,26 +7,23 @@ intuned_cli/commands/attempt_authsession_create_command.py,sha256=ynAKlNoHuaVPgx
|
|
7
7
|
intuned_cli/commands/attempt_command.py,sha256=7NZC2dXA8GViCH3ZS2PMziR43hv2RmS6-jgW7l9bu40,253
|
8
8
|
intuned_cli/commands/command.py,sha256=b0OlQIFhoCjCo5QIerfysccBKcU9iIsvqiU7oxshA2M,727
|
9
9
|
intuned_cli/commands/deploy_command.py,sha256=0XG8lo5fGeorZMxMtEx12EaqLFMvmFYJ7Zu9sgJCmQ8,1540
|
10
|
-
intuned_cli/commands/init_command.py,sha256=
|
10
|
+
intuned_cli/commands/init_command.py,sha256=JIDr9vY_SRKCvVbXE_lCEeHQlmC48LMZz0cghMPAs90,492
|
11
11
|
intuned_cli/commands/run_api_command.py,sha256=Y6FkSFI9wybuoW204YqOAiQ794__hIw12Eaf9px_ovE,3013
|
12
12
|
intuned_cli/commands/run_authsession_command.py,sha256=kM_TANy8M3yx8iBUsgSDO42byzccikLOd9KJfytfLmQ,269
|
13
13
|
intuned_cli/commands/run_authsession_create_command.py,sha256=SiuIZ7LTMqT3gnd8DUt-VmP3cyuA0I_i27daI3IJ2tw,2089
|
14
14
|
intuned_cli/commands/run_authsession_update_command.py,sha256=Op7epDgvEhjp17qs2dIXerTckHz7wlOsZsBDp8UJLjI,2145
|
15
15
|
intuned_cli/commands/run_authsession_validate_command.py,sha256=5eET02paijq1f1rzsheINvAqVYj1JGUA_iGD5Py3LVM,2008
|
16
16
|
intuned_cli/commands/run_command.py,sha256=JN1yCewcyb4zFquMcv0wEZ_aRmhJZIBMheY8L9yBeDE,245
|
17
|
-
intuned_cli/constants/__init__.py,sha256=AIfIQYQ55zSdGKL2p15wv4H9ANeRjYEkeUiObu6JFt8,37
|
18
|
-
intuned_cli/constants/readme.py,sha256=3OUkZNmz9kss7hA3V2j5h49dv7ADy4LON5-iJkrJUA4,5240
|
19
17
|
intuned_cli/controller/__test__/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
18
|
intuned_cli/controller/__test__/test_api.py,sha256=8Hk3V52qKhWIqjirKFwN68Tv5er70gTxemjI35HyZiU,19295
|
21
19
|
intuned_cli/controller/__test__/test_authsession.py,sha256=y7O3dkDPHZTf2W7WRLEhysYYRati0hg93-J8BvGjRUM,35110
|
22
20
|
intuned_cli/controller/api.py,sha256=1wHgatavA5zr6kYMf1BJ3lC5JR1IvlVBiab_ahOSVnY,7172
|
23
21
|
intuned_cli/controller/authsession.py,sha256=z8C97T-beEORNemLFx2x_JsHr2gXRakwKGV3Rgivnu8,14309
|
24
22
|
intuned_cli/controller/deploy.py,sha256=5cQgtpT2nDQ5XSUnIoeBR9xdyVoLWcY-2btuUxUD7Es,12343
|
25
|
-
intuned_cli/controller/init.py,sha256=TfkPkq9rbYam2-fchwrpMIt9GJIkY-TQnWpwvPomRZE,3774
|
26
23
|
intuned_cli/types.py,sha256=Lsykui4mbq5T1_1L7Gg5OZpJvCOmaz9EiQcZVfUEoLc,766
|
27
24
|
intuned_cli/utils/api_helpers.py,sha256=57gvXVYM_9hMGIkMqSEN_jzs3mYWlXzinPjYXzzqgg8,1122
|
28
25
|
intuned_cli/utils/auth_session_helpers.py,sha256=acKAPUjOfdybstLur4Lk3huaLFP2Ipl4AjPSqQPQLzY,1899
|
29
|
-
intuned_cli/utils/backend.py,sha256=
|
26
|
+
intuned_cli/utils/backend.py,sha256=9HmaIvyKRkybUL-lEiJYHbIclinEZEBNBFLUG0hNC0A,150
|
30
27
|
intuned_cli/utils/confirmation.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
31
28
|
intuned_cli/utils/console.py,sha256=jgin2xB-0kpANPMoiDBOnWhrc8Ey5zT4IHRxQa8f5CQ,93
|
32
29
|
intuned_cli/utils/error.py,sha256=DmNlXBb7zfpHDRya00ouEcRgUEZuGaTqIy1SIjDWVfo,842
|
@@ -34,7 +31,7 @@ intuned_cli/utils/exclusions.py,sha256=Qe7NkWA3lsX73dOC9WprdD0KyYc_vuiwkrUCwD105
|
|
34
31
|
intuned_cli/utils/get_auth_parameters.py,sha256=HmMSjBE8bPulkUdX319Ipr383Ko2Gtz3y8_WT9CK3Kw,798
|
35
32
|
intuned_cli/utils/import_function.py,sha256=UmE2yw5std1ENMFdBU-TUBuQ01qsv7Qr5ElnAhqE6Yc,453
|
36
33
|
intuned_cli/utils/timeout.py,sha256=DkoeoU9XvKKKSQ06CpwqcNvxWqLPAOVuAMw6kSr4Tuo,886
|
37
|
-
intuned_internal_cli/__init__.py,sha256=
|
34
|
+
intuned_internal_cli/__init__.py,sha256=MLkUSYcMp4qMvCHeRgsPDzUlz10XwiAHAsgoNvzl6MA,1468
|
38
35
|
intuned_internal_cli/commands/__init__.py,sha256=gZ-r8UIXvRakeCfvmOZCNWr8CJHBcYzRzbMjyKYPXV0,1003
|
39
36
|
intuned_internal_cli/commands/ai_source/__init__.py,sha256=lg7owgcK8owNn2a4VBUP9RKxzFsLruhtnnQV0F_z6pc,149
|
40
37
|
intuned_internal_cli/commands/ai_source/ai_source.py,sha256=2woQtCmhxKvLfEz832eUoCT9gMsuSvEE6rMnHSYXC7w,138
|
@@ -58,10 +55,12 @@ intuned_internal_cli/logger.py,sha256=bZK3q-KUdGxk_qzDb6pn-n0LOhKJvi6a9p8oSwZtq3
|
|
58
55
|
intuned_internal_cli/utils/ai_source_project.py,sha256=xUCM6p3i1XN4bJbuQz8LCzeI4BwqAdSvCl_vwDAEi0k,831
|
59
56
|
intuned_internal_cli/utils/code_tree.py,sha256=1wfxZoQ5kRCfqs2SEPAicbAIPTiD6P1LxSuwYu_eeaI,2790
|
60
57
|
intuned_internal_cli/utils/run_apis.py,sha256=Zee4zkgt9R8XY1XCGzj2Nc4zJ3jlRz1xnO493wotuWw,4690
|
58
|
+
intuned_internal_cli/utils/setup_ide_functions_token.py,sha256=72-hf5HOPO9hj_eo3MTSVEPIwtkaIma_NRepsw_FHQM,304
|
61
59
|
intuned_internal_cli/utils/unix_socket.py,sha256=UISmkJMHrir5iBLUm6vxC3uzTGIFyOk_wa0C9LUw4Cc,1889
|
60
|
+
intuned_runtime/__init__.py,sha256=XBrEiE9yNC8Lgn8NgIkqNXbI6e4ap237E83Zj_nlhCQ,249
|
62
61
|
runtime/__init__.py,sha256=87gDXuxUv_kGzQfuB1mh6DF-dDysJN8r684c7jGnHxc,144
|
63
62
|
runtime/backend_functions/__init__.py,sha256=j2EaK4FK8bmdFtqc5FxtFwx1KhIn_7qKPChrrAhJI3s,119
|
64
|
-
runtime/backend_functions/_call_backend_function.py,sha256=
|
63
|
+
runtime/backend_functions/_call_backend_function.py,sha256=h6UUsQ2pLUTbEQNYM1TLdesc5yWWaUrPUaTbCtCIxlU,2769
|
65
64
|
runtime/backend_functions/get_auth_session_parameters.py,sha256=pOvB7XiWpphEuBpazdKALw9EWgBU1PeY3gkzBfVLpkc,869
|
66
65
|
runtime/browser/__init__.py,sha256=EPWfa4ZmdR8GJqh2qcsx1ZvHmCYiUYrQ-zeHYVapH9s,285
|
67
66
|
runtime/browser/helpers.py,sha256=b1Xp005adbl7ZeJrSEYgH2OPzrZEHxTPgGTsnS3OqS0,554
|
@@ -74,7 +73,7 @@ runtime/context/context.py,sha256=pl_0x77_d5CiAznz1qGSk6o9cW-msNvlCt-2eFoMKlA,17
|
|
74
73
|
runtime/env.py,sha256=OXxzLpM56AJVlX0gmG7Ph82xAfqZboW3kv2232lzXb4,306
|
75
74
|
runtime/errors/__init__.py,sha256=oqiBSvT_yFLQ3hG0AbCUA3WYFaxkTDVkDMSy59xvBCo,688
|
76
75
|
runtime/errors/auth_session_errors.py,sha256=6b4XTI8UCDHDPX4jEA8_HyrNUp4VZ1TrEA8DRh6Z3rM,228
|
77
|
-
runtime/errors/run_api_errors.py,sha256=
|
76
|
+
runtime/errors/run_api_errors.py,sha256=Bjs_gsgdIUbVLN67E_s-804qB2lyzIljDEaAcymgwK0,3906
|
78
77
|
runtime/errors/trace_errors.py,sha256=Lzfo0sH3zGaWz1kn5DHcAXQMn3aR2y2bnauj6xP1LYE,110
|
79
78
|
runtime/helpers/__init__.py,sha256=jozYPKHgZJ7Na5U1Wjt83egzjPATMZ_OMInEI6swSbY,234
|
80
79
|
runtime/helpers/extend_payload.py,sha256=towZF08WTpTTDBL4AV1bUU3XpKAQHEB66kGUfTICDe0,246
|
@@ -85,15 +84,15 @@ runtime/run/__init__.py,sha256=zxMYVb7hn147YTrhMLsrcX6-KTd71HLrYHstJOWeWXQ,52
|
|
85
84
|
runtime/run/intuned_settings.py,sha256=vy2-ktEzUfUp5Z90dp3l7jPKHNjgB-8GSMDgAY-rYaU,1074
|
86
85
|
runtime/run/playwright_constructs.py,sha256=EIfnRlAi1k1wRiTT2OpPFVmrWwEeeiryuORibaLD1Xw,573
|
87
86
|
runtime/run/pydantic_encoder.py,sha256=wJCljwwINSICvCJ0i2izp2RLkQ15nYglUQCyyjM40Jk,332
|
88
|
-
runtime/run/run_api.py,sha256=
|
87
|
+
runtime/run/run_api.py,sha256=JiO-UBpCBP7H4TZbKXmYmGZmYrkqidqEg7epaxj_3jA,10105
|
89
88
|
runtime/run/traces.py,sha256=fKzh11LqV47ujgq_9I2tdp-dgld566wffWaHwU_4gis,1123
|
90
89
|
runtime/types/__init__.py,sha256=IJkDfqsau8F8R_j8TO6j-JwW4ElQr6aU6LNaWRehg5U,401
|
91
90
|
runtime/types/payload.py,sha256=sty8HgDEn3nJbZrwEOMCXyuG7_ICGDwlBIIWSON5ABY,124
|
92
91
|
runtime/types/run_types.py,sha256=-j-XKXfRkzlyoW-Doe0og2jbqMMQWjOTIUqRFEc8lHA,4582
|
93
92
|
runtime_helpers/__init__.py,sha256=XBrEiE9yNC8Lgn8NgIkqNXbI6e4ap237E83Zj_nlhCQ,249
|
94
93
|
runtime_helpers/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
95
|
-
intuned_runtime-1.
|
96
|
-
intuned_runtime-1.
|
97
|
-
intuned_runtime-1.
|
98
|
-
intuned_runtime-1.
|
99
|
-
intuned_runtime-1.
|
94
|
+
intuned_runtime-1.2.0.dist-info/LICENSE,sha256=9LIjQdgyU_ptzNIfItNCR7VmEHqYnrY1f1XwOreKFI0,3714
|
95
|
+
intuned_runtime-1.2.0.dist-info/METADATA,sha256=-aElUUcnS5RP8i05pbZ6fe2JEINpwjTXWxnAjhv6DzI,5299
|
96
|
+
intuned_runtime-1.2.0.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
97
|
+
intuned_runtime-1.2.0.dist-info/entry_points.txt,sha256=ToMS2cqDeRmF1FGkflwoeD-Xz6jJV5p1zIbw9G7IxMg,85
|
98
|
+
intuned_runtime-1.2.0.dist-info/RECORD,,
|
@@ -28,9 +28,6 @@ async def call_backend_function[T: BaseModel](
|
|
28
28
|
raise Exception("No workspace ID or project ID found.")
|
29
29
|
|
30
30
|
context = IntunedContext.current()
|
31
|
-
if context.run_context is None:
|
32
|
-
# todo
|
33
|
-
raise Exception("No run context found.")
|
34
31
|
|
35
32
|
async with AsyncClient() as client:
|
36
33
|
if context.functions_token:
|
runtime/errors/run_api_errors.py
CHANGED
@@ -14,6 +14,7 @@ RunErrorCode = Literal[
|
|
14
14
|
"MaxLevelsExceededError",
|
15
15
|
"AutomationError",
|
16
16
|
"InternalInvalidInputError",
|
17
|
+
"ResultTooBigError",
|
17
18
|
]
|
18
19
|
|
19
20
|
|
@@ -122,3 +123,18 @@ class InternalInvalidInputError(RunApiError):
|
|
122
123
|
"InternalInvalidInputError",
|
123
124
|
)
|
124
125
|
self.details = details
|
126
|
+
|
127
|
+
|
128
|
+
class ResultTooBigError(RunApiError):
|
129
|
+
def __init__(self, size_in_bytes: int, max_size_in_bytes: int):
|
130
|
+
size_mb = round((size_in_bytes / 1024 / 1024) * 100) / 100
|
131
|
+
max_size_mb = round((max_size_in_bytes / 1024 / 1024) * 100) / 100
|
132
|
+
|
133
|
+
super().__init__(
|
134
|
+
f"Automation result is too big. Size: {size_mb}MB, Max allowed: {max_size_mb}MB",
|
135
|
+
"ResultTooBigError",
|
136
|
+
)
|
137
|
+
self.details = {
|
138
|
+
"sizeInBytes": size_in_bytes,
|
139
|
+
"maxSizeInBytes": max_size_in_bytes,
|
140
|
+
}
|
runtime/run/run_api.py
CHANGED
@@ -21,6 +21,7 @@ from runtime.browser.storage_state import get_storage_state
|
|
21
21
|
from runtime.browser.storage_state import set_storage_state
|
22
22
|
from runtime.context import IntunedContext
|
23
23
|
from runtime.errors.run_api_errors import InvalidSessionError
|
24
|
+
from runtime.errors.run_api_errors import ResultTooBigError
|
24
25
|
from runtime.errors.run_api_errors import RunApiError
|
25
26
|
from runtime.types import RunAutomationSuccessResult
|
26
27
|
from runtime.types.run_types import PayloadToAppend
|
@@ -34,6 +35,15 @@ from .playwright_constructs import get_production_playwright_constructs
|
|
34
35
|
from .pydantic_encoder import PydanticEncoder
|
35
36
|
|
36
37
|
|
38
|
+
def get_object_size_in_bytes(obj: Any) -> int:
|
39
|
+
"""Calculate the approximate size of an object in bytes."""
|
40
|
+
try:
|
41
|
+
return len(json.dumps(obj, cls=PydanticEncoder).encode("utf-8"))
|
42
|
+
except (TypeError, ValueError):
|
43
|
+
# If JSON serialization fails, return a conservative estimate
|
44
|
+
return len(str(obj).encode("utf-8"))
|
45
|
+
|
46
|
+
|
37
47
|
def import_function_from_api_dir(
|
38
48
|
*,
|
39
49
|
file_path: str,
|
@@ -182,15 +192,26 @@ async def run_api(
|
|
182
192
|
else:
|
183
193
|
automation_result = await automation_coroutine_with_page(parameters.automation_function.params)
|
184
194
|
try:
|
185
|
-
json.dumps(automation_result, cls=PydanticEncoder)
|
195
|
+
automation_result = json.loads(json.dumps(automation_result, cls=PydanticEncoder))
|
186
196
|
except TypeError as e:
|
187
197
|
raise AutomationError(TypeError("Result is not JSON serializable")) from e
|
188
198
|
|
199
|
+
# Check if result size exceeds 2MB limit
|
200
|
+
MAX_RESULT_SIZE_BYTES = 2 * 1024 * 1024 # 2MB
|
201
|
+
result_size_in_bytes = get_object_size_in_bytes(automation_result)
|
202
|
+
if result_size_in_bytes > MAX_RESULT_SIZE_BYTES:
|
203
|
+
raise ResultTooBigError(result_size_in_bytes, MAX_RESULT_SIZE_BYTES)
|
204
|
+
|
189
205
|
response = RunAutomationSuccessResult(
|
190
206
|
result=automation_result,
|
191
207
|
)
|
192
208
|
extended_payloads = IntunedContext.current().extended_payloads
|
193
209
|
if extended_payloads:
|
210
|
+
for payload in extended_payloads:
|
211
|
+
try:
|
212
|
+
payload["parameters"] = json.loads(json.dumps(payload["parameters"], cls=PydanticEncoder))
|
213
|
+
except TypeError as e:
|
214
|
+
raise AutomationError(TypeError("Parameters are not JSON serializable")) from e
|
194
215
|
response.payload_to_append = [
|
195
216
|
PayloadToAppend(
|
196
217
|
api_name=payload["api"],
|
@@ -1 +0,0 @@
|
|
1
|
-
from .readme import readme as readme
|
intuned_cli/constants/readme.py
DELETED
@@ -1,134 +0,0 @@
|
|
1
|
-
readme = r"""# Intuned CLI
|
2
|
-
|
3
|
-
## Development Commands
|
4
|
-
|
5
|
-
For each command, add `--help` to see more details and options.
|
6
|
-
|
7
|
-
### Initialize a Project
|
8
|
-
`pipx run --spec intuned-runtime intuned init`
|
9
|
-
|
10
|
-
Once you install the dependencies, you will have `intuned` command available in your environment.
|
11
|
-
|
12
|
-
### Run an API
|
13
|
-
`intuned run api <api-name> <parameters>`
|
14
|
-
|
15
|
-
|
16
|
-
### Deploy a Project
|
17
|
-
`intuned deploy [project-name]`
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
### Create an auth session
|
22
|
-
`intuned run authsession create <parameters>`
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
### Validate an auth session
|
27
|
-
`intuned run authsession validate <auth-session-name>`
|
28
|
-
|
29
|
-
## Configuration
|
30
|
-
|
31
|
-
### Environment Variables and Settings
|
32
|
-
- `workspaceId`: Your Intuned workspace ID ([How to get your workspaceId](https://docs.intunedhq.com/docs/guides/platform/how-to-get-a-workspace-id))
|
33
|
-
- Set in `intuned.json` file under the `workspaceId` property
|
34
|
-
- Or provide via CLI with `--workspace-id` flag during deployment
|
35
|
-
|
36
|
-
- `projectName`: The name of your Intuned project
|
37
|
-
- Set in `intuned.json` file under the `projectName` property
|
38
|
-
- Or override via command line when deploying with `yarn intuned deploy my-project-name` or `npm run intuned deploy my-project-name`
|
39
|
-
|
40
|
-
- `INTUNED_API_KEY`: Your Intuned API key
|
41
|
-
- Set as an environment variable: `export INTUNED_API_KEY=your_api_key_here`
|
42
|
-
- Or include in your .env file for development
|
43
|
-
- Or provide via CLI with `--api-key` flag during deployment
|
44
|
-
|
45
|
-
## Project Structure
|
46
|
-
|
47
|
-
### Generated Artifacts
|
48
|
-
- `./intuned.json`: Project configuration file
|
49
|
-
- `./api`: Folder containing API implementation files
|
50
|
-
- `./auth-sessions`: Folder containing auth-session APIs if you use them
|
51
|
-
- `./auth-sessions-instances`: Folder containing auth session instances
|
52
|
-
|
53
|
-
## Types of auth sessions
|
54
|
-
- `MANUAL`: Manual auth session, records the session using a recorder and stores it in the `auth-sessions-instances` folder
|
55
|
-
- `API`: Auth session created via create API, stores the session in the `auth-sessions-instances` folder
|
56
|
-
|
57
|
-
### Notes
|
58
|
-
- All commands should be run from the project root directory
|
59
|
-
- Verify you're in the correct location by confirming the presence of package.json and intuned.json
|
60
|
-
- Running commands from subdirectories may result in errors
|
61
|
-
- You can manage your deployed projects through the Intuned platform
|
62
|
-
|
63
|
-
## `Intuned.json` Reference
|
64
|
-
```jsonc
|
65
|
-
{
|
66
|
-
// Your Intuned workspace ID.
|
67
|
-
// Optional - If not provided here, it must be supplied via the \`--workspace-id\` flag during deployment.
|
68
|
-
"workspaceId": "your_workspace_id",
|
69
|
-
|
70
|
-
// The name of your Intuned project.
|
71
|
-
// Optional - If not provided here, it must be supplied via the command line when deploying.
|
72
|
-
"projectName": "your_project_name",
|
73
|
-
|
74
|
-
// Replication settings
|
75
|
-
"replication": {
|
76
|
-
// The maximum number of concurrent executions allowed via Intuned API. This does not affect jobs.
|
77
|
-
// A number of machines equal to this will be allocated to handle API requests.
|
78
|
-
// Not applicable if api access is disabled.
|
79
|
-
"maxConcurrentRequests": 1,
|
80
|
-
|
81
|
-
// The machine size to use for this project. This is applicable for both API requests and jobs.
|
82
|
-
// "standard": Standard machine size (6 shared vCPUs, 2GB RAM)
|
83
|
-
// "large": Large machine size (8 shared vCPUs, 4GB RAM)
|
84
|
-
// "xlarge": Extra large machine size (1 performance vCPU, 8GB RAM)
|
85
|
-
"size": "standard"
|
86
|
-
}
|
87
|
-
|
88
|
-
// Auth session settings
|
89
|
-
"authSessions": {
|
90
|
-
// Whether auth sessions are enabled for this project.
|
91
|
-
// If enabled, "auth-sessions/check.py" API must be implemented to validate the auth session.
|
92
|
-
"enabled": true,
|
93
|
-
|
94
|
-
// Whether to save Playwright traces for auth session runs.
|
95
|
-
"saveTraces": false,
|
96
|
-
|
97
|
-
// The type of auth session to use.
|
98
|
-
// "API" type requires implementing "auth-sessions/create.py" API to create/recreate the auth session programmatically.
|
99
|
-
// "MANUAL" type uses a recorder to manually create the auth session.
|
100
|
-
"type": "API",
|
101
|
-
|
102
|
-
// Recorder start URL for the recorder to navigate to when creating the auth session.
|
103
|
-
// Required if "type" is "MANUAL". Not used if "type" is "API".
|
104
|
-
"startUrl": "https://example.com/login",
|
105
|
-
|
106
|
-
// Recorder finish URL for the recorder. Once this URL is reached, the recorder stops and saves the auth session.
|
107
|
-
// Required if "type" is "MANUAL". Not used if "type" is "API".
|
108
|
-
"finishUrl": "https://example.com/dashboard",
|
109
|
-
|
110
|
-
// Recorder browser mode
|
111
|
-
// "fullscreen": Launches the browser in fullscreen mode.
|
112
|
-
// "kiosk": Launches the browser in kiosk mode (no address bar, no navigation controls).
|
113
|
-
// Only applicable for "MANUAL" type.
|
114
|
-
"browserMode": "fullscreen"
|
115
|
-
}
|
116
|
-
|
117
|
-
// API access settings
|
118
|
-
"apiAccess": {
|
119
|
-
// Whether to enable consumption through Intuned API. If this is false, the project can only be consumed through jobs.
|
120
|
-
// This is required for projects that use auth sessions.
|
121
|
-
"enabled": true
|
122
|
-
},
|
123
|
-
|
124
|
-
// Whether to run the deployed API in a headful browser. Running in headful can help with some anti-bot detections. However, it requires more resources and may work slower or crash if the machine size is "standard".
|
125
|
-
"headful": false,
|
126
|
-
|
127
|
-
// The region where your Intuned project is hosted.
|
128
|
-
// For a list of available regions, contact support or refer to the documentation.
|
129
|
-
// Optional - Default: "us"
|
130
|
-
"region": "us"
|
131
|
-
}
|
132
|
-
```
|
133
|
-
|
134
|
-
"""
|
intuned_cli/controller/init.py
DELETED
@@ -1,97 +0,0 @@
|
|
1
|
-
from typing import Literal
|
2
|
-
|
3
|
-
import httpx
|
4
|
-
from anyio import Path
|
5
|
-
|
6
|
-
from intuned_cli.constants import readme
|
7
|
-
from intuned_cli.types import DirectoryNode
|
8
|
-
from intuned_cli.types import FileNode
|
9
|
-
from intuned_cli.types import FileNodeContent
|
10
|
-
from intuned_cli.types import FileSystemTree
|
11
|
-
from intuned_cli.utils.backend import get_base_url
|
12
|
-
from intuned_cli.utils.console import console
|
13
|
-
from intuned_cli.utils.error import CLIError
|
14
|
-
from intuned_cli.utils.exclusions import exclusions as default_excludes
|
15
|
-
|
16
|
-
PythonTemplateName = Literal["python-empty"]
|
17
|
-
|
18
|
-
python_template_name: PythonTemplateName = "python-empty"
|
19
|
-
|
20
|
-
|
21
|
-
async def check_empty_directory() -> bool:
|
22
|
-
cwd = await Path().resolve()
|
23
|
-
try:
|
24
|
-
if not await cwd.is_dir():
|
25
|
-
raise CLIError("The current path is not a directory.")
|
26
|
-
|
27
|
-
files = [f async for f in cwd.iterdir() if await f.is_file()]
|
28
|
-
significant_files = [f for f in files if f.name not in default_excludes]
|
29
|
-
|
30
|
-
return len(significant_files) == 0
|
31
|
-
except FileNotFoundError as e:
|
32
|
-
raise CLIError("The specified directory does not exist.") from e
|
33
|
-
|
34
|
-
|
35
|
-
async def fetch_project_template(template_name: PythonTemplateName) -> FileSystemTree:
|
36
|
-
"""
|
37
|
-
Fetch the project template from the templates directory.
|
38
|
-
|
39
|
-
Args:
|
40
|
-
template_name (PythonTemplateName): The name of the template to fetch.
|
41
|
-
|
42
|
-
Returns:
|
43
|
-
FileSystemTree: The fetched template.
|
44
|
-
"""
|
45
|
-
base_url = get_base_url()
|
46
|
-
url = f"{base_url}/api/templates/{template_name}"
|
47
|
-
async with httpx.AsyncClient() as client:
|
48
|
-
response = await client.get(url)
|
49
|
-
if response.status_code != 200:
|
50
|
-
raise CLIError(f"Failed to fetch template '{template_name}': {response.text}")
|
51
|
-
template_data = response.json()
|
52
|
-
return FileSystemTree.model_validate(template_data["template"])
|
53
|
-
|
54
|
-
|
55
|
-
def prepare_cli_template(file_tree: FileSystemTree):
|
56
|
-
file_tree.root["parameters"] = DirectoryNode(directory=FileSystemTree(root={}))
|
57
|
-
|
58
|
-
file_tree.root["README.md"] = FileNode(file=FileNodeContent(contents=readme))
|
59
|
-
|
60
|
-
|
61
|
-
async def mount_file_tree(file_tree: FileSystemTree, working_directory: Path | None = None):
|
62
|
-
working_directory = working_directory or await Path().resolve()
|
63
|
-
if not await working_directory.is_dir():
|
64
|
-
raise CLIError(f"The specified working directory '{working_directory}' is not a directory.")
|
65
|
-
for name, node in file_tree.root.items():
|
66
|
-
node_path = working_directory / name
|
67
|
-
if isinstance(node, DirectoryNode):
|
68
|
-
await node_path.mkdir(parents=True, exist_ok=True)
|
69
|
-
await mount_file_tree(node.directory, working_directory=node_path)
|
70
|
-
else:
|
71
|
-
await node_path.write_text(node.file.contents)
|
72
|
-
|
73
|
-
|
74
|
-
async def write_project_from_file_tree(template_name: PythonTemplateName, is_target_directory_empty: bool):
|
75
|
-
if not is_target_directory_empty:
|
76
|
-
response = (
|
77
|
-
console.input(
|
78
|
-
"[bold]The current directory is not empty. Do you want to proceed and override files?[/bold] (y/N)"
|
79
|
-
)
|
80
|
-
.strip()
|
81
|
-
.lower()
|
82
|
-
)
|
83
|
-
confirmed = response in ["y", "yes"]
|
84
|
-
if not confirmed:
|
85
|
-
raise CLIError("Project initialization cancelled")
|
86
|
-
|
87
|
-
console.print(f"[cyan]🚀 Initializing[/cyan] [bold]{template_name}[/bold] [cyan]project...[/cyan]")
|
88
|
-
|
89
|
-
project_template = await fetch_project_template(template_name)
|
90
|
-
console.print("[cyan]🔨 Creating project files...[/cyan]")
|
91
|
-
|
92
|
-
prepare_cli_template(project_template)
|
93
|
-
await mount_file_tree(project_template)
|
94
|
-
|
95
|
-
console.print(
|
96
|
-
"[green][bold]🎉 Project initialized successfully![/bold][/green] [bright_green]Run[/bright_green] [bold]poetry install[/bold] [bright_green]to install dependencies and start coding![/bright_green]"
|
97
|
-
)
|
File without changes
|
File without changes
|