crewui 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.
@@ -0,0 +1,82 @@
1
+ # Contributing to crewui
2
+
3
+ crewui is a small, focused package: a Textual TUI for a sequential CrewAI
4
+ pipeline. The bar is that it stays small and stays honest about its one
5
+ dependency seam. These are the rules that keep it there.
6
+
7
+ ## The line the package holds
8
+
9
+ **crewui reads a `Crew` and renders it. It does not reach up into the host.**
10
+
11
+ Everything application-specific — a run id, metrics, pricing, persistence — is
12
+ injected through the `on_start` / `on_complete` / `get_token_cost` callbacks.
13
+ If you find yourself importing anything from a host application, or adding a
14
+ third runtime dependency, stop: that logic belongs in the host, passed in as a
15
+ callback, not in crewui.
16
+
17
+ There are exactly **two** runtime dependencies — `crewai` and `textual` — and
18
+ that is a promise, not an accident. A presentation layer that drags in a
19
+ transitive dependency tree is a liability in someone else's app.
20
+
21
+ ## The one seam that reaches past the public surface
22
+
23
+ Human review leans on `crewai.core.providers.human_input`, which is
24
+ semi-internal. It is isolated to a single factory (`_make_tui_human_input_provider`)
25
+ and a deferred import in `crewui/app.py`, on purpose: when crewai moves it, that
26
+ is the one place to fix. This is also why the crewai pin has a ceiling
27
+ (`<1.16`) — a minor bump is a review event for that seam, not an automatic
28
+ upgrade. See the README's version-pin section.
29
+
30
+ ## Before you commit
31
+
32
+ Run the same stack CI runs, inside a `.venv` with `pip install -e ".[dev]"`:
33
+
34
+ ```bash
35
+ ruff check .
36
+ mypy
37
+ pylint crewui
38
+ pytest --cov=crewui --cov-branch --cov-report=term-missing --cov-fail-under=95
39
+ ```
40
+
41
+ Then `git diff origin/main --stat` to confirm only what you meant to change is
42
+ staged. Running the linters is part of the work, not a formality left for
43
+ review.
44
+
45
+ - **Branch** from current `main`, named `<type>/<short-description>` where
46
+ `<type>` matches the commit type (`feat/`, `fix/`, `docs/`, `chore/`,
47
+ `refactor/`). Do not work on `main`.
48
+ - **Commit** with Conventional Commits — `<type>(<scope>)?: <subject>`,
49
+ lowercase imperative subject. CI checks this, and `cz bump` derives the
50
+ version from it.
51
+ - **Never** force-push, `push --delete`, or `branch -D` a shared or PR branch
52
+ without explicit authorisation. `--force-with-lease` is no exception.
53
+ - Never put session URLs in commit messages or PR bodies.
54
+
55
+ ## Testing discipline
56
+
57
+ The pure helpers (`crewui/_helpers.py`) are unit-tested to full branch
58
+ coverage. The App layer is driven through Textual's `pilot` harness against the
59
+ fake offline crew in `tests/conftest.py` — never against a real `crewai.Crew`,
60
+ so the suite needs no API key and makes no network call. A new code path in the
61
+ App gets a pilot test; a new helper gets a unit test.
62
+
63
+ An assertion that still passes on empty output is not an assertion — check the
64
+ value, not just that nothing raised.
65
+
66
+ ## Releasing
67
+
68
+ Versions are derived from commit messages, not chosen by hand:
69
+
70
+ ```bash
71
+ cz bump # reads commits since the last tag, decides the increment
72
+ git push --follow-tags # the bump commit carries the tag with it
73
+ ```
74
+
75
+ Pushing a `v*` tag is the only thing that triggers `release.yml`: build,
76
+ `twine check`, smoke-test the built wheel on 3.10 and 3.13 (both entry points
77
+ must agree), then publish to PyPI via Trusted Publishing and cut a GitHub
78
+ release. Nothing publishes on an ordinary push or pull request.
79
+
80
+ Do not cut the release through the GitHub UI — `release.yml` runs
81
+ `gh release create` itself, and a hand-made release makes that step fail after
82
+ PyPI has already published.
crewui-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nathan Bottomley-Cook
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.
@@ -0,0 +1,7 @@
1
+ # Ship the suite and docs so a downstream packager can verify the build they
2
+ # are shipping, rather than taking our word for it or fetching from git.
3
+ include README.md CONTRIBUTING.md LICENSE
4
+ recursive-include tests *.py
5
+ # The default stylesheet is package data (see pyproject), but spell it out here
6
+ # too so the sdist carries it even if package-data handling changes.
7
+ recursive-include crewui *.tcss py.typed
crewui-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,164 @@
1
+ Metadata-Version: 2.4
2
+ Name: crewui
3
+ Version: 0.1.0
4
+ Summary: A Textual TUI for sequential CrewAI pipelines
5
+ Author: Nathan Bottomley-Cook
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/coolhandle01/crewui
8
+ Project-URL: Issues, https://github.com/coolhandle01/crewui/issues
9
+ Keywords: crewai,crewai-tools,textual,tui,terminal,console,ai-agents,agentic,pipeline,observability,monitoring
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: Framework :: AsyncIO
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: User Interfaces
22
+ Classifier: Topic :: System :: Monitoring
23
+ Classifier: Topic :: Terminals
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: <3.14,>=3.10
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: crewai<1.16,>=1.15.6
29
+ Requires-Dist: textual>=0.60
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=8; extra == "dev"
32
+ Requires-Dist: pytest-cov>=5; extra == "dev"
33
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
34
+ Requires-Dist: mypy>=1.11; extra == "dev"
35
+ Requires-Dist: pylint>=3.2; extra == "dev"
36
+ Requires-Dist: ruff==0.16.0; extra == "dev"
37
+ Dynamic: license-file
38
+
39
+ # crewui
40
+
41
+ A [Textual](https://textual.textualize.io/) terminal UI for a sequential
42
+ [CrewAI](https://github.com/crewAIInc/crewAI) pipeline. You build a `Crew`;
43
+ crewui renders it — a sidebar task tracker, a live agent-output stream, a
44
+ pipeline log, and a token/cost summary — and, when a task asks for human
45
+ review, routes the feedback prompt to an input box instead of a blocking
46
+ terminal `input()`.
47
+
48
+ crewui is a presentation layer and nothing more. It reads a `Crew` and touches
49
+ only two things beyond it: the step/task callback contract and, for human
50
+ review, one semi-internal crewai provider hook. Everything application-specific
51
+ — where a run id is recorded, how run metrics are computed and priced — is
52
+ injected through callbacks, so the package never reaches up into your app.
53
+
54
+ ## Install
55
+
56
+ ```bash
57
+ pipx install crewui # the CLI + the demo, isolated
58
+ # or, as a library in your project:
59
+ pip install crewui
60
+ ```
61
+
62
+ See it run without writing any code or setting an API key:
63
+
64
+ ```bash
65
+ crewui demo # drives a scripted three-phase pipeline in the TUI
66
+ crewui demo --dry-run # render the layout without walking the pipeline
67
+ ```
68
+
69
+ The demo is fully offline — no LLM call, no network — so it doubles as a smoke
70
+ test that the install works.
71
+
72
+ ## Use it in your app
73
+
74
+ ```python
75
+ from crewui import CrewAIPipelineTUI
76
+
77
+ CrewAIPipelineTUI(
78
+ crew=build_my_crew(), # any sequential crewai.Crew
79
+ record_prefix="myapp", # log records under this name render in the agent pane
80
+ pipeline_name="My Pipeline", # sidebar title
81
+ ).run()
82
+ ```
83
+
84
+ The sidebar reads each task's display name (`Task.name`) and agent role straight
85
+ off `crew.tasks`, so wiring the crew is the whole job — there is no separate task
86
+ map to maintain.
87
+
88
+ ### Injecting host behaviour
89
+
90
+ Three optional callbacks carry everything crewui deliberately does not know:
91
+
92
+ | Callback | When it fires | Typical use |
93
+ |---|---|---|
94
+ | `on_start()` | worker thread, right before `kickoff()` | bind a run id, stamp the start time |
95
+ | `on_complete(result)` | right after `kickoff()` | persist run metrics |
96
+ | `get_token_cost(input_tokens, output_tokens)` | when rendering the summary | return the USD estimate to display |
97
+
98
+ A save failure in `on_complete` is swallowed and surfaced in the pipeline log —
99
+ persistence never takes the UI down.
100
+
101
+ ### Theming
102
+
103
+ `CrewAIPipelineTUI` ships a usable dark theme and owns it as an absolute
104
+ `CSS_PATH`. A subclass ships its own look by setting its own `CSS_PATH`:
105
+
106
+ ```python
107
+ class MyTUI(CrewAIPipelineTUI):
108
+ CSS_PATH = "my_app.tcss"
109
+ ```
110
+
111
+ ### Human review
112
+
113
+ A `Task(human_input=True)` pauses the run for feedback. crewui opens the input
114
+ box, parks the worker thread until you submit, and feeds what you type back into
115
+ crewai's feedback loop (empty input — just Enter — means "accept"). The routing
116
+ leans on `crewai.core.providers.human_input`, a semi-internal API isolated to a
117
+ single factory in `crewui/app.py` so there is one place to fix if it moves
118
+ between crewai versions.
119
+
120
+ ## The crewai version pin, and the fork
121
+
122
+ crewui declares `crewai>=1.15.6,<1.16`.
123
+
124
+ - **The floor (1.15.6)** is the first release where the agent executor's
125
+ `ask_for_human_input` compatibility property is back. A 1.14.5 executor
126
+ regression crashed `human_input=True` with
127
+ `'AgentExecutor' object has no attribute 'ask_for_human_input'`; below the
128
+ floor, the human-review feature does not work.
129
+ - **The ceiling (<1.16)** is deliberate, not lazy. The human-input routing is
130
+ the one place crewui reaches past crewai's public surface, so a minor bump is
131
+ a review event: re-check that seam, then move the ceiling.
132
+
133
+ There is a **second, separate** human-input bug that a stock PyPI crewai still
134
+ carries: the feedback prompt tells the operator to review "the Final Result
135
+ above", but crewai only renders that result when `verbose=True` — so under a TUI
136
+ with verbose off, the operator is asked to review output that was never printed.
137
+ The downstream project this library was extracted from pins a
138
+ [crewai fork](https://github.com/coolhandle01/crewai) that adds a
139
+ Result-for-Review panel so the referenced output is always shown. **crewui does
140
+ not pin that fork** — it works against stock crewai, and simply streams each
141
+ answer into the agent-output pane before the review gate opens, which is the
142
+ generic equivalent of what the fork's panel does. If you need the fork's exact
143
+ behaviour, pin it in *your* application, not here.
144
+
145
+ ## Development
146
+
147
+ ```bash
148
+ python -m venv .venv && . .venv/bin/activate
149
+ pip install -e ".[dev]"
150
+
151
+ ruff check .
152
+ mypy
153
+ pylint crewui
154
+ pytest --cov=crewui --cov-branch --cov-report=term-missing --cov-fail-under=95
155
+ ```
156
+
157
+ The pure helpers in `crewui/_helpers.py` are unit-tested; the App itself is
158
+ driven through Textual's `pilot` harness in `tests/test_app.py` — status
159
+ transitions, the dry-run preview, the metrics block, the error path, and the
160
+ human-review gate all run against a fake offline crew.
161
+
162
+ ## Licence
163
+
164
+ MIT. See [LICENSE](LICENSE).
crewui-0.1.0/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # crewui
2
+
3
+ A [Textual](https://textual.textualize.io/) terminal UI for a sequential
4
+ [CrewAI](https://github.com/crewAIInc/crewAI) pipeline. You build a `Crew`;
5
+ crewui renders it — a sidebar task tracker, a live agent-output stream, a
6
+ pipeline log, and a token/cost summary — and, when a task asks for human
7
+ review, routes the feedback prompt to an input box instead of a blocking
8
+ terminal `input()`.
9
+
10
+ crewui is a presentation layer and nothing more. It reads a `Crew` and touches
11
+ only two things beyond it: the step/task callback contract and, for human
12
+ review, one semi-internal crewai provider hook. Everything application-specific
13
+ — where a run id is recorded, how run metrics are computed and priced — is
14
+ injected through callbacks, so the package never reaches up into your app.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pipx install crewui # the CLI + the demo, isolated
20
+ # or, as a library in your project:
21
+ pip install crewui
22
+ ```
23
+
24
+ See it run without writing any code or setting an API key:
25
+
26
+ ```bash
27
+ crewui demo # drives a scripted three-phase pipeline in the TUI
28
+ crewui demo --dry-run # render the layout without walking the pipeline
29
+ ```
30
+
31
+ The demo is fully offline — no LLM call, no network — so it doubles as a smoke
32
+ test that the install works.
33
+
34
+ ## Use it in your app
35
+
36
+ ```python
37
+ from crewui import CrewAIPipelineTUI
38
+
39
+ CrewAIPipelineTUI(
40
+ crew=build_my_crew(), # any sequential crewai.Crew
41
+ record_prefix="myapp", # log records under this name render in the agent pane
42
+ pipeline_name="My Pipeline", # sidebar title
43
+ ).run()
44
+ ```
45
+
46
+ The sidebar reads each task's display name (`Task.name`) and agent role straight
47
+ off `crew.tasks`, so wiring the crew is the whole job — there is no separate task
48
+ map to maintain.
49
+
50
+ ### Injecting host behaviour
51
+
52
+ Three optional callbacks carry everything crewui deliberately does not know:
53
+
54
+ | Callback | When it fires | Typical use |
55
+ |---|---|---|
56
+ | `on_start()` | worker thread, right before `kickoff()` | bind a run id, stamp the start time |
57
+ | `on_complete(result)` | right after `kickoff()` | persist run metrics |
58
+ | `get_token_cost(input_tokens, output_tokens)` | when rendering the summary | return the USD estimate to display |
59
+
60
+ A save failure in `on_complete` is swallowed and surfaced in the pipeline log —
61
+ persistence never takes the UI down.
62
+
63
+ ### Theming
64
+
65
+ `CrewAIPipelineTUI` ships a usable dark theme and owns it as an absolute
66
+ `CSS_PATH`. A subclass ships its own look by setting its own `CSS_PATH`:
67
+
68
+ ```python
69
+ class MyTUI(CrewAIPipelineTUI):
70
+ CSS_PATH = "my_app.tcss"
71
+ ```
72
+
73
+ ### Human review
74
+
75
+ A `Task(human_input=True)` pauses the run for feedback. crewui opens the input
76
+ box, parks the worker thread until you submit, and feeds what you type back into
77
+ crewai's feedback loop (empty input — just Enter — means "accept"). The routing
78
+ leans on `crewai.core.providers.human_input`, a semi-internal API isolated to a
79
+ single factory in `crewui/app.py` so there is one place to fix if it moves
80
+ between crewai versions.
81
+
82
+ ## The crewai version pin, and the fork
83
+
84
+ crewui declares `crewai>=1.15.6,<1.16`.
85
+
86
+ - **The floor (1.15.6)** is the first release where the agent executor's
87
+ `ask_for_human_input` compatibility property is back. A 1.14.5 executor
88
+ regression crashed `human_input=True` with
89
+ `'AgentExecutor' object has no attribute 'ask_for_human_input'`; below the
90
+ floor, the human-review feature does not work.
91
+ - **The ceiling (<1.16)** is deliberate, not lazy. The human-input routing is
92
+ the one place crewui reaches past crewai's public surface, so a minor bump is
93
+ a review event: re-check that seam, then move the ceiling.
94
+
95
+ There is a **second, separate** human-input bug that a stock PyPI crewai still
96
+ carries: the feedback prompt tells the operator to review "the Final Result
97
+ above", but crewai only renders that result when `verbose=True` — so under a TUI
98
+ with verbose off, the operator is asked to review output that was never printed.
99
+ The downstream project this library was extracted from pins a
100
+ [crewai fork](https://github.com/coolhandle01/crewai) that adds a
101
+ Result-for-Review panel so the referenced output is always shown. **crewui does
102
+ not pin that fork** — it works against stock crewai, and simply streams each
103
+ answer into the agent-output pane before the review gate opens, which is the
104
+ generic equivalent of what the fork's panel does. If you need the fork's exact
105
+ behaviour, pin it in *your* application, not here.
106
+
107
+ ## Development
108
+
109
+ ```bash
110
+ python -m venv .venv && . .venv/bin/activate
111
+ pip install -e ".[dev]"
112
+
113
+ ruff check .
114
+ mypy
115
+ pylint crewui
116
+ pytest --cov=crewui --cov-branch --cov-report=term-missing --cov-fail-under=95
117
+ ```
118
+
119
+ The pure helpers in `crewui/_helpers.py` are unit-tested; the App itself is
120
+ driven through Textual's `pilot` harness in `tests/test_app.py` — status
121
+ transitions, the dry-run preview, the metrics block, the error path, and the
122
+ human-review gate all run against a fake offline crew.
123
+
124
+ ## Licence
125
+
126
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,42 @@
1
+ """crewui - a Textual TUI for any sequential CrewAI crew.
2
+
3
+ ``CrewAIPipelineTUI`` is a Textual App that renders a sidebar task tracker, an
4
+ agent output log, and a pipeline log for a CrewAI ``Crew``. The host builds the
5
+ crew and hands it in; the TUI stays out of crew construction entirely.
6
+
7
+ The package depends only on ``crewai`` and ``textual``. Live metrics come
8
+ straight from CrewAI (token usage off ``kickoff()``'s result); everything
9
+ host-specific is delegated to injected callbacks, so nothing here reaches up
10
+ into the host application:
11
+
12
+ - ``on_start()`` - fired in the worker thread right before kickoff (e.g. to
13
+ bind a run id and stamp the start time)
14
+ - ``on_complete(result)`` - fired right after kickoff (e.g. to persist run
15
+ metrics)
16
+ - ``get_token_cost(input_tokens, output_tokens)`` - returns the USD estimate to
17
+ display; cost is not a CrewAI metric, so the host supplies it
18
+
19
+ Human review (``Task(human_input=True)``) is routed to the TUI's input box
20
+ instead of a blocking terminal ``input()`` - see ``crewui.app``.
21
+
22
+ Typical usage::
23
+
24
+ from crewui import CrewAIPipelineTUI
25
+
26
+ CrewAIPipelineTUI(
27
+ crew=build_my_crew(),
28
+ record_prefix="myapp",
29
+ pipeline_name="My Pipeline",
30
+ ).run()
31
+
32
+ The class owns a default theme; a derived class ships its own look by setting
33
+ its own ``CSS_PATH``.
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ from crewui.app import CrewAIPipelineTUI
39
+
40
+ __version__ = "0.1.0"
41
+
42
+ __all__ = ["CrewAIPipelineTUI", "__version__"]
@@ -0,0 +1,15 @@
1
+ """``python -m crewui`` entry point.
2
+
3
+ Kept to two lines and a guard so it mirrors the console script exactly: both
4
+ routes go through ``crewui.cli:main``, so a wheel cannot ship one working and
5
+ the other broken.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import sys
11
+
12
+ from crewui.cli import main
13
+
14
+ if __name__ == "__main__":
15
+ sys.exit(main())
@@ -0,0 +1,78 @@
1
+ """crewui._helpers - Pure functions extracted from CrewAIPipelineTUI.
2
+
3
+ The TUI class itself (CrewAIPipelineTUI) is App + widgets + threading and needs
4
+ a textual.pilot harness to test properly. The pure-logic helpers it relies on -
5
+ truncation, log routing, step-message formatting, sidebar layout, metrics
6
+ block formatting - have no Textual or threading dependency and live here so
7
+ they can be branch-covered by ordinary unit tests.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import Any
13
+
14
+ from crewai.agents.parser import AgentAction, AgentFinish
15
+
16
+
17
+ def truncate(text: str, limit: int) -> str:
18
+ """Return ``text`` truncated to ``limit`` characters.
19
+
20
+ Returns the input unchanged when it is already at or below the limit.
21
+ """
22
+ return text[:limit] if len(text) > limit else text
23
+
24
+
25
+ def route_log_record(record_name: str, prefix: str) -> str:
26
+ """Decide which TUI log pane a logging record belongs in.
27
+
28
+ Returns ``"agent"`` when ``record_name`` starts with ``prefix`` (the host
29
+ app's record prefix), else ``"crew"``.
30
+ """
31
+ return "agent" if record_name.startswith(prefix) else "crew"
32
+
33
+
34
+ def task_layout(tasks: list[Any]) -> list[tuple[str, str]]:
35
+ """Build the sidebar entries for a sequential pipeline.
36
+
37
+ For each task that has an assigned agent, return a ``(heading, role)``
38
+ pair in pipeline order. ``heading`` is the task's display name
39
+ (``Task.name``), falling back to the agent role when a task carries no
40
+ name; ``role`` is the agent role shown on the task's status row. Because
41
+ the heading is per-task rather than per-agent, an agent that runs more than
42
+ one task in the pipeline gets a distinct heading for each.
43
+
44
+ Tasks with no agent are skipped, so a partially-wired crew never raises.
45
+ """
46
+ layout: list[tuple[str, str]] = []
47
+ for task in tasks:
48
+ agent = getattr(task, "agent", None)
49
+ if agent is None:
50
+ continue
51
+ role = agent.role
52
+ layout.append((task.name or role, role))
53
+ return layout
54
+
55
+
56
+ def format_metrics_block(total_tokens: int, estimated_cost_usd: float, status: str = "done") -> str:
57
+ """Render the fixed-width metrics summary shown in the sidebar."""
58
+ return f" Tokens: {total_tokens:,}\n Cost: ${estimated_cost_usd:.4f}\n Status: {status}"
59
+
60
+
61
+ def format_step_message(step: object) -> str:
62
+ """Format a CrewAI step (AgentAction / AgentFinish / other) as rich-text.
63
+
64
+ AgentAction yields a Thought + tool-call line and an optional result block.
65
+ AgentFinish yields an Answer line. Anything else is rendered as its
66
+ truncated ``str()``. Trusts the crewai parser types - the caller's
67
+ callback is responsible for swallowing any unexpected exceptions, since
68
+ the step-callback contract is fire-and-forget telemetry.
69
+ """
70
+ if isinstance(step, AgentAction):
71
+ tool_call = f"[cyan]> {step.tool}[/cyan]({truncate(step.tool_input, 120)})"
72
+ msg = f"[yellow]Thought:[/yellow] {step.thought}\n{tool_call}"
73
+ if step.result:
74
+ msg += f"\n[dim]{truncate(step.result, 300)}[/dim]"
75
+ return msg
76
+ if isinstance(step, AgentFinish):
77
+ return f"[bold green]Answer:[/bold green] {truncate(str(step.output), 500)}"
78
+ return truncate(str(step), 300)