steplot 0.1.0__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.
- steplot-0.1.0/.github/workflows/ci.yml +31 -0
- steplot-0.1.0/.github/workflows/publish.yml +25 -0
- steplot-0.1.0/.gitignore +30 -0
- steplot-0.1.0/LICENSE +21 -0
- steplot-0.1.0/PKG-INFO +124 -0
- steplot-0.1.0/README.md +108 -0
- steplot-0.1.0/examples/simple_agent.py +37 -0
- steplot-0.1.0/pyproject.toml +52 -0
- steplot-0.1.0/steplot/__init__.py +20 -0
- steplot-0.1.0/steplot/display.py +71 -0
- steplot-0.1.0/steplot/models.py +213 -0
- steplot-0.1.0/steplot/py.typed +0 -0
- steplot-0.1.0/steplot/storage.py +51 -0
- steplot-0.1.0/steplot/tracker.py +268 -0
- steplot-0.1.0/tests/test_tracker.py +562 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.12"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ matrix.python-version }}
|
|
19
|
+
cache: "pip"
|
|
20
|
+
- name: Install dependencies
|
|
21
|
+
run: |
|
|
22
|
+
python -m pip install --upgrade pip
|
|
23
|
+
pip install -e ".[dev]"
|
|
24
|
+
- name: Lint with ruff
|
|
25
|
+
run: ruff check steplot tests examples
|
|
26
|
+
- name: Format check with ruff
|
|
27
|
+
run: ruff format --check steplot tests examples
|
|
28
|
+
- name: Type check with mypy
|
|
29
|
+
run: mypy steplot tests
|
|
30
|
+
- name: Test with pytest
|
|
31
|
+
run: pytest
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
id-token: write # Required for trusted publishing (OIDC) to PyPI.
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
publish:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.12"
|
|
20
|
+
- name: Install build tooling
|
|
21
|
+
run: python -m pip install --upgrade pip build
|
|
22
|
+
- name: Build sdist and wheel
|
|
23
|
+
run: python -m build
|
|
24
|
+
- name: Publish to PyPI
|
|
25
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
steplot-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Distribution / packaging
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
*.egg
|
|
11
|
+
|
|
12
|
+
# Virtual environments
|
|
13
|
+
.venv/
|
|
14
|
+
venv/
|
|
15
|
+
env/
|
|
16
|
+
|
|
17
|
+
# Test & coverage
|
|
18
|
+
.pytest_cache/
|
|
19
|
+
.coverage
|
|
20
|
+
htmlcov/
|
|
21
|
+
|
|
22
|
+
# IDE
|
|
23
|
+
.idea/
|
|
24
|
+
.vscode/
|
|
25
|
+
*.swp
|
|
26
|
+
|
|
27
|
+
# steplot runtime artifacts
|
|
28
|
+
steplot/runs/
|
|
29
|
+
.steplot/
|
|
30
|
+
*.log
|
steplot-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mohammadamin Albooyeh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
steplot-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: steplot
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lightweight observability for AI agents
|
|
5
|
+
Project-URL: Homepage, https://github.com/MohammadaminAlbooyeh/steplot
|
|
6
|
+
Project-URL: Repository, https://github.com/MohammadaminAlbooyeh/steplot
|
|
7
|
+
Project-URL: Issues, https://github.com/MohammadaminAlbooyeh/steplot/issues
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Python: >=3.12
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
13
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
14
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# steplot
|
|
18
|
+
|
|
19
|
+
Lightweight agent step tracker with decorator and logging support.
|
|
20
|
+
|
|
21
|
+
`steplot` helps you visualize and persist the execution flow of your AI agents,
|
|
22
|
+
scripts, or any multi-step pipeline. Wrap functions with `@track`, emit log
|
|
23
|
+
events with `log_event`, and render a tree of steps to the terminal or to a
|
|
24
|
+
JSON file.
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- `@track` decorator -- wraps any function in a timed `Step`.
|
|
29
|
+
- `run_context` / `step_context` -- context managers for nesting.
|
|
30
|
+
- `log_event` -- emit structured events into the current step.
|
|
31
|
+
- `display_run` -- pretty-print a run tree to the terminal.
|
|
32
|
+
- `save_run` / `load_run` -- persist runs as JSON.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install -e .
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Quick start
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from steplot import track, run_context, display_run
|
|
44
|
+
|
|
45
|
+
@track
|
|
46
|
+
def research(topic: str) -> str:
|
|
47
|
+
return f"papers about {topic}"
|
|
48
|
+
|
|
49
|
+
@track
|
|
50
|
+
def summarize(papers: str) -> str:
|
|
51
|
+
return papers.upper()
|
|
52
|
+
|
|
53
|
+
with run_context("my-agent") as run:
|
|
54
|
+
papers = research("transformers")
|
|
55
|
+
summary = summarize(papers)
|
|
56
|
+
|
|
57
|
+
display_run(run)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Output:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
╔══ Run: my-agent [0.02s] ═══
|
|
64
|
+
║
|
|
65
|
+
║ ├─ research [0.01s]
|
|
66
|
+
║ └─ summarize [0.00s]
|
|
67
|
+
╚════════════════════════════╝
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Nested steps
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from steplot import run_context, step_context
|
|
74
|
+
|
|
75
|
+
with run_context("pipeline") as run:
|
|
76
|
+
with step_context("fetch"):
|
|
77
|
+
...
|
|
78
|
+
with step_context("process"):
|
|
79
|
+
with step_context("validate"):
|
|
80
|
+
with step_context("score"):
|
|
81
|
+
...
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Persisting runs
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from steplot import save_run, load_run
|
|
88
|
+
|
|
89
|
+
save_run(run, "steplot/runs/run-1.json")
|
|
90
|
+
loaded = load_run("steplot/runs/run-1.json")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## API reference
|
|
94
|
+
|
|
95
|
+
### `track`
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
@track
|
|
99
|
+
def my_step(): ...
|
|
100
|
+
|
|
101
|
+
@track(step_name="custom", capture_args=True)
|
|
102
|
+
def another_step(x, y): ...
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### `run_context`, `step_context`
|
|
106
|
+
|
|
107
|
+
Context managers that create and automatically finish a `Run` or `Step`.
|
|
108
|
+
|
|
109
|
+
### `log_event`
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
import logging
|
|
113
|
+
from steplot import log_event
|
|
114
|
+
|
|
115
|
+
log_event(logging.INFO, "processing item", item_id=42)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### `display_run`
|
|
119
|
+
|
|
120
|
+
Prints a tree of steps and events to stdout.
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
MIT
|
steplot-0.1.0/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# steplot
|
|
2
|
+
|
|
3
|
+
Lightweight agent step tracker with decorator and logging support.
|
|
4
|
+
|
|
5
|
+
`steplot` helps you visualize and persist the execution flow of your AI agents,
|
|
6
|
+
scripts, or any multi-step pipeline. Wrap functions with `@track`, emit log
|
|
7
|
+
events with `log_event`, and render a tree of steps to the terminal or to a
|
|
8
|
+
JSON file.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- `@track` decorator -- wraps any function in a timed `Step`.
|
|
13
|
+
- `run_context` / `step_context` -- context managers for nesting.
|
|
14
|
+
- `log_event` -- emit structured events into the current step.
|
|
15
|
+
- `display_run` -- pretty-print a run tree to the terminal.
|
|
16
|
+
- `save_run` / `load_run` -- persist runs as JSON.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install -e .
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from steplot import track, run_context, display_run
|
|
28
|
+
|
|
29
|
+
@track
|
|
30
|
+
def research(topic: str) -> str:
|
|
31
|
+
return f"papers about {topic}"
|
|
32
|
+
|
|
33
|
+
@track
|
|
34
|
+
def summarize(papers: str) -> str:
|
|
35
|
+
return papers.upper()
|
|
36
|
+
|
|
37
|
+
with run_context("my-agent") as run:
|
|
38
|
+
papers = research("transformers")
|
|
39
|
+
summary = summarize(papers)
|
|
40
|
+
|
|
41
|
+
display_run(run)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Output:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
╔══ Run: my-agent [0.02s] ═══
|
|
48
|
+
║
|
|
49
|
+
║ ├─ research [0.01s]
|
|
50
|
+
║ └─ summarize [0.00s]
|
|
51
|
+
╚════════════════════════════╝
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Nested steps
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from steplot import run_context, step_context
|
|
58
|
+
|
|
59
|
+
with run_context("pipeline") as run:
|
|
60
|
+
with step_context("fetch"):
|
|
61
|
+
...
|
|
62
|
+
with step_context("process"):
|
|
63
|
+
with step_context("validate"):
|
|
64
|
+
with step_context("score"):
|
|
65
|
+
...
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Persisting runs
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from steplot import save_run, load_run
|
|
72
|
+
|
|
73
|
+
save_run(run, "steplot/runs/run-1.json")
|
|
74
|
+
loaded = load_run("steplot/runs/run-1.json")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## API reference
|
|
78
|
+
|
|
79
|
+
### `track`
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
@track
|
|
83
|
+
def my_step(): ...
|
|
84
|
+
|
|
85
|
+
@track(step_name="custom", capture_args=True)
|
|
86
|
+
def another_step(x, y): ...
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### `run_context`, `step_context`
|
|
90
|
+
|
|
91
|
+
Context managers that create and automatically finish a `Run` or `Step`.
|
|
92
|
+
|
|
93
|
+
### `log_event`
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
import logging
|
|
97
|
+
from steplot import log_event
|
|
98
|
+
|
|
99
|
+
log_event(logging.INFO, "processing item", item_id=42)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### `display_run`
|
|
103
|
+
|
|
104
|
+
Prints a tree of steps and events to stdout.
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Simple example demonstrating steplot with a fake agent."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
from steplot import display_run, log_event, run_context, save_run, step_context, track
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@track
|
|
10
|
+
async def fetch_data(query: str) -> dict:
|
|
11
|
+
await asyncio.sleep(0.1)
|
|
12
|
+
return {"result": f"data for {query}"}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@track
|
|
16
|
+
async def process_data(data: dict) -> str:
|
|
17
|
+
await asyncio.sleep(0.1)
|
|
18
|
+
if not data:
|
|
19
|
+
raise ValueError("empty data")
|
|
20
|
+
return f"processed: {data['result']}"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def main():
|
|
24
|
+
with run_context("simple-agent", agent="demo") as run:
|
|
25
|
+
with step_context("fetch"):
|
|
26
|
+
log_event(logging.INFO, "fetching data", query="test query")
|
|
27
|
+
data = await fetch_data("test query")
|
|
28
|
+
with step_context("process"):
|
|
29
|
+
log_event(logging.INFO, "processing data")
|
|
30
|
+
result = await process_data(data)
|
|
31
|
+
print(f"agent result: {result}")
|
|
32
|
+
|
|
33
|
+
display_run(run)
|
|
34
|
+
save_run(run, "steplot/runs/example-run.json")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "steplot"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Lightweight observability for AI agents"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.12"
|
|
12
|
+
dependencies = []
|
|
13
|
+
|
|
14
|
+
[project.urls]
|
|
15
|
+
Homepage = "https://github.com/MohammadaminAlbooyeh/steplot"
|
|
16
|
+
Repository = "https://github.com/MohammadaminAlbooyeh/steplot"
|
|
17
|
+
Issues = "https://github.com/MohammadaminAlbooyeh/steplot/issues"
|
|
18
|
+
|
|
19
|
+
[project.optional-dependencies]
|
|
20
|
+
dev = ["pytest>=8.0", "ruff>=0.5", "mypy>=1.10"]
|
|
21
|
+
|
|
22
|
+
[tool.hatch.build.targets.wheel]
|
|
23
|
+
packages = ["steplot"]
|
|
24
|
+
|
|
25
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
26
|
+
"steplot/py.typed" = "steplot/py.typed"
|
|
27
|
+
|
|
28
|
+
[tool.pytest.ini_options]
|
|
29
|
+
testpaths = ["tests"]
|
|
30
|
+
python_files = ["test_*.py"]
|
|
31
|
+
addopts = "-v"
|
|
32
|
+
|
|
33
|
+
[tool.ruff]
|
|
34
|
+
line-length = 110
|
|
35
|
+
target-version = "py312"
|
|
36
|
+
src = ["steplot", "tests", "examples"]
|
|
37
|
+
|
|
38
|
+
[tool.ruff.lint]
|
|
39
|
+
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
40
|
+
|
|
41
|
+
[tool.ruff.format]
|
|
42
|
+
quote-style = "double"
|
|
43
|
+
|
|
44
|
+
[tool.mypy]
|
|
45
|
+
python_version = "3.12"
|
|
46
|
+
check_untyped_defs = true
|
|
47
|
+
disallow_untyped_defs = false
|
|
48
|
+
warn_redundant_casts = true
|
|
49
|
+
warn_unused_ignores = true
|
|
50
|
+
warn_return_any = true
|
|
51
|
+
no_implicit_optional = true
|
|
52
|
+
packages = ["steplot"]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""steplot — lightweight agent step tracker with decorator and logging support."""
|
|
2
|
+
|
|
3
|
+
from .display import display_run
|
|
4
|
+
from .storage import load_run, save_run
|
|
5
|
+
from .tracker import get_current_run, get_current_step, log_event, reset, run_context, step_context, track
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"track",
|
|
9
|
+
"run_context",
|
|
10
|
+
"step_context",
|
|
11
|
+
"log_event",
|
|
12
|
+
"get_current_run",
|
|
13
|
+
"get_current_step",
|
|
14
|
+
"reset",
|
|
15
|
+
"display_run",
|
|
16
|
+
"save_run",
|
|
17
|
+
"load_run",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Terminal display for steplot runs — renders the run tree to stdout."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .models import Event, Run, Step, StepStatus
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _status_annotation(step: Step) -> str:
|
|
9
|
+
"""Return a short status suffix for a step when it isn't a clean success."""
|
|
10
|
+
if step.status == StepStatus.FAILED:
|
|
11
|
+
return " ✗"
|
|
12
|
+
if step.status == StepStatus.RUNNING:
|
|
13
|
+
return " ..."
|
|
14
|
+
return ""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _format_duration(seconds: float | None) -> str:
|
|
18
|
+
return f"[{seconds:.2f}s]" if seconds is not None else ""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _render_step(step: Step, prefix: str, is_last: bool, lines: list[str]) -> None:
|
|
22
|
+
"""Recursively append a step and its events/children to ``lines``."""
|
|
23
|
+
branch = "└─ " if is_last else "├─ "
|
|
24
|
+
line = f"{prefix}{branch}{step.name} {_format_duration(step.duration)}{_status_annotation(step)}"
|
|
25
|
+
lines.append(line)
|
|
26
|
+
|
|
27
|
+
# Continuation prefix for events and children beneath this branch.
|
|
28
|
+
child_prefix = prefix + (" " if is_last else "│ ")
|
|
29
|
+
|
|
30
|
+
for event in step.events:
|
|
31
|
+
_render_event(event, child_prefix, lines)
|
|
32
|
+
|
|
33
|
+
for i, child in enumerate(step.children):
|
|
34
|
+
_render_step(child, child_prefix, i == len(step.children) - 1, lines)
|
|
35
|
+
|
|
36
|
+
if step.error:
|
|
37
|
+
lines.append(f"{child_prefix} error: {step.error}")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _render_event(event: Event, prefix: str, lines: list[str]) -> None:
|
|
41
|
+
lines.append(f"{prefix} • {event.message}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def display_run(run: Run) -> None:
|
|
45
|
+
"""Pretty-print a Run tree to stdout.
|
|
46
|
+
|
|
47
|
+
Renders the run name, total duration, and each step (including nested
|
|
48
|
+
children and structured events) as a boxed tree.
|
|
49
|
+
"""
|
|
50
|
+
title = f"Run: {run.name}"
|
|
51
|
+
duration = _format_duration(run.duration)
|
|
52
|
+
if duration:
|
|
53
|
+
title += f" {duration}"
|
|
54
|
+
|
|
55
|
+
lines: list[str] = []
|
|
56
|
+
|
|
57
|
+
for i, step in enumerate(run.steps):
|
|
58
|
+
_render_step(step, " ", i == len(run.steps) - 1, lines)
|
|
59
|
+
|
|
60
|
+
for event in run.events:
|
|
61
|
+
lines.append(f" • {event.message}")
|
|
62
|
+
|
|
63
|
+
top = f"╔══ {title} ═══"
|
|
64
|
+
width = max([len(line) for line in lines] + [len(top) - 2])
|
|
65
|
+
bottom = "╚" + "═" * width + "╝"
|
|
66
|
+
|
|
67
|
+
print(top)
|
|
68
|
+
print("║")
|
|
69
|
+
for line in lines:
|
|
70
|
+
print(f"║{line}")
|
|
71
|
+
print(bottom)
|