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.
- crewui-0.1.0/CONTRIBUTING.md +82 -0
- crewui-0.1.0/LICENSE +21 -0
- crewui-0.1.0/MANIFEST.in +7 -0
- crewui-0.1.0/PKG-INFO +164 -0
- crewui-0.1.0/README.md +126 -0
- crewui-0.1.0/crewui/__init__.py +42 -0
- crewui-0.1.0/crewui/__main__.py +15 -0
- crewui-0.1.0/crewui/_helpers.py +78 -0
- crewui-0.1.0/crewui/app.py +308 -0
- crewui-0.1.0/crewui/cli.py +65 -0
- crewui-0.1.0/crewui/default.tcss +105 -0
- crewui-0.1.0/crewui/demo.py +158 -0
- crewui-0.1.0/crewui/py.typed +0 -0
- crewui-0.1.0/crewui.egg-info/PKG-INFO +164 -0
- crewui-0.1.0/crewui.egg-info/SOURCES.txt +24 -0
- crewui-0.1.0/crewui.egg-info/dependency_links.txt +1 -0
- crewui-0.1.0/crewui.egg-info/entry_points.txt +2 -0
- crewui-0.1.0/crewui.egg-info/requires.txt +10 -0
- crewui-0.1.0/crewui.egg-info/top_level.txt +1 -0
- crewui-0.1.0/pyproject.toml +189 -0
- crewui-0.1.0/setup.cfg +4 -0
- crewui-0.1.0/tests/__init__.py +0 -0
- crewui-0.1.0/tests/conftest.py +100 -0
- crewui-0.1.0/tests/test_app.py +278 -0
- crewui-0.1.0/tests/test_cli.py +88 -0
- crewui-0.1.0/tests/test_helpers.py +133 -0
|
@@ -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.
|
crewui-0.1.0/MANIFEST.in
ADDED
|
@@ -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)
|