crewai-cli 1.14.8a3__tar.gz → 1.14.8a4__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.
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/PKG-INFO +2 -2
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/pyproject.toml +1 -1
- crewai_cli-1.14.8a4/src/crewai_cli/__init__.py +1 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/crew_run_tui.py +238 -2
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/experimental/skills/main.py +29 -1
- crewai_cli-1.14.8a4/src/crewai_cli/kickoff_flow.py +105 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/run_crew.py +10 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/run_declarative_flow.py +68 -8
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/crew/pyproject.toml +1 -1
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/declarative_flow/pyproject.toml +1 -1
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/flow/pyproject.toml +1 -1
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/tool/pyproject.toml +1 -1
- crewai_cli-1.14.8a4/tests/skills/test_safe_extract.py +140 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/test_crew_run_tui.py +46 -0
- crewai_cli-1.14.8a4/tests/test_flow_commands.py +248 -0
- crewai_cli-1.14.8a4/tests/test_kickoff_flow.py +63 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/test_run_crew.py +46 -2
- crewai_cli-1.14.8a3/src/crewai_cli/__init__.py +0 -1
- crewai_cli-1.14.8a3/tests/test_flow_commands.py +0 -117
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/.gitignore +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/README.md +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/add_crew_to_flow.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/authentication/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/authentication/constants.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/authentication/main.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/authentication/providers/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/authentication/providers/auth0.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/authentication/providers/base_provider.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/authentication/providers/entra_id.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/authentication/providers/keycloak.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/authentication/providers/okta.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/authentication/providers/workos.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/authentication/token.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/authentication/utils.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/checkpoint_cli.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/checkpoint_tui.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/cli.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/command.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/config.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/constants.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/create_crew.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/create_flow.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/create_json_crew.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/crew_chat.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/deploy/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/deploy/archive.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/deploy/main.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/deploy/validate.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/enterprise/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/enterprise/main.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/evaluate_crew.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/experimental/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/experimental/skills/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/git.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/install_crew.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/memory_tui.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/organization/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/organization/main.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/plot_flow.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/plus_api.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/provider.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/py.typed +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/remote_template/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/remote_template/main.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/replay_from_task.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/reset_memories_command.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/settings/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/settings/main.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/shared/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/shared/token_manager.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/task_outputs.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/AGENTS.md +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/crew/.gitignore +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/crew/README.md +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/crew/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/crew/config/agents.yaml +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/crew/config/tasks.yaml +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/crew/crew.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/crew/knowledge/user_preference.txt +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/crew/main.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/crew/skills/.gitkeep +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/crew/tools/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/crew/tools/custom_tool.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/declarative_flow/.gitignore +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/declarative_flow/README.md +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/declarative_flow/flow.yaml +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/flow/.gitignore +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/flow/README.md +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/flow/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/flow/crews/content_crew/config/agents.yaml +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/flow/crews/content_crew/config/tasks.yaml +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/flow/crews/content_crew/content_crew.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/flow/main.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/flow/skills/.gitkeep +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/flow/tools/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/flow/tools/custom_tool.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/tool/.gitignore +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/tool/README.md +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/tool/src/{{folder_name}}/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/templates/tool/src/{{folder_name}}/tool.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/tools/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/tools/main.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/train_crew.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/triggers/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/triggers/main.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/tui_picker.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/update_crew.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/user_data.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/utils.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/src/crewai_cli/version.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/authentication/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/authentication/providers/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/authentication/providers/test_auth0.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/authentication/providers/test_entra_id.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/authentication/providers/test_keycloak.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/authentication/providers/test_okta.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/authentication/providers/test_workos.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/authentication/test_auth_main.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/authentication/test_utils.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/deploy/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/deploy/test_archive.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/deploy/test_deploy_main.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/deploy/test_validate.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/enterprise/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/enterprise/test_main.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/experimental/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/experimental/skills/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/experimental/skills/test_main.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/organization/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/organization/test_main.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/skills/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/test_cli.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/test_click_compatibility.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/test_config.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/test_constants.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/test_create_crew.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/test_create_flow.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/test_crew_test.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/test_git.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/test_install_crew.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/test_plus_api.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/test_run_declarative_flow.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/test_settings_command.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/test_token_manager.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/test_train_crew.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/test_utils.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/test_version.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/tools/__init__.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/tools/test_main.py +0 -0
- {crewai_cli-1.14.8a3 → crewai_cli-1.14.8a4}/tests/triggers/test_main.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: crewai-cli
|
|
3
|
-
Version: 1.14.
|
|
3
|
+
Version: 1.14.8a4
|
|
4
4
|
Summary: CLI for CrewAI — scaffold, run, deploy and manage AI agent crews.
|
|
5
5
|
Project-URL: Homepage, https://crewai.com
|
|
6
6
|
Project-URL: Documentation, https://docs.crewai.com
|
|
@@ -10,7 +10,7 @@ Requires-Python: <3.14,>=3.10
|
|
|
10
10
|
Requires-Dist: appdirs~=1.4.4
|
|
11
11
|
Requires-Dist: certifi
|
|
12
12
|
Requires-Dist: click<9,>=8.1.7
|
|
13
|
-
Requires-Dist: crewai-core==1.14.
|
|
13
|
+
Requires-Dist: crewai-core==1.14.8a4
|
|
14
14
|
Requires-Dist: cryptography>=42.0
|
|
15
15
|
Requires-Dist: httpx~=0.28.1
|
|
16
16
|
Requires-Dist: packaging>=23.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.14.8a4"
|
|
@@ -17,7 +17,7 @@ from textual.binding import Binding, BindingType
|
|
|
17
17
|
from textual.containers import Horizontal, Vertical, VerticalScroll
|
|
18
18
|
from textual.css.query import NoMatches
|
|
19
19
|
from textual.screen import ModalScreen
|
|
20
|
-
from textual.widgets import Button, Footer, Header, Static
|
|
20
|
+
from textual.widgets import Button, Footer, Header, Input, Static
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
_SPINNER = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
|
|
@@ -382,6 +382,18 @@ Screen {
|
|
|
382
382
|
height: auto;
|
|
383
383
|
}
|
|
384
384
|
|
|
385
|
+
#conversation-input {
|
|
386
|
+
display: none;
|
|
387
|
+
height: 3;
|
|
388
|
+
border-top: hkey #333333;
|
|
389
|
+
background: #1c1c1c;
|
|
390
|
+
color: #e0e0e0;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
#conversation-input:focus {
|
|
394
|
+
border-top: hkey #1F7982;
|
|
395
|
+
}
|
|
396
|
+
|
|
385
397
|
Header {
|
|
386
398
|
background: #1c1c1c;
|
|
387
399
|
color: #FF5A50;
|
|
@@ -483,6 +495,7 @@ FooterKey .footer-key--key {
|
|
|
483
495
|
total_tasks: int = 0,
|
|
484
496
|
agent_names: list[str] | None = None,
|
|
485
497
|
task_names: list[str] | None = None,
|
|
498
|
+
conversational: bool = False,
|
|
486
499
|
):
|
|
487
500
|
super().__init__()
|
|
488
501
|
self.title = f"CrewAI — {crew_name}"
|
|
@@ -544,6 +557,13 @@ FooterKey .footer-key--key {
|
|
|
544
557
|
self._event_handlers: list[tuple[type, Any]] = []
|
|
545
558
|
|
|
546
559
|
self._crew: Any = None
|
|
560
|
+
self._flow: Any = None
|
|
561
|
+
self._is_conversational = conversational
|
|
562
|
+
self._conversation_messages: list[tuple[str, str]] = []
|
|
563
|
+
self._conversation_turns = 0
|
|
564
|
+
self._conversation_turn_in_progress = False
|
|
565
|
+
self._conversation_previous_defer_trace_finalization: bool | None = None
|
|
566
|
+
self._conversation_exit_commands = {"exit", "quit"}
|
|
547
567
|
self._default_inputs: dict[str, Any] | None = None
|
|
548
568
|
self._crew_result: Any = None
|
|
549
569
|
self._crew_json_path: Any = None
|
|
@@ -566,6 +586,10 @@ FooterKey .footer-key--key {
|
|
|
566
586
|
yield Static(id="task-header")
|
|
567
587
|
with VerticalScroll(id="scroll-area"):
|
|
568
588
|
yield Static(id="main-content")
|
|
589
|
+
yield Input(
|
|
590
|
+
placeholder="Message the flow...",
|
|
591
|
+
id="conversation-input",
|
|
592
|
+
)
|
|
569
593
|
with VerticalScroll(id="log-panel"):
|
|
570
594
|
yield Static(id="log-content")
|
|
571
595
|
yield Footer()
|
|
@@ -574,7 +598,9 @@ FooterKey .footer-key--key {
|
|
|
574
598
|
self._start_time = time.time()
|
|
575
599
|
self._subscribe()
|
|
576
600
|
self._tick_timer = self.set_interval(1 / 8, self._tick)
|
|
577
|
-
if self.
|
|
601
|
+
if self._is_conversational and self._flow:
|
|
602
|
+
self._start_conversational_session()
|
|
603
|
+
elif self._crew:
|
|
578
604
|
self._run_crew_worker()
|
|
579
605
|
elif self._crew_json_path:
|
|
580
606
|
self._load_and_run_worker()
|
|
@@ -725,6 +751,140 @@ FooterKey .footer-key--key {
|
|
|
725
751
|
self._tick_timer = self.set_interval(1 / 2, self._tick)
|
|
726
752
|
self._unsubscribe_if_no_running_memory_save(wait_for_queued=True)
|
|
727
753
|
|
|
754
|
+
# ── Conversational flow execution ───────────────────────
|
|
755
|
+
|
|
756
|
+
def _start_conversational_session(self) -> None:
|
|
757
|
+
from crewai.events.listeners.tracing.utils import (
|
|
758
|
+
set_suppress_tracing_messages,
|
|
759
|
+
set_tui_mode,
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
set_tui_mode(True)
|
|
763
|
+
set_suppress_tracing_messages(True)
|
|
764
|
+
with self._lock:
|
|
765
|
+
self._status = "chatting"
|
|
766
|
+
self._current_step = None
|
|
767
|
+
self._elapsed_frozen = None
|
|
768
|
+
self._conversation_previous_defer_trace_finalization = getattr(
|
|
769
|
+
self._flow, "defer_trace_finalization", False
|
|
770
|
+
)
|
|
771
|
+
self._flow.defer_trace_finalization = True
|
|
772
|
+
|
|
773
|
+
try:
|
|
774
|
+
input_widget = self.query_one("#conversation-input", Input)
|
|
775
|
+
input_widget.display = True
|
|
776
|
+
input_widget.focus()
|
|
777
|
+
except Exception: # noqa: S110
|
|
778
|
+
pass
|
|
779
|
+
|
|
780
|
+
def _finalize_conversational_session(self) -> None:
|
|
781
|
+
if not (self._is_conversational and self._flow):
|
|
782
|
+
return
|
|
783
|
+
try:
|
|
784
|
+
self._flow.finalize_session_traces()
|
|
785
|
+
except Exception: # noqa: S110
|
|
786
|
+
pass
|
|
787
|
+
previous = self._conversation_previous_defer_trace_finalization
|
|
788
|
+
if previous is not None:
|
|
789
|
+
try:
|
|
790
|
+
self._flow.defer_trace_finalization = previous
|
|
791
|
+
except Exception: # noqa: S110
|
|
792
|
+
pass
|
|
793
|
+
|
|
794
|
+
def on_input_submitted(self, event: Input.Submitted) -> None:
|
|
795
|
+
if event.input.id != "conversation-input":
|
|
796
|
+
return
|
|
797
|
+
if not self._is_conversational:
|
|
798
|
+
return
|
|
799
|
+
|
|
800
|
+
message = event.value.strip()
|
|
801
|
+
event.input.value = ""
|
|
802
|
+
if not message:
|
|
803
|
+
return
|
|
804
|
+
if message.lower() in self._conversation_exit_commands:
|
|
805
|
+
self._finalize_conversational_session()
|
|
806
|
+
self._unsubscribe()
|
|
807
|
+
self.exit(self._crew_result)
|
|
808
|
+
return
|
|
809
|
+
if self._conversation_turn_in_progress:
|
|
810
|
+
return
|
|
811
|
+
|
|
812
|
+
with self._lock:
|
|
813
|
+
self._conversation_messages.append(("user", message))
|
|
814
|
+
self._conversation_turn_in_progress = True
|
|
815
|
+
self._conversation_turns += 1
|
|
816
|
+
self._status = "working"
|
|
817
|
+
self._current_step = ("yellow", "Thinking…", "")
|
|
818
|
+
self._is_streaming = False
|
|
819
|
+
self._streaming_text = ""
|
|
820
|
+
self._task_full_output = ""
|
|
821
|
+
self._current_llm_text = ""
|
|
822
|
+
|
|
823
|
+
event.input.disabled = True
|
|
824
|
+
self._run_conversation_turn_worker(message)
|
|
825
|
+
|
|
826
|
+
@work(thread=True, exclusive=True, group="conversation")
|
|
827
|
+
def _run_conversation_turn_worker(self, message: str) -> None:
|
|
828
|
+
from crewai.events.listeners.tracing.utils import (
|
|
829
|
+
set_suppress_tracing_messages,
|
|
830
|
+
set_tui_mode,
|
|
831
|
+
)
|
|
832
|
+
|
|
833
|
+
set_tui_mode(True)
|
|
834
|
+
set_suppress_tracing_messages(True)
|
|
835
|
+
try:
|
|
836
|
+
result = self._flow.handle_turn(message)
|
|
837
|
+
if hasattr(result, "get_full_text") and hasattr(result, "result"):
|
|
838
|
+
for _chunk in result:
|
|
839
|
+
pass
|
|
840
|
+
result = result.result
|
|
841
|
+
self.call_from_thread(self._on_conversation_turn_done, result)
|
|
842
|
+
except Exception as e:
|
|
843
|
+
self.call_from_thread(self._on_conversation_turn_failed, str(e))
|
|
844
|
+
|
|
845
|
+
def _on_conversation_turn_done(self, result: Any) -> None:
|
|
846
|
+
with self._lock:
|
|
847
|
+
output = self._stringify_output(result)
|
|
848
|
+
self._conversation_messages.append(("assistant", output))
|
|
849
|
+
self._crew_result = result
|
|
850
|
+
self._conversation_turn_in_progress = False
|
|
851
|
+
self._status = "chatting"
|
|
852
|
+
self._is_streaming = False
|
|
853
|
+
self._streaming_text = ""
|
|
854
|
+
self._current_step = None
|
|
855
|
+
self._enable_conversation_input()
|
|
856
|
+
self._tick()
|
|
857
|
+
self._scroll_to_result()
|
|
858
|
+
|
|
859
|
+
def _on_conversation_turn_failed(self, error: str) -> None:
|
|
860
|
+
with self._lock:
|
|
861
|
+
self._status = "failed"
|
|
862
|
+
self._error = error
|
|
863
|
+
self._conversation_turn_in_progress = False
|
|
864
|
+
self._is_streaming = False
|
|
865
|
+
self._current_step = None
|
|
866
|
+
self._enable_conversation_input()
|
|
867
|
+
self._tick()
|
|
868
|
+
|
|
869
|
+
def _enable_conversation_input(self) -> None:
|
|
870
|
+
try:
|
|
871
|
+
input_widget = self.query_one("#conversation-input", Input)
|
|
872
|
+
input_widget.disabled = False
|
|
873
|
+
input_widget.focus()
|
|
874
|
+
except Exception: # noqa: S110
|
|
875
|
+
pass
|
|
876
|
+
|
|
877
|
+
def _stringify_output(self, result: Any) -> str:
|
|
878
|
+
raw_result = getattr(result, "raw", result)
|
|
879
|
+
if raw_result is None:
|
|
880
|
+
return ""
|
|
881
|
+
if isinstance(raw_result, str):
|
|
882
|
+
return raw_result
|
|
883
|
+
try:
|
|
884
|
+
return _json.dumps(raw_result, default=str, ensure_ascii=False)
|
|
885
|
+
except TypeError:
|
|
886
|
+
return str(raw_result)
|
|
887
|
+
|
|
728
888
|
# ── Actions ─────────────────────────────────────────────
|
|
729
889
|
|
|
730
890
|
def action_toggle_sidebar(self) -> None:
|
|
@@ -783,6 +943,7 @@ FooterKey .footer-key--key {
|
|
|
783
943
|
self._refresh_log_panel()
|
|
784
944
|
|
|
785
945
|
async def action_quit(self) -> None:
|
|
946
|
+
self._finalize_conversational_session()
|
|
786
947
|
self._unsubscribe()
|
|
787
948
|
self.exit(self._crew_result)
|
|
788
949
|
|
|
@@ -958,6 +1119,30 @@ FooterKey .footer-key--key {
|
|
|
958
1119
|
t = Text()
|
|
959
1120
|
sidebar_width = 30
|
|
960
1121
|
|
|
1122
|
+
if self._is_conversational:
|
|
1123
|
+
t.append(" CONVERSATION\n", style=f"bold {_C_PRIMARY}")
|
|
1124
|
+
t.append("\n")
|
|
1125
|
+
if self._conversation_turn_in_progress:
|
|
1126
|
+
t.append(f" {self._spinner()} ", style=_C_PRIMARY)
|
|
1127
|
+
t.append("Working\n", style=f"bold {_C_TEXT}")
|
|
1128
|
+
elif self._status == "failed":
|
|
1129
|
+
t.append(" ✘ Failed\n", style=_C_RED)
|
|
1130
|
+
else:
|
|
1131
|
+
t.append(" ● Ready\n", style=_C_GREEN)
|
|
1132
|
+
t.append(f" Turns {self._conversation_turns}\n", style=_C_DIM)
|
|
1133
|
+
t.append("\n")
|
|
1134
|
+
t.append(" TOKENS\n", style=f"bold {_C_PRIMARY}")
|
|
1135
|
+
t.append("\n")
|
|
1136
|
+
out = self._output_tokens + self._live_out_tokens
|
|
1137
|
+
t.append(f" ↑ {self._input_tokens:,}\n", style=_C_DIM)
|
|
1138
|
+
t.append(f" ↓ {out:,}\n", style=_C_DIM)
|
|
1139
|
+
t.append("\n")
|
|
1140
|
+
t.append(" COMMANDS\n", style=f"bold {_C_PRIMARY}")
|
|
1141
|
+
t.append("\n")
|
|
1142
|
+
t.append(" quit / exit\n", style=_C_DIM)
|
|
1143
|
+
widget.update(t)
|
|
1144
|
+
return
|
|
1145
|
+
|
|
961
1146
|
t.append(" TASKS\n", style=f"bold {_C_PRIMARY}")
|
|
962
1147
|
t.append("\n")
|
|
963
1148
|
|
|
@@ -1011,6 +1196,22 @@ FooterKey .footer-key--key {
|
|
|
1011
1196
|
widget = self.query_one("#task-header", Static)
|
|
1012
1197
|
t = Text()
|
|
1013
1198
|
|
|
1199
|
+
if self._is_conversational:
|
|
1200
|
+
if self._status == "failed":
|
|
1201
|
+
t.append("✘ ", style=f"bold {_C_RED}")
|
|
1202
|
+
t.append("Failed", style=f"bold {_C_RED}")
|
|
1203
|
+
if self._error:
|
|
1204
|
+
t.append(f"\n{self._error[:120]}", style=_C_RED)
|
|
1205
|
+
elif self._conversation_turn_in_progress:
|
|
1206
|
+
t.append(f"{self._spinner()} ", style=_C_PRIMARY)
|
|
1207
|
+
t.append("Flow is responding", style=f"bold {_C_PRIMARY}")
|
|
1208
|
+
else:
|
|
1209
|
+
t.append("● ", style=f"bold {_C_GREEN}")
|
|
1210
|
+
t.append("Conversational flow ready", style=f"bold {_C_GREEN}")
|
|
1211
|
+
t.append(" Type a message below", style=_C_DIM)
|
|
1212
|
+
widget.update(t)
|
|
1213
|
+
return
|
|
1214
|
+
|
|
1014
1215
|
if self._status == "completed":
|
|
1015
1216
|
elapsed = self._elapsed_frozen or (time.time() - self._start_time)
|
|
1016
1217
|
t.append("✔ ", style=f"bold {_C_GREEN}")
|
|
@@ -1062,6 +1263,41 @@ FooterKey .footer-key--key {
|
|
|
1062
1263
|
t = Text()
|
|
1063
1264
|
should_scroll = False
|
|
1064
1265
|
|
|
1266
|
+
if self._is_conversational:
|
|
1267
|
+
if not self._conversation_messages and not self._is_streaming:
|
|
1268
|
+
t.append(" Start the conversation below.\n", style=_C_MUTED)
|
|
1269
|
+
for role, content in self._conversation_messages:
|
|
1270
|
+
if role == "user":
|
|
1271
|
+
t.append("\n You\n", style=f"bold {_C_TEAL}")
|
|
1272
|
+
else:
|
|
1273
|
+
t.append("\n Assistant\n", style=f"bold {_C_PRIMARY}")
|
|
1274
|
+
rendered = _format_json_in_text(_unescape_text(content))
|
|
1275
|
+
for line in rendered.split("\n"):
|
|
1276
|
+
style = _C_TEXT if role == "assistant" else _C_DIM
|
|
1277
|
+
t.append(f" {line}\n", style=style)
|
|
1278
|
+
|
|
1279
|
+
if self._is_streaming and self._streaming_text:
|
|
1280
|
+
text = _unescape_text(self._filtered_streaming_text())
|
|
1281
|
+
if text.strip():
|
|
1282
|
+
t.append("\n Assistant\n", style=f"bold {_C_PRIMARY}")
|
|
1283
|
+
for line in text.rstrip().split("\n")[-40:]:
|
|
1284
|
+
t.append(f" {line}\n", style=_C_TEXT)
|
|
1285
|
+
should_scroll = True
|
|
1286
|
+
|
|
1287
|
+
if self._status == "failed" and self._error:
|
|
1288
|
+
t.append("\n Error\n", style=f"bold {_C_RED}")
|
|
1289
|
+
t.append(f" {self._error}\n", style=_C_RED)
|
|
1290
|
+
|
|
1291
|
+
widget.update(t)
|
|
1292
|
+
if should_scroll:
|
|
1293
|
+
try:
|
|
1294
|
+
self.query_one("#scroll-area", VerticalScroll).scroll_end(
|
|
1295
|
+
animate=False
|
|
1296
|
+
)
|
|
1297
|
+
except Exception: # noqa: S110
|
|
1298
|
+
pass
|
|
1299
|
+
return
|
|
1300
|
+
|
|
1065
1301
|
# Plan section
|
|
1066
1302
|
if self._plan and self._plan.get("steps"):
|
|
1067
1303
|
plan_title = self._plan.get("plan", "Plan")
|
|
@@ -378,12 +378,40 @@ class SkillCommand(BaseCommand, PlusAPIMixin):
|
|
|
378
378
|
|
|
379
379
|
|
|
380
380
|
def _safe_extractall(tf: tarfile.TarFile, dest: Path) -> None:
|
|
381
|
-
"""Path-traversal-safe extraction for Python
|
|
381
|
+
"""Path-traversal-safe extraction for Python versions without tar filters.
|
|
382
|
+
|
|
383
|
+
Validates both the member's own path and, for symlink/hardlink members,
|
|
384
|
+
the link target. Without the link-target check a malicious archive can
|
|
385
|
+
plant a symlink that escapes ``dest`` (e.g. ``link -> /home/user/.ssh``)
|
|
386
|
+
followed by a regular member written *through* that link
|
|
387
|
+
(``link/authorized_keys``), escaping ``dest`` even though every member
|
|
388
|
+
name resolves inside it. This mirrors the protection that
|
|
389
|
+
``tarfile.extractall(..., filter="data")`` provides when available.
|
|
390
|
+
"""
|
|
382
391
|
dest_resolved = dest.resolve()
|
|
383
392
|
for member in tf.getmembers():
|
|
384
393
|
member_path = (dest / member.name).resolve()
|
|
385
394
|
if not member_path.is_relative_to(dest_resolved):
|
|
386
395
|
raise ValueError(f"Blocked path traversal attempt: {member.name!r}")
|
|
396
|
+
if not (member.isfile() or member.isdir() or member.issym() or member.islnk()):
|
|
397
|
+
raise ValueError(f"Blocked unsupported tar member: {member.name!r}")
|
|
398
|
+
if member.issym() or member.islnk():
|
|
399
|
+
link_target = member.linkname
|
|
400
|
+
# Absolute link targets always escape the destination.
|
|
401
|
+
if os.path.isabs(link_target):
|
|
402
|
+
raise ValueError(
|
|
403
|
+
f"Blocked link target escaping destination: "
|
|
404
|
+
f"{member.name!r} -> {link_target!r}"
|
|
405
|
+
)
|
|
406
|
+
# Hardlink names are relative to the archive root; symlink
|
|
407
|
+
# targets are relative to the member's own directory.
|
|
408
|
+
anchor = dest if member.islnk() else (dest / member.name).parent
|
|
409
|
+
resolved_target = (anchor / link_target).resolve()
|
|
410
|
+
if not resolved_target.is_relative_to(dest_resolved):
|
|
411
|
+
raise ValueError(
|
|
412
|
+
f"Blocked link target escaping destination: "
|
|
413
|
+
f"{member.name!r} -> {link_target!r}"
|
|
414
|
+
)
|
|
387
415
|
tf.extractall(dest) # noqa: S202
|
|
388
416
|
|
|
389
417
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
import inspect
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _project_script_target(script_name: str) -> str | None:
|
|
14
|
+
try:
|
|
15
|
+
from crewai_cli.utils import read_toml
|
|
16
|
+
|
|
17
|
+
pyproject = read_toml()
|
|
18
|
+
except Exception:
|
|
19
|
+
return None
|
|
20
|
+
|
|
21
|
+
target = pyproject.get("project", {}).get("scripts", {}).get(script_name)
|
|
22
|
+
return target if isinstance(target, str) else None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _prepare_project_import_path() -> None:
|
|
26
|
+
cwd = Path.cwd()
|
|
27
|
+
for path in (cwd / "src", cwd):
|
|
28
|
+
path_str = str(path)
|
|
29
|
+
if path.exists() and path_str not in sys.path:
|
|
30
|
+
sys.path.insert(0, path_str)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _load_conversational_flow_from_kickoff_script() -> Any | None:
|
|
34
|
+
target = _project_script_target("kickoff")
|
|
35
|
+
if not target or ":" not in target:
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
module_name, _callable_name = target.split(":", 1)
|
|
39
|
+
_prepare_project_import_path()
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
module = importlib.import_module(module_name)
|
|
43
|
+
from crewai.flow.flow import Flow
|
|
44
|
+
except Exception:
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
for value in vars(module).values():
|
|
48
|
+
if (
|
|
49
|
+
inspect.isclass(value)
|
|
50
|
+
and value is not Flow
|
|
51
|
+
and issubclass(value, Flow)
|
|
52
|
+
and getattr(value, "conversational", False)
|
|
53
|
+
):
|
|
54
|
+
return value()
|
|
55
|
+
|
|
56
|
+
for value in vars(module).values():
|
|
57
|
+
if (
|
|
58
|
+
isinstance(value, Flow)
|
|
59
|
+
and getattr(value, "conversational", False)
|
|
60
|
+
and callable(getattr(value, "handle_turn", None))
|
|
61
|
+
):
|
|
62
|
+
return value
|
|
63
|
+
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _run_conversational_flow_tui(flow: Any) -> Any:
|
|
68
|
+
from crewai_cli.crew_run_tui import CrewRunApp
|
|
69
|
+
|
|
70
|
+
app = CrewRunApp(
|
|
71
|
+
crew_name=getattr(flow, "name", None) or type(flow).__name__,
|
|
72
|
+
conversational=True,
|
|
73
|
+
)
|
|
74
|
+
app._flow = flow
|
|
75
|
+
app.run()
|
|
76
|
+
|
|
77
|
+
if app._status == "failed":
|
|
78
|
+
raise SystemExit(1)
|
|
79
|
+
|
|
80
|
+
return app._crew_result
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def kickoff_flow() -> None:
|
|
84
|
+
"""
|
|
85
|
+
Kickoff the flow by running a command in the UV environment.
|
|
86
|
+
"""
|
|
87
|
+
flow = _load_conversational_flow_from_kickoff_script()
|
|
88
|
+
if flow is not None:
|
|
89
|
+
_run_conversational_flow_tui(flow)
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
command = ["uv", "run", "kickoff"]
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
result = subprocess.run(command, capture_output=False, text=True, check=True) # noqa: S603
|
|
96
|
+
|
|
97
|
+
if result.stderr:
|
|
98
|
+
click.echo(result.stderr, err=True)
|
|
99
|
+
|
|
100
|
+
except subprocess.CalledProcessError as e:
|
|
101
|
+
click.echo(f"An error occurred while running the flow: {e}", err=True)
|
|
102
|
+
click.echo(e.output, err=True)
|
|
103
|
+
|
|
104
|
+
except Exception as e:
|
|
105
|
+
click.echo(f"An unexpected error occurred: {e}", err=True)
|
|
@@ -604,6 +604,16 @@ def _run_flow_project(
|
|
|
604
604
|
run_declarative_flow_in_project_env(definition=definition)
|
|
605
605
|
return
|
|
606
606
|
|
|
607
|
+
from crewai_cli.kickoff_flow import (
|
|
608
|
+
_load_conversational_flow_from_kickoff_script,
|
|
609
|
+
_run_conversational_flow_tui,
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
flow = _load_conversational_flow_from_kickoff_script()
|
|
613
|
+
if flow is not None:
|
|
614
|
+
_run_conversational_flow_tui(flow)
|
|
615
|
+
return
|
|
616
|
+
|
|
607
617
|
_execute_uv_script("kickoff", entity_type="flow")
|
|
608
618
|
|
|
609
619
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
-
from pathlib import Path
|
|
4
|
+
from pathlib import Path, PureWindowsPath
|
|
5
5
|
import subprocess
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
@@ -12,7 +12,7 @@ from crewai_cli.utils import build_env_with_all_tool_credentials
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def run_declarative_flow_in_project_env(
|
|
15
|
-
definition: str, inputs: str | None = None
|
|
15
|
+
definition: str | Path, inputs: str | None = None
|
|
16
16
|
) -> None:
|
|
17
17
|
"""Run a declarative flow inside the project's Python environment."""
|
|
18
18
|
if is_declarative_flow_project_env() or not _has_project_file():
|
|
@@ -25,7 +25,7 @@ def run_declarative_flow_in_project_env(
|
|
|
25
25
|
_execute_declarative_flow_command(["uv", "run", "crewai", "run"])
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def plot_declarative_flow_in_project_env(definition: str) -> None:
|
|
28
|
+
def plot_declarative_flow_in_project_env(definition: str | Path) -> None:
|
|
29
29
|
"""Plot a declarative flow inside the project's Python environment."""
|
|
30
30
|
if is_declarative_flow_project_env() or not _has_project_file():
|
|
31
31
|
plot_declarative_flow(definition=definition)
|
|
@@ -34,7 +34,7 @@ def plot_declarative_flow_in_project_env(definition: str) -> None:
|
|
|
34
34
|
_execute_declarative_flow_command(["uv", "run", "crewai", "flow", "plot"])
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
def run_declarative_flow(definition: str, inputs: str | None = None) -> None:
|
|
37
|
+
def run_declarative_flow(definition: str | Path, inputs: str | None = None) -> None:
|
|
38
38
|
"""Run a declarative flow from a definition path."""
|
|
39
39
|
parsed_inputs = _parse_inputs(inputs)
|
|
40
40
|
|
|
@@ -50,7 +50,7 @@ def run_declarative_flow(definition: str, inputs: str | None = None) -> None:
|
|
|
50
50
|
click.echo(_format_result(result))
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
def plot_declarative_flow(definition: str) -> None:
|
|
53
|
+
def plot_declarative_flow(definition: str | Path) -> None:
|
|
54
54
|
"""Plot a declarative flow from a definition path."""
|
|
55
55
|
try:
|
|
56
56
|
flow = load_declarative_flow(definition)
|
|
@@ -62,7 +62,7 @@ def plot_declarative_flow(definition: str) -> None:
|
|
|
62
62
|
raise SystemExit(1) from exc
|
|
63
63
|
|
|
64
64
|
|
|
65
|
-
def load_declarative_flow(definition: str) -> Any:
|
|
65
|
+
def load_declarative_flow(definition: str | Path) -> Any:
|
|
66
66
|
"""Load a declarative Flow instance from a definition path."""
|
|
67
67
|
try:
|
|
68
68
|
from crewai.flow.flow import Flow
|
|
@@ -102,7 +102,8 @@ def load_declarative_flow(definition: str) -> Any:
|
|
|
102
102
|
|
|
103
103
|
def configured_project_declarative_flow(
|
|
104
104
|
pyproject_data: dict[str, Any] | None = None,
|
|
105
|
-
|
|
105
|
+
project_root: Path | None = None,
|
|
106
|
+
) -> Path | None:
|
|
106
107
|
"""Return the configured declarative flow source for flow projects."""
|
|
107
108
|
if pyproject_data is None:
|
|
108
109
|
try:
|
|
@@ -118,7 +119,66 @@ def configured_project_declarative_flow(
|
|
|
118
119
|
definition = crewai_config.get("definition")
|
|
119
120
|
if not isinstance(definition, str):
|
|
120
121
|
return None
|
|
121
|
-
|
|
122
|
+
definition = definition.strip()
|
|
123
|
+
if not definition:
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
return _resolve_project_definition_path(
|
|
127
|
+
definition=definition,
|
|
128
|
+
project_root=project_root or Path.cwd(),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _resolve_project_definition_path(definition: str, project_root: Path) -> Path:
|
|
133
|
+
definition_path = Path(definition)
|
|
134
|
+
windows_definition_path = PureWindowsPath(definition)
|
|
135
|
+
|
|
136
|
+
if definition.startswith("~"):
|
|
137
|
+
raise click.UsageError(
|
|
138
|
+
"[tool.crewai] definition must be a project-local path; "
|
|
139
|
+
f"got {definition!r}."
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
if definition_path.is_absolute() or windows_definition_path.is_absolute():
|
|
143
|
+
raise click.UsageError(
|
|
144
|
+
"[tool.crewai] definition must be relative to the project root; "
|
|
145
|
+
f"got {definition!r}."
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
root = project_root.resolve(strict=True)
|
|
150
|
+
except OSError as exc:
|
|
151
|
+
raise click.UsageError(
|
|
152
|
+
f"Invalid project root for [tool.crewai] definition: {exc}"
|
|
153
|
+
) from exc
|
|
154
|
+
|
|
155
|
+
candidate = root / definition_path
|
|
156
|
+
try:
|
|
157
|
+
resolved_candidate = candidate.resolve(strict=False)
|
|
158
|
+
except OSError as exc:
|
|
159
|
+
raise click.UsageError(
|
|
160
|
+
f"Invalid [tool.crewai] definition path {definition!r}: {exc}"
|
|
161
|
+
) from exc
|
|
162
|
+
|
|
163
|
+
if not resolved_candidate.is_relative_to(root):
|
|
164
|
+
raise click.UsageError(
|
|
165
|
+
"[tool.crewai] definition must resolve inside the project root; "
|
|
166
|
+
f"got {definition!r}."
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
if not resolved_candidate.exists():
|
|
170
|
+
raise click.UsageError(
|
|
171
|
+
"[tool.crewai] definition must point to an existing file; "
|
|
172
|
+
f"got {definition!r}."
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if not resolved_candidate.is_file():
|
|
176
|
+
raise click.UsageError(
|
|
177
|
+
"[tool.crewai] definition must point to a regular file; "
|
|
178
|
+
f"got {definition!r}."
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
return resolved_candidate
|
|
122
182
|
|
|
123
183
|
|
|
124
184
|
def _execute_declarative_flow_command(command: list[str]) -> None:
|