uipath 2.1.85__py3-none-any.whl → 2.1.87__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of uipath might be problematic. Click here for more details.
- uipath/_cli/__init__.py +0 -1
- uipath/_cli/_utils/_input_args.py +22 -3
- uipath/_cli/_utils/_project_files.py +122 -0
- uipath/_cli/cli_init.py +20 -14
- uipath/_cli/cli_pull.py +11 -151
- uipath/_resources/AGENTS.md +27 -712
- uipath/_resources/CLAUDE.md +1 -0
- uipath/_resources/CLI_REFERENCE.md +219 -0
- uipath/_resources/REQUIRED_STRUCTURE.md +93 -0
- uipath/_resources/SDK_REFERENCE.md +414 -0
- {uipath-2.1.85.dist-info → uipath-2.1.87.dist-info}/METADATA +1 -1
- {uipath-2.1.85.dist-info → uipath-2.1.87.dist-info}/RECORD +15 -11
- {uipath-2.1.85.dist-info → uipath-2.1.87.dist-info}/WHEEL +0 -0
- {uipath-2.1.85.dist-info → uipath-2.1.87.dist-info}/entry_points.txt +0 -0
- {uipath-2.1.85.dist-info → uipath-2.1.87.dist-info}/licenses/LICENSE +0 -0
uipath/_cli/__init__.py
CHANGED
|
@@ -8,7 +8,6 @@ from .cli_auth import auth as auth
|
|
|
8
8
|
from .cli_deploy import deploy as deploy # type: ignore
|
|
9
9
|
from .cli_dev import dev as dev
|
|
10
10
|
from .cli_eval import eval as eval # type: ignore
|
|
11
|
-
from .cli_init import generate_agents_md as generate_agents_md # type: ignore
|
|
12
11
|
from .cli_init import init as init # type: ignore
|
|
13
12
|
from .cli_invoke import invoke as invoke # type: ignore
|
|
14
13
|
from .cli_new import new as new # type: ignore
|
|
@@ -2,6 +2,7 @@ import importlib.util
|
|
|
2
2
|
import inspect
|
|
3
3
|
import sys
|
|
4
4
|
from dataclasses import fields, is_dataclass
|
|
5
|
+
from enum import Enum
|
|
5
6
|
from types import ModuleType
|
|
6
7
|
from typing import (
|
|
7
8
|
Any,
|
|
@@ -16,11 +17,11 @@ from typing import (
|
|
|
16
17
|
|
|
17
18
|
from pydantic import BaseModel
|
|
18
19
|
|
|
19
|
-
SchemaType = Literal["object", "integer", "
|
|
20
|
+
SchemaType = Literal["object", "integer", "number", "string", "boolean", "array"]
|
|
20
21
|
|
|
21
22
|
TYPE_MAP: Dict[str, SchemaType] = {
|
|
22
23
|
"int": "integer",
|
|
23
|
-
"float": "
|
|
24
|
+
"float": "number",
|
|
24
25
|
"str": "string",
|
|
25
26
|
"bool": "boolean",
|
|
26
27
|
"list": "array",
|
|
@@ -52,7 +53,25 @@ def get_type_schema(type_hint: Any) -> Dict[str, Any]:
|
|
|
52
53
|
return {"type": "object"}
|
|
53
54
|
|
|
54
55
|
if inspect.isclass(type_hint):
|
|
55
|
-
|
|
56
|
+
if issubclass(type_hint, Enum):
|
|
57
|
+
enum_values = [member.value for member in type_hint]
|
|
58
|
+
if not enum_values:
|
|
59
|
+
return {"type": "string", "enum": []}
|
|
60
|
+
|
|
61
|
+
first_value = enum_values[0]
|
|
62
|
+
if isinstance(first_value, str):
|
|
63
|
+
enum_type = "string"
|
|
64
|
+
elif isinstance(first_value, int):
|
|
65
|
+
enum_type = "integer"
|
|
66
|
+
elif isinstance(first_value, float):
|
|
67
|
+
enum_type = "number"
|
|
68
|
+
elif isinstance(first_value, bool):
|
|
69
|
+
enum_type = "boolean"
|
|
70
|
+
else:
|
|
71
|
+
enum_type = "string"
|
|
72
|
+
|
|
73
|
+
return {"type": enum_type, "enum": enum_values}
|
|
74
|
+
|
|
56
75
|
if issubclass(type_hint, BaseModel):
|
|
57
76
|
properties = {}
|
|
58
77
|
required = []
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
# type: ignore
|
|
2
|
+
import hashlib
|
|
2
3
|
import json
|
|
3
4
|
import os
|
|
4
5
|
import re
|
|
6
|
+
from pathlib import Path
|
|
5
7
|
from typing import Any, Dict, Optional, Tuple
|
|
6
8
|
|
|
9
|
+
import click
|
|
7
10
|
from pydantic import BaseModel
|
|
8
11
|
|
|
9
12
|
from .._utils._console import ConsoleLogger
|
|
10
13
|
from ._constants import is_binary_file
|
|
14
|
+
from ._studio_project import (
|
|
15
|
+
ProjectFile,
|
|
16
|
+
ProjectFolder,
|
|
17
|
+
StudioClient,
|
|
18
|
+
get_folder_by_name,
|
|
19
|
+
)
|
|
11
20
|
|
|
12
21
|
try:
|
|
13
22
|
import tomllib
|
|
@@ -431,3 +440,116 @@ def files_to_include(
|
|
|
431
440
|
)
|
|
432
441
|
)
|
|
433
442
|
return extra_files
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def compute_normalized_hash(content: str) -> str:
|
|
446
|
+
"""Compute hash of normalized content.
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
content: Content to hash
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
str: SHA256 hash of the normalized content
|
|
453
|
+
"""
|
|
454
|
+
try:
|
|
455
|
+
# Try to parse as JSON to handle formatting
|
|
456
|
+
json_content = json.loads(content)
|
|
457
|
+
normalized = json.dumps(json_content, indent=2)
|
|
458
|
+
except json.JSONDecodeError:
|
|
459
|
+
# Not JSON, normalize line endings
|
|
460
|
+
normalized = content.replace("\r\n", "\n").replace("\r", "\n")
|
|
461
|
+
|
|
462
|
+
return hashlib.sha256(normalized.encode("utf-8")).hexdigest()
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def collect_files_from_folder(
|
|
466
|
+
folder: ProjectFolder, base_path: str, files_dict: Dict[str, ProjectFile]
|
|
467
|
+
) -> None:
|
|
468
|
+
"""Recursively collect all files from a folder and its subfolders.
|
|
469
|
+
|
|
470
|
+
Args:
|
|
471
|
+
folder: The folder to collect files from
|
|
472
|
+
base_path: Base path for file paths
|
|
473
|
+
files_dict: Dictionary to store collected files
|
|
474
|
+
"""
|
|
475
|
+
# Add files from current folder
|
|
476
|
+
for file in folder.files:
|
|
477
|
+
file_path = os.path.join(base_path, file.name)
|
|
478
|
+
files_dict[file_path] = file
|
|
479
|
+
|
|
480
|
+
# Recursively process subfolders
|
|
481
|
+
for subfolder in folder.folders:
|
|
482
|
+
subfolder_path = os.path.join(base_path, subfolder.name)
|
|
483
|
+
collect_files_from_folder(subfolder, subfolder_path, files_dict)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
async def download_folder_files(
|
|
487
|
+
studio_client: StudioClient,
|
|
488
|
+
folder: ProjectFolder,
|
|
489
|
+
base_path: Path,
|
|
490
|
+
) -> None:
|
|
491
|
+
"""Download files from a folder recursively.
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
studio_client: Studio client
|
|
495
|
+
folder: The folder to download files from
|
|
496
|
+
base_path: Base path for local file storage
|
|
497
|
+
"""
|
|
498
|
+
files_dict: Dict[str, ProjectFile] = {}
|
|
499
|
+
collect_files_from_folder(folder, "", files_dict)
|
|
500
|
+
for file_path, remote_file in files_dict.items():
|
|
501
|
+
local_path = base_path / file_path
|
|
502
|
+
local_path.parent.mkdir(parents=True, exist_ok=True)
|
|
503
|
+
|
|
504
|
+
# Download remote file
|
|
505
|
+
response = await studio_client.download_file_async(remote_file.id)
|
|
506
|
+
remote_content = response.read().decode("utf-8")
|
|
507
|
+
remote_hash = compute_normalized_hash(remote_content)
|
|
508
|
+
|
|
509
|
+
if os.path.exists(local_path):
|
|
510
|
+
# Read and hash local file
|
|
511
|
+
with open(local_path, "r", encoding="utf-8") as f:
|
|
512
|
+
local_content = f.read()
|
|
513
|
+
local_hash = compute_normalized_hash(local_content)
|
|
514
|
+
|
|
515
|
+
# Compare hashes
|
|
516
|
+
if local_hash != remote_hash:
|
|
517
|
+
styled_path = click.style(str(file_path), fg="cyan")
|
|
518
|
+
console.warning(f"File {styled_path}" + " differs from remote version.")
|
|
519
|
+
response = click.prompt("Do you want to overwrite it? (y/n)", type=str)
|
|
520
|
+
if response.lower() == "y":
|
|
521
|
+
with open(local_path, "w", encoding="utf-8", newline="\n") as f:
|
|
522
|
+
f.write(remote_content)
|
|
523
|
+
console.success(f"Updated {click.style(str(file_path), fg='cyan')}")
|
|
524
|
+
else:
|
|
525
|
+
console.info(f"Skipped {click.style(str(file_path), fg='cyan')}")
|
|
526
|
+
else:
|
|
527
|
+
console.info(
|
|
528
|
+
f"File {click.style(str(file_path), fg='cyan')} is up to date"
|
|
529
|
+
)
|
|
530
|
+
else:
|
|
531
|
+
# File doesn't exist locally, create it
|
|
532
|
+
with open(local_path, "w", encoding="utf-8", newline="\n") as f:
|
|
533
|
+
f.write(remote_content)
|
|
534
|
+
console.success(f"Downloaded {click.style(str(file_path), fg='cyan')}")
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
async def pull_project(project_id: str, download_configuration: dict[str, Path]):
|
|
538
|
+
studio_client = StudioClient(project_id)
|
|
539
|
+
|
|
540
|
+
with console.spinner("Pulling UiPath project files..."):
|
|
541
|
+
try:
|
|
542
|
+
structure = await studio_client.get_project_structure_async()
|
|
543
|
+
for source_key, destination in download_configuration.items():
|
|
544
|
+
source_folder = get_folder_by_name(structure, source_key)
|
|
545
|
+
if source_folder:
|
|
546
|
+
await download_folder_files(
|
|
547
|
+
studio_client,
|
|
548
|
+
source_folder,
|
|
549
|
+
destination,
|
|
550
|
+
)
|
|
551
|
+
else:
|
|
552
|
+
console.warning(f"No {source_key} folder found in remote project")
|
|
553
|
+
|
|
554
|
+
except Exception as e:
|
|
555
|
+
console.error(f"Failed to pull UiPath project: {str(e)}")
|
uipath/_cli/cli_init.py
CHANGED
|
@@ -58,32 +58,28 @@ def generate_env_file(target_directory):
|
|
|
58
58
|
console.success(f" Created '{relative_path}' file.")
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
def
|
|
62
|
-
"""Generate
|
|
61
|
+
def generate_agent_specific_file_md(target_directory: str, file_name: str) -> None:
|
|
62
|
+
"""Generate an agent-specific file from the packaged resource.
|
|
63
63
|
|
|
64
64
|
Args:
|
|
65
|
-
target_directory: The directory where
|
|
65
|
+
target_directory: The directory where the file should be created.
|
|
66
|
+
file_name: The name of the file should be created.
|
|
66
67
|
"""
|
|
67
|
-
target_path = os.path.join(target_directory,
|
|
68
|
+
target_path = os.path.join(target_directory, file_name)
|
|
68
69
|
|
|
69
|
-
# Skip if file already exists
|
|
70
70
|
if os.path.exists(target_path):
|
|
71
|
-
console.info("Skipping '
|
|
71
|
+
console.info(f"Skipping '{file_name}' creation as it already exists.")
|
|
72
72
|
return
|
|
73
73
|
|
|
74
74
|
try:
|
|
75
|
-
|
|
76
|
-
source_path = importlib.resources.files("uipath._resources").joinpath(
|
|
77
|
-
"AGENTS.md"
|
|
78
|
-
)
|
|
75
|
+
source_path = importlib.resources.files("uipath._resources").joinpath(file_name)
|
|
79
76
|
|
|
80
|
-
# Copy the file to the target directory
|
|
81
77
|
with importlib.resources.as_file(source_path) as s_path:
|
|
82
78
|
shutil.copy(s_path, target_path)
|
|
83
79
|
|
|
84
|
-
console.success(" Created '
|
|
80
|
+
console.success(f" Created '{file_name}' file.")
|
|
85
81
|
except Exception as e:
|
|
86
|
-
console.warning(f"Could not create
|
|
82
|
+
console.warning(f"Could not create {file_name}: {e}")
|
|
87
83
|
|
|
88
84
|
|
|
89
85
|
def get_existing_settings(config_path: str) -> Optional[Dict[str, Any]]:
|
|
@@ -160,7 +156,17 @@ def init(entrypoint: str, infer_bindings: bool) -> None:
|
|
|
160
156
|
current_directory = os.getcwd()
|
|
161
157
|
generate_env_file(current_directory)
|
|
162
158
|
create_telemetry_config_file(current_directory)
|
|
163
|
-
|
|
159
|
+
generate_agent_specific_file_md(current_directory, "AGENTS.md")
|
|
160
|
+
generate_agent_specific_file_md(
|
|
161
|
+
os.path.join(current_directory, ".agent"), "CLI_REFERENCE.md"
|
|
162
|
+
)
|
|
163
|
+
generate_agent_specific_file_md(
|
|
164
|
+
os.path.join(current_directory, ".agent"), "REQUIRED_STRUCTURE.md"
|
|
165
|
+
)
|
|
166
|
+
generate_agent_specific_file_md(
|
|
167
|
+
os.path.join(current_directory, ".agent"), "SDK_REFERENCE.md"
|
|
168
|
+
)
|
|
169
|
+
generate_agent_specific_file_md(current_directory, "CLAUDE.md")
|
|
164
170
|
|
|
165
171
|
result = Middlewares.next(
|
|
166
172
|
"init",
|
uipath/_cli/cli_pull.py
CHANGED
|
@@ -11,133 +11,27 @@ It handles:
|
|
|
11
11
|
|
|
12
12
|
# type: ignore
|
|
13
13
|
import asyncio
|
|
14
|
-
import hashlib
|
|
15
|
-
import json
|
|
16
14
|
import os
|
|
17
|
-
from
|
|
15
|
+
from pathlib import Path
|
|
18
16
|
|
|
19
17
|
import click
|
|
20
18
|
|
|
21
19
|
from ..telemetry import track
|
|
22
20
|
from ._utils._console import ConsoleLogger
|
|
23
21
|
from ._utils._constants import UIPATH_PROJECT_ID
|
|
24
|
-
from ._utils.
|
|
25
|
-
ProjectFile,
|
|
26
|
-
ProjectFolder,
|
|
27
|
-
StudioClient,
|
|
28
|
-
get_folder_by_name,
|
|
29
|
-
)
|
|
22
|
+
from ._utils._project_files import pull_project
|
|
30
23
|
|
|
31
24
|
console = ConsoleLogger()
|
|
32
25
|
|
|
33
26
|
|
|
34
|
-
def compute_normalized_hash(content: str) -> str:
|
|
35
|
-
"""Compute hash of normalized content.
|
|
36
|
-
|
|
37
|
-
Args:
|
|
38
|
-
content: Content to hash
|
|
39
|
-
|
|
40
|
-
Returns:
|
|
41
|
-
str: SHA256 hash of the normalized content
|
|
42
|
-
"""
|
|
43
|
-
try:
|
|
44
|
-
# Try to parse as JSON to handle formatting
|
|
45
|
-
json_content = json.loads(content)
|
|
46
|
-
normalized = json.dumps(json_content, indent=2)
|
|
47
|
-
except json.JSONDecodeError:
|
|
48
|
-
# Not JSON, normalize line endings
|
|
49
|
-
normalized = content.replace("\r\n", "\n").replace("\r", "\n")
|
|
50
|
-
|
|
51
|
-
return hashlib.sha256(normalized.encode("utf-8")).hexdigest()
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def collect_files_from_folder(
|
|
55
|
-
folder: ProjectFolder, base_path: str, files_dict: Dict[str, ProjectFile]
|
|
56
|
-
) -> None:
|
|
57
|
-
"""Recursively collect all files from a folder and its subfolders.
|
|
58
|
-
|
|
59
|
-
Args:
|
|
60
|
-
folder: The folder to collect files from
|
|
61
|
-
base_path: Base path for file paths
|
|
62
|
-
files_dict: Dictionary to store collected files
|
|
63
|
-
"""
|
|
64
|
-
# Add files from current folder
|
|
65
|
-
for file in folder.files:
|
|
66
|
-
file_path = os.path.join(base_path, file.name)
|
|
67
|
-
files_dict[file_path] = file
|
|
68
|
-
|
|
69
|
-
# Recursively process subfolders
|
|
70
|
-
for subfolder in folder.folders:
|
|
71
|
-
subfolder_path = os.path.join(base_path, subfolder.name)
|
|
72
|
-
collect_files_from_folder(subfolder, subfolder_path, files_dict)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
async def download_folder_files(
|
|
76
|
-
studio_client: StudioClient,
|
|
77
|
-
folder: ProjectFolder,
|
|
78
|
-
base_path: str,
|
|
79
|
-
processed_files: Set[str],
|
|
80
|
-
) -> None:
|
|
81
|
-
"""Download files from a folder recursively.
|
|
82
|
-
|
|
83
|
-
Args:
|
|
84
|
-
studio_client: Studio client
|
|
85
|
-
folder: The folder to download files from
|
|
86
|
-
base_path: Base path for local file storage
|
|
87
|
-
processed_files: Set to track processed files
|
|
88
|
-
"""
|
|
89
|
-
files_dict: Dict[str, ProjectFile] = {}
|
|
90
|
-
collect_files_from_folder(folder, "", files_dict)
|
|
91
|
-
|
|
92
|
-
for file_path, remote_file in files_dict.items():
|
|
93
|
-
local_path = os.path.join(base_path, file_path)
|
|
94
|
-
local_dir = os.path.dirname(local_path)
|
|
95
|
-
|
|
96
|
-
# Create directory if it doesn't exist
|
|
97
|
-
if not os.path.exists(local_dir):
|
|
98
|
-
os.makedirs(local_dir)
|
|
99
|
-
|
|
100
|
-
# Download remote file
|
|
101
|
-
response = await studio_client.download_file_async(remote_file.id)
|
|
102
|
-
remote_content = response.read().decode("utf-8")
|
|
103
|
-
remote_hash = compute_normalized_hash(remote_content)
|
|
104
|
-
|
|
105
|
-
if os.path.exists(local_path):
|
|
106
|
-
# Read and hash local file
|
|
107
|
-
with open(local_path, "r", encoding="utf-8") as f:
|
|
108
|
-
local_content = f.read()
|
|
109
|
-
local_hash = compute_normalized_hash(local_content)
|
|
110
|
-
|
|
111
|
-
# Compare hashes
|
|
112
|
-
if local_hash != remote_hash:
|
|
113
|
-
styled_path = click.style(str(file_path), fg="cyan")
|
|
114
|
-
console.warning(f"File {styled_path}" + " differs from remote version.")
|
|
115
|
-
response = click.prompt("Do you want to overwrite it? (y/n)", type=str)
|
|
116
|
-
if response.lower() == "y":
|
|
117
|
-
with open(local_path, "w", encoding="utf-8", newline="\n") as f:
|
|
118
|
-
f.write(remote_content)
|
|
119
|
-
console.success(f"Updated {click.style(str(file_path), fg='cyan')}")
|
|
120
|
-
else:
|
|
121
|
-
console.info(f"Skipped {click.style(str(file_path), fg='cyan')}")
|
|
122
|
-
else:
|
|
123
|
-
console.info(
|
|
124
|
-
f"File {click.style(str(file_path), fg='cyan')} is up to date"
|
|
125
|
-
)
|
|
126
|
-
else:
|
|
127
|
-
# File doesn't exist locally, create it
|
|
128
|
-
with open(local_path, "w", encoding="utf-8", newline="\n") as f:
|
|
129
|
-
f.write(remote_content)
|
|
130
|
-
console.success(f"Downloaded {click.style(str(file_path), fg='cyan')}")
|
|
131
|
-
|
|
132
|
-
processed_files.add(file_path)
|
|
133
|
-
|
|
134
|
-
|
|
135
27
|
@click.command()
|
|
136
28
|
@click.argument(
|
|
137
|
-
"root",
|
|
29
|
+
"root",
|
|
30
|
+
type=click.Path(exists=False, file_okay=False, dir_okay=True, path_type=Path),
|
|
31
|
+
default=Path("."),
|
|
138
32
|
)
|
|
139
33
|
@track
|
|
140
|
-
def pull(root:
|
|
34
|
+
def pull(root: Path) -> None:
|
|
141
35
|
"""Pull remote project files from Studio Web Project.
|
|
142
36
|
|
|
143
37
|
This command pulls the remote project files from a UiPath Studio Web project.
|
|
@@ -158,42 +52,8 @@ def pull(root: str) -> None:
|
|
|
158
52
|
if not (project_id := os.getenv(UIPATH_PROJECT_ID, False)):
|
|
159
53
|
console.error("UIPATH_PROJECT_ID environment variable not found.")
|
|
160
54
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
processed_files: Set[str] = set()
|
|
168
|
-
|
|
169
|
-
# Process source_code folder
|
|
170
|
-
source_code_folder = get_folder_by_name(structure, "source_code")
|
|
171
|
-
if source_code_folder:
|
|
172
|
-
asyncio.run(
|
|
173
|
-
download_folder_files(
|
|
174
|
-
studio_client,
|
|
175
|
-
source_code_folder,
|
|
176
|
-
root,
|
|
177
|
-
processed_files,
|
|
178
|
-
)
|
|
179
|
-
)
|
|
180
|
-
else:
|
|
181
|
-
console.warning("No source_code folder found in remote project")
|
|
182
|
-
|
|
183
|
-
# Process evals folder
|
|
184
|
-
evals_folder = get_folder_by_name(structure, "evals")
|
|
185
|
-
if evals_folder:
|
|
186
|
-
evals_path = os.path.join(root, "evals")
|
|
187
|
-
asyncio.run(
|
|
188
|
-
download_folder_files(
|
|
189
|
-
studio_client,
|
|
190
|
-
evals_folder,
|
|
191
|
-
evals_path,
|
|
192
|
-
processed_files,
|
|
193
|
-
)
|
|
194
|
-
)
|
|
195
|
-
else:
|
|
196
|
-
console.warning("No evals folder found in remote project")
|
|
197
|
-
|
|
198
|
-
except Exception as e:
|
|
199
|
-
console.error(f"Failed to pull UiPath project: {str(e)}")
|
|
55
|
+
default_download_configuration = {
|
|
56
|
+
"source_code": root,
|
|
57
|
+
"evals": root / "evals",
|
|
58
|
+
}
|
|
59
|
+
asyncio.run(pull_project(project_id, default_download_configuration))
|