uipath-dev 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,110 @@
1
+ """Panel for displaying execution run history."""
2
+
3
+ from typing import List, Optional
4
+
5
+ from textual.app import ComposeResult
6
+ from textual.containers import Container, Vertical
7
+ from textual.widgets import (
8
+ Button,
9
+ ListItem,
10
+ ListView,
11
+ Static,
12
+ TabbedContent,
13
+ TabPane,
14
+ )
15
+
16
+ from uipath.dev.models.execution import ExecutionRun
17
+
18
+
19
+ class RunHistoryPanel(Container):
20
+ """Left panel showing execution run history."""
21
+
22
+ def __init__(self, **kwargs):
23
+ """Initialize RunHistoryPanel."""
24
+ super().__init__(**kwargs)
25
+ self.runs: List[ExecutionRun] = []
26
+ self.selected_run: Optional[ExecutionRun] = None
27
+
28
+ def compose(self) -> ComposeResult:
29
+ """Compose the UI layout."""
30
+ with TabbedContent():
31
+ with TabPane("History", id="history-tab"):
32
+ with Vertical():
33
+ yield ListView(id="run-list", classes="run-list")
34
+ yield Button(
35
+ "+ New",
36
+ id="new-run-btn",
37
+ variant="primary",
38
+ classes="new-run-btn",
39
+ )
40
+
41
+ def on_mount(self) -> None:
42
+ """Set up periodic refresh on mount."""
43
+ # Update only running items every 5 seconds
44
+ self.set_interval(5.0, self._refresh_running_items)
45
+
46
+ def add_run(self, run: ExecutionRun):
47
+ """Add a new run to history."""
48
+ self.runs.insert(0, run) # Add to top
49
+ self.refresh_list()
50
+
51
+ def update_run(self, run: ExecutionRun):
52
+ """Update an existing run."""
53
+ self.refresh_list()
54
+
55
+ def refresh_list(self):
56
+ """Refresh the run list display."""
57
+ run_list = self.query_one("#run-list", ListView)
58
+ run_list.clear()
59
+
60
+ for run in self.runs:
61
+ item = ListItem(
62
+ Static(run.display_name), classes=f"run-item run-{run.status}"
63
+ )
64
+ # Store run id directly on the ListItem
65
+ item.run_id = run.id # type: ignore[attr-defined]
66
+ run_list.append(item)
67
+
68
+ def get_run_by_id(self, run_id: str) -> Optional[ExecutionRun]:
69
+ """Get run by id."""
70
+ for run in self.runs:
71
+ if run.id == run_id:
72
+ return run
73
+ return None
74
+
75
+ def clear_runs(self):
76
+ """Clear all runs from history."""
77
+ self.runs.clear()
78
+ self.refresh_list()
79
+
80
+ def _refresh_running_items(self) -> None:
81
+ """Refresh display names for running items only."""
82
+ if not any(run.status == "running" for run in self.runs):
83
+ return None
84
+
85
+ try:
86
+ run_list = self.query_one("#run-list", ListView)
87
+ except Exception:
88
+ return None
89
+
90
+ # Take a snapshot of items to avoid mid-iteration changes
91
+ items_snapshot = list(run_list.children)
92
+
93
+ for item in items_snapshot:
94
+ if not hasattr(item, "run_id"):
95
+ continue
96
+
97
+ run = self.get_run_by_id(item.run_id)
98
+ if not run or run.status != "running":
99
+ continue
100
+
101
+ # Check if item still exists in the list (wasn't removed)
102
+ if item not in run_list.children:
103
+ continue
104
+
105
+ try:
106
+ static = item.query_one(Static)
107
+ static.update(run.display_name)
108
+ except Exception:
109
+ # Item structure changed or was removed
110
+ continue
@@ -0,0 +1,27 @@
1
+ """TextArea component that validates JSON input."""
2
+
3
+ import json
4
+
5
+ from textual.widgets import TextArea
6
+
7
+
8
+ class JsonInput(TextArea):
9
+ """TextArea that validates JSON on change."""
10
+
11
+ def validate_json(self) -> bool:
12
+ """Validate the current text as JSON."""
13
+ text = self.text.strip()
14
+ if not text:
15
+ self.remove_class("invalid")
16
+ return True
17
+ try:
18
+ json.loads(text)
19
+ self.remove_class("invalid")
20
+ return True
21
+ except json.JSONDecodeError:
22
+ self.add_class("invalid")
23
+ return False
24
+
25
+ def on_text_area_changed(self, event: TextArea.Changed) -> None:
26
+ """Validate JSON when the text changes."""
27
+ self.validate_json()
@@ -0,0 +1,142 @@
1
+ """Panel for creating new runs with entrypoint selection and JSON input."""
2
+
3
+ import json
4
+ import os
5
+ from typing import Any, Tuple, cast
6
+
7
+ from textual.app import ComposeResult
8
+ from textual.containers import Container, Horizontal, Vertical
9
+ from textual.reactive import reactive
10
+ from textual.widgets import Button, Select, TabbedContent, TabPane, TextArea
11
+
12
+ from uipath.dev.components.json_input import JsonInput
13
+
14
+
15
+ def mock_json_from_schema(schema: dict[str, Any]) -> dict[str, Any]:
16
+ """Generate a mock JSON object based on a given JSON schema."""
17
+ props: dict[str, Any] = schema.get("properties", {})
18
+ required = schema.get("required", [])
19
+ mock = {}
20
+ for key, info in props.items():
21
+ if "default" in info:
22
+ mock[key] = info["default"]
23
+ continue
24
+ t = info.get("type")
25
+ if t == "string":
26
+ mock[key] = f"example_{key}" if key in required else ""
27
+ elif t == "integer":
28
+ mock[key] = 0 if key in required else None
29
+ elif t == "boolean":
30
+ mock[key] = True if key in required else False
31
+ elif t == "array":
32
+ item_schema = info.get("items", {"type": "string"})
33
+ mock[key] = [mock_json_from_schema(item_schema)]
34
+ elif t == "object":
35
+ mock[key] = mock_json_from_schema(info)
36
+ else:
37
+ mock[key] = None
38
+ return mock
39
+
40
+
41
+ class NewRunPanel(Container):
42
+ """Panel for creating new runs with a Select entrypoint selector."""
43
+
44
+ selected_entrypoint = reactive("")
45
+
46
+ def __init__(self, **kwargs):
47
+ """Initialize NewRunPanel with entrypoints from uipath.json."""
48
+ super().__init__(**kwargs)
49
+ json_path = os.path.join(os.getcwd(), "uipath.json")
50
+ data: dict[str, Any] = {}
51
+ if os.path.exists(json_path):
52
+ with open(json_path, "r") as f:
53
+ data = json.load(f)
54
+
55
+ self.entrypoints = data.get("entryPoints", [{"filePath": "default"}])
56
+ self.entrypoint_paths = [ep["filePath"] for ep in self.entrypoints]
57
+ self.conversational = False
58
+ self.selected_entrypoint = (
59
+ self.entrypoint_paths[0] if self.entrypoint_paths else ""
60
+ )
61
+ ep: dict[str, Any] = next(
62
+ (
63
+ ep
64
+ for ep in self.entrypoints
65
+ if ep["filePath"] == self.selected_entrypoint
66
+ ),
67
+ {},
68
+ )
69
+ self.initial_input = json.dumps(
70
+ mock_json_from_schema(ep.get("input", {})), indent=2
71
+ )
72
+
73
+ def compose(self) -> ComposeResult:
74
+ """Compose the UI layout."""
75
+ with TabbedContent():
76
+ with TabPane("New run", id="new-tab"):
77
+ with Vertical():
78
+ options = [(path, path) for path in self.entrypoint_paths]
79
+ yield Select(
80
+ options,
81
+ id="entrypoint-select",
82
+ value=self.selected_entrypoint,
83
+ allow_blank=False,
84
+ )
85
+
86
+ yield JsonInput(
87
+ text=self.initial_input,
88
+ language="json",
89
+ id="json-input",
90
+ classes="input-field json-input",
91
+ )
92
+
93
+ with Horizontal(classes="run-actions"):
94
+ yield Button(
95
+ "▶ Run",
96
+ id="execute-btn",
97
+ variant="primary",
98
+ classes="action-btn",
99
+ )
100
+
101
+ async def on_select_changed(self, event: Select.Changed) -> None:
102
+ """Update JSON input when user selects an entrypoint."""
103
+ self.selected_entrypoint = cast(str, event.value)
104
+
105
+ ep: dict[str, Any] = next(
106
+ (
107
+ ep
108
+ for ep in self.entrypoints
109
+ if ep["filePath"] == self.selected_entrypoint
110
+ ),
111
+ {},
112
+ )
113
+ json_input = self.query_one("#json-input", TextArea)
114
+ json_input.text = json.dumps(
115
+ mock_json_from_schema(ep.get("input", {})), indent=2
116
+ )
117
+
118
+ def get_input_values(self) -> Tuple[str, str, bool]:
119
+ """Get the selected entrypoint and JSON input values."""
120
+ json_input = self.query_one("#json-input", TextArea)
121
+ return self.selected_entrypoint, json_input.text.strip(), self.conversational
122
+
123
+ def reset_form(self):
124
+ """Reset selection and JSON input to defaults."""
125
+ self.selected_entrypoint = (
126
+ self.entrypoint_paths[0] if self.entrypoint_paths else ""
127
+ )
128
+ select = self.query_one("#entrypoint-select", Select)
129
+ select.value = self.selected_entrypoint
130
+
131
+ ep: dict[str, Any] = next(
132
+ (
133
+ ep
134
+ for ep in self.entrypoints
135
+ if ep["filePath"] == self.selected_entrypoint
136
+ ),
137
+ {},
138
+ )
139
+ json_input = self.query_one("#json-input", TextArea)
140
+ json_input.text = json.dumps(
141
+ mock_json_from_schema(ep.get("input", {})), indent=2
142
+ )
@@ -0,0 +1,80 @@
1
+ """Models for representing execution runs and their data."""
2
+
3
+ import os
4
+ from datetime import datetime
5
+ from typing import Any, Optional, Union
6
+ from uuid import uuid4
7
+
8
+ from rich.text import Text
9
+ from uipath.runtime.errors import UiPathErrorContract
10
+
11
+ from uipath.dev.models.messages import LogMessage, TraceMessage
12
+
13
+
14
+ class ExecutionRun:
15
+ """Represents a single execution run."""
16
+
17
+ def __init__(
18
+ self,
19
+ entrypoint: str,
20
+ input_data: Union[dict[str, Any]],
21
+ conversational: bool = False,
22
+ ):
23
+ """Initialize an ExecutionRun instance."""
24
+ self.id = str(uuid4())[:8]
25
+ self.entrypoint = entrypoint
26
+ self.input_data = input_data
27
+ self.conversational = conversational
28
+ self.resume_data: Optional[dict[str, Any]] = None
29
+ self.output_data: Optional[dict[str, Any]] = None
30
+ self.start_time = datetime.now()
31
+ self.end_time: Optional[datetime] = None
32
+ self.status = "pending" # pending, running, completed, failed, suspended
33
+ self.traces: list[TraceMessage] = []
34
+ self.logs: list[LogMessage] = []
35
+ self.error: Optional[UiPathErrorContract] = None
36
+
37
+ @property
38
+ def duration(self) -> str:
39
+ """Get the duration of the run as a formatted string."""
40
+ if self.end_time:
41
+ delta = self.end_time - self.start_time
42
+ return f"{delta.total_seconds():.1f}s"
43
+ elif self.start_time:
44
+ delta = datetime.now() - self.start_time
45
+ return f"{delta.total_seconds():.1f}s"
46
+ return "0.0s"
47
+
48
+ @property
49
+ def display_name(self) -> Text:
50
+ """Get a rich Text representation of the run for display."""
51
+ status_colors = {
52
+ "pending": "grey50",
53
+ "running": "yellow",
54
+ "suspended": "cyan",
55
+ "completed": "green",
56
+ "failed": "red",
57
+ }
58
+
59
+ status_icon = {
60
+ "pending": "●",
61
+ "running": "▶",
62
+ "suspended": "⏸",
63
+ "completed": "✔",
64
+ "failed": "✖",
65
+ }.get(self.status, "?")
66
+
67
+ script_name = (
68
+ os.path.basename(self.entrypoint) if self.entrypoint else "untitled"
69
+ )
70
+ truncated_script = script_name[:8]
71
+ time_str = self.start_time.strftime("%H:%M:%S")
72
+ duration_str = self.duration[:6]
73
+
74
+ text = Text()
75
+ text.append(f"{status_icon:<2} ", style=status_colors.get(self.status, "white"))
76
+ text.append(f"{truncated_script:<8} ")
77
+ text.append(f"({time_str:<8}) ")
78
+ text.append(f"[{duration_str:<6}]")
79
+
80
+ return text
@@ -0,0 +1,53 @@
1
+ """Messages used for inter-component communication in the UiPath Developer Console."""
2
+
3
+ from datetime import datetime
4
+ from typing import Any, Optional, Union
5
+
6
+ from rich.console import RenderableType
7
+ from textual.message import Message
8
+
9
+
10
+ class LogMessage(Message):
11
+ """Message sent when a new log entry is created."""
12
+
13
+ def __init__(
14
+ self,
15
+ run_id: str,
16
+ level: str,
17
+ message: Union[str, RenderableType],
18
+ timestamp: Optional[datetime] = None,
19
+ ):
20
+ """Initialize a LogMessage instance."""
21
+ self.run_id = run_id
22
+ self.level = level
23
+ self.message = message
24
+ self.timestamp = timestamp or datetime.now()
25
+ super().__init__()
26
+
27
+
28
+ class TraceMessage(Message):
29
+ """Message sent when a new trace entry is created."""
30
+
31
+ def __init__(
32
+ self,
33
+ run_id: str,
34
+ span_name: str,
35
+ span_id: str,
36
+ parent_span_id: Optional[str] = None,
37
+ trace_id: Optional[str] = None,
38
+ status: str = "running",
39
+ duration_ms: Optional[float] = None,
40
+ timestamp: Optional[datetime] = None,
41
+ attributes: Optional[dict[str, Any]] = None,
42
+ ):
43
+ """Initialize a TraceMessage instance."""
44
+ self.run_id = run_id
45
+ self.span_name = span_name
46
+ self.span_id = span_id
47
+ self.parent_span_id = parent_span_id
48
+ self.trace_id = trace_id
49
+ self.status = status
50
+ self.duration_ms = duration_ms
51
+ self.timestamp = timestamp or datetime.now()
52
+ self.attributes = attributes or {}
53
+ super().__init__()
uipath/dev/py.typed ADDED
File without changes
@@ -0,0 +1,59 @@
1
+ Metadata-Version: 2.4
2
+ Name: uipath-dev
3
+ Version: 0.0.1
4
+ Summary: UiPath Dev Terminal
5
+ Project-URL: Homepage, https://uipath.com
6
+ Project-URL: Repository, https://github.com/UiPath/uipath-dev-python
7
+ Project-URL: Documentation, https://uipath.github.io/uipath-python/
8
+ Maintainer-email: Cristian Pufu <cristian.pufu@uipath.com>
9
+ License-File: LICENSE
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Topic :: Software Development :: Build Tools
15
+ Requires-Python: >=3.11
16
+ Requires-Dist: pyperclip>=1.11.0
17
+ Requires-Dist: textual>=6.5.0
18
+ Requires-Dist: uipath-runtime>=0.0.4
19
+ Description-Content-Type: text/markdown
20
+
21
+ # UiPath Developer Console
22
+
23
+ The **UiPath Developer Console** is an interactive terminal application for building, testing, and debugging UiPath Python runtimes, agents, and automation scripts.
24
+
25
+ ## Overview
26
+
27
+ The Developer Console provides a local environment for developers who are building or experimenting with Python-based UiPath runtimes.
28
+ It integrates with the [`uipath-runtime`](https://pypi.org/project/uipath-runtime/) SDK to execute agents and visualize their behavior in real time using the [`textual`](https://github.com/Textualize/textual) framework.
29
+
30
+ This tool is designed for:
31
+ - Developers building **UiPath agents** or **custom runtime integrations**
32
+ - Python engineers testing **standalone automation scripts** before deployment
33
+ - Contributors exploring **runtime orchestration** and **execution traces**
34
+
35
+ ## Features
36
+
37
+ - Run and inspect Python runtimes interactively
38
+ - View structured logs, output, and OpenTelemetry traces
39
+ - Export and review execution history
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ uv add uipath-dev
45
+ ```
46
+
47
+ ## Development
48
+
49
+ Launch the Developer Console with mocked data:
50
+
51
+ ```bash
52
+ uv run uipath-dev
53
+ ```
54
+
55
+ To run tests:
56
+
57
+ ```bash
58
+ pytest
59
+ ```
@@ -0,0 +1,19 @@
1
+ uipath/dev/__init__.py,sha256=TfVX-yiAQ7t6l8_vzRsxzBFzooqE-Hf6KTzCErD-L6c,12492
2
+ uipath/dev/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ uipath/dev/_demo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ uipath/dev/_demo/mock_runtime.py,sha256=xhnA88yjddqnNMiV_bACO_gAUdsdv-U_Iplb8c73h1M,1950
5
+ uipath/dev/_demo/run_dev_console.py,sha256=J7uEjKYkR9-hDs55rVUEyMLIdrwVDV6R96cxXgP1yXE,390
6
+ uipath/dev/_styles/terminal.tcss,sha256=ktVpKwXIXw2VZp8KIZD6fO9i9NTGvts_icCTxMdzEiY,3240
7
+ uipath/dev/_utils/_exporter.py,sha256=4jElOckC6dQh9qF-ArMnOBPXMMr9foG7uxmi8eyOLEU,4151
8
+ uipath/dev/_utils/_logger.py,sha256=7OByjJAWPEdOHfd6n6OSpsSX2OxK_I47HrGspAR2f-s,2950
9
+ uipath/dev/components/details.py,sha256=MExZBuiyjfEB530KSf8L6KeRUD15m9fbQptl00K5pm0,16483
10
+ uipath/dev/components/history.py,sha256=rCO5Gmnp9LZbVVWt8h8Mf0dyJktpTzTLa4U-xnF5iUI,3422
11
+ uipath/dev/components/json_input.py,sha256=vyvIi1m0yRLUBpXmwwM4InZ1taFJfmTy2Jx0vB_6Gvo,745
12
+ uipath/dev/components/new.py,sha256=QXc9SeEAeJ9M9pjCNLgD-6mY4N4-tWYfqP5oU6TNWlU,5128
13
+ uipath/dev/models/execution.py,sha256=KvODGsSTuWkYZ8zGW0_a_OreL-ByqqEq5tp291-Pz-Q,2606
14
+ uipath/dev/models/messages.py,sha256=3NxTpXrLaOpt7fJd0GJwLL6V_hUQS9_pM97L5zk-Qr8,1580
15
+ uipath_dev-0.0.1.dist-info/METADATA,sha256=hILtVjEM8z-5X3_XgHs5eRipUgxWqXNw4EDKpFfUtTY,1917
16
+ uipath_dev-0.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
+ uipath_dev-0.0.1.dist-info/entry_points.txt,sha256=xuRnlpKV3M6HE-47nOrLF_Y61OouvNen_8msafnuIb8,69
18
+ uipath_dev-0.0.1.dist-info/licenses/LICENSE,sha256=-KBavWXepyDjimmzH5fVAsi-6jNVpIKFc2kZs0Ri4ng,1058
19
+ uipath_dev-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ uipath-dev = uipath.dev._demo.run_dev_console:main
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright 2025 UiPath
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.