mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-08-06 06:21:43 +00:00
Compare commits
1 Commits
docs/agent
...
cursor/fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f99bafa429 |
@@ -36,73 +36,24 @@ crewai [COMMAND] [OPTIONS] [ARGUMENTS]
|
||||
|
||||
### 1. Create
|
||||
|
||||
Create a new crew, flow, tool, skill, or template project.
|
||||
Create a new crew or flow.
|
||||
|
||||
```shell Terminal
|
||||
crewai create [OPTIONS] TYPE NAME
|
||||
```
|
||||
|
||||
- `TYPE`: `crew`, `flow`, `tool`, `skill`, or `template`
|
||||
- `NAME`: Name of the project, tool handle, skill, or template
|
||||
- `TYPE`: Choose between "crew" or "flow"
|
||||
- `NAME`: Name of the crew or flow
|
||||
|
||||
#### Crew
|
||||
Example:
|
||||
|
||||
```shell Terminal
|
||||
crewai create crew my_new_crew
|
||||
crewai create crew my_new_crew --classic
|
||||
crewai create flow my_new_flow
|
||||
```
|
||||
|
||||
By default, `crewai create crew` creates a JSON-first crew project with `crew.jsonc` and `agents/*.jsonc`. Use `crewai create crew my_new_crew --classic` only when you want the older Python/YAML scaffold with `crew.py`, `config/agents.yaml`, and `config/tasks.yaml`.
|
||||
|
||||
#### Flow
|
||||
|
||||
```shell Terminal
|
||||
crewai create flow my_new_flow
|
||||
crewai create flow my_new_flow --declarative
|
||||
```
|
||||
|
||||
#### Tool
|
||||
|
||||
Scaffold a custom tool repository:
|
||||
|
||||
```shell Terminal
|
||||
crewai create tool my_tool
|
||||
```
|
||||
|
||||
#### Skill
|
||||
|
||||
Scaffold an agent skill. Inside a crew project (where `pyproject.toml` exists), the skill is created under `./skills/`:
|
||||
|
||||
```shell Terminal
|
||||
crewai create skill my-skill
|
||||
crewai create skill my-skill --no-project
|
||||
```
|
||||
|
||||
Use `--no-project` to create the skill in the current directory instead of `./skills/`.
|
||||
|
||||
#### Template
|
||||
|
||||
Add a remote project template to the current directory:
|
||||
|
||||
```shell Terminal
|
||||
crewai create template my-template
|
||||
crewai create template my-template --output-dir custom_dir
|
||||
```
|
||||
|
||||
Use `--output-dir` to override the output folder name (defaults to the template name).
|
||||
|
||||
#### Deprecated create aliases
|
||||
|
||||
These older commands still work but print a yellow deprecation warning. Prefer the `crewai create <type>` forms above.
|
||||
|
||||
| Deprecated | Use instead |
|
||||
| :--- | :--- |
|
||||
| `crewai tool create <handle>` | `crewai create tool <handle>` |
|
||||
| `crewai skill create <name>` | `crewai create skill <name>` |
|
||||
| `crewai template add <name>` | `crewai create template <name>` |
|
||||
|
||||
Lifecycle commands are unchanged — for example `crewai tool install`, `crewai skill publish`, and `crewai template list` stay under their resource groups.
|
||||
|
||||
### 2. Version
|
||||
|
||||
Show the installed version of CrewAI.
|
||||
|
||||
@@ -32,10 +32,10 @@ You often need **both**: skills for expertise, tools for action. They are config
|
||||
The CLI is the supported way to create a skill — it scaffolds the directory layout and a valid `SKILL.md` for you:
|
||||
|
||||
```shell Terminal
|
||||
crewai create skill code-review
|
||||
crewai skill create code-review
|
||||
```
|
||||
|
||||
Inside a crew project (where `pyproject.toml` lives) this creates `./skills/code-review/`; outside a project it creates `./code-review/` in the current directory (you can force that behavior with `--no-project` on `crewai create skill`):
|
||||
Inside a crew project (where `pyproject.toml` lives) this creates `./skills/code-review/`; outside a project it creates `./code-review/` in the current directory (you can force that behavior with `--no-project`):
|
||||
|
||||
```
|
||||
skills/
|
||||
@@ -178,16 +178,12 @@ agent = Agent(
|
||||
|
||||
## Creating, Publishing, and Installing Skills
|
||||
|
||||
Skills have a full lifecycle managed by the CLI: **create them with `crewai create skill`, publish them with `crewai skill publish`** — hand-rolling directories works for local experiments, but the CLI is the intended workflow and keeps your skill layout and frontmatter valid.
|
||||
|
||||
<Note>
|
||||
`crewai skill create` is deprecated and still works with a warning. Use `crewai create skill` instead.
|
||||
</Note>
|
||||
Skills have a full lifecycle managed by the CLI: **create them with `crewai skill create`, publish them with `crewai skill publish`** — hand-rolling directories works for local experiments, but the CLI is the intended workflow and keeps your skill layout and frontmatter valid.
|
||||
|
||||
### Create
|
||||
|
||||
```shell Terminal
|
||||
crewai create skill my-skill
|
||||
crewai skill create my-skill
|
||||
```
|
||||
|
||||
Scaffolds the directory (into `./skills/` inside a crew project) with a template `SKILL.md`, plus empty `scripts/`, `references/`, and `assets/` directories. Edit `SKILL.md` to define the instructions.
|
||||
|
||||
@@ -21,13 +21,9 @@ crewai create crew my_crew
|
||||
crewai create flow my_flow
|
||||
|
||||
# Tool repository
|
||||
crewai create tool my_tool
|
||||
crewai tool create my_tool
|
||||
```
|
||||
|
||||
<Note>
|
||||
`crewai tool create` is deprecated and still works with a warning. Use `crewai create tool` instead.
|
||||
</Note>
|
||||
|
||||
## Tool Setup: Point Assistants to AGENTS.md
|
||||
|
||||
### Codex
|
||||
|
||||
@@ -19,7 +19,6 @@ from crewai_cli.utils import (
|
||||
enable_prompt_line_editing,
|
||||
is_dmn_mode_enabled,
|
||||
read_toml,
|
||||
warn_deprecated_command,
|
||||
)
|
||||
|
||||
|
||||
@@ -138,10 +137,7 @@ def uv(uv_args: tuple[str, ...]) -> None:
|
||||
|
||||
@crewai.command()
|
||||
@click.argument(
|
||||
"type",
|
||||
required=False,
|
||||
default=None,
|
||||
type=click.Choice(["crew", "flow", "tool", "skill", "template"]),
|
||||
"type", required=False, default=None, type=click.Choice(["crew", "flow"])
|
||||
)
|
||||
@click.argument("name", required=False, default=None)
|
||||
@click.option("--provider", type=str, help="The provider to use for the crew")
|
||||
@@ -156,21 +152,6 @@ def uv(uv_args: tuple[str, ...]) -> None:
|
||||
is_flag=True,
|
||||
help="Create a declarative Flow project instead of a Python Flow project",
|
||||
)
|
||||
@click.option(
|
||||
"--no-project",
|
||||
"in_project",
|
||||
is_flag=True,
|
||||
default=True,
|
||||
flag_value=False,
|
||||
help="Skill only: create in current dir instead of ./skills/",
|
||||
)
|
||||
@click.option(
|
||||
"-o",
|
||||
"--output-dir",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Template only: directory name for the template (defaults to template name)",
|
||||
)
|
||||
def create(
|
||||
type: str | None,
|
||||
name: str | None,
|
||||
@@ -178,17 +159,14 @@ def create(
|
||||
skip_provider: bool = False,
|
||||
classic: bool = False,
|
||||
declarative: bool = False,
|
||||
in_project: bool = True,
|
||||
output_dir: str | None = None,
|
||||
) -> None:
|
||||
"""Create a new crew, flow, tool, skill, or template."""
|
||||
"""Create a new crew, or flow."""
|
||||
dmn_mode = is_dmn_mode_enabled()
|
||||
if not type:
|
||||
if dmn_mode:
|
||||
raise click.UsageError(
|
||||
"TYPE is required when CREWAI_DMN is set. "
|
||||
"Use `crewai create <type> <name>` where type is one of: "
|
||||
"crew, flow, tool, skill, template."
|
||||
"Use `crewai create crew <name>` or `crewai create flow <name>`."
|
||||
)
|
||||
from crewai_cli.tui_picker import pick
|
||||
|
||||
@@ -198,9 +176,6 @@ def create(
|
||||
"flow",
|
||||
"A deterministic workflow with full control over agents and crews",
|
||||
),
|
||||
("tool", "A custom tool for the CrewAI Tool Repository"),
|
||||
("skill", "An agent skill with instructions and optional assets"),
|
||||
("template", "A remote project template from the CrewAI gallery"),
|
||||
]
|
||||
type = pick("What would you like to create?", options)
|
||||
if type is None:
|
||||
@@ -214,36 +189,9 @@ def create(
|
||||
click.style(f" Name of your {type}", fg="cyan", bold=True),
|
||||
prompt_suffix=click.style(" › ", fg="bright_white"), # noqa: RUF001
|
||||
)
|
||||
if dmn_mode and type == "crew":
|
||||
if dmn_mode:
|
||||
skip_provider = True
|
||||
if not in_project and type != "skill":
|
||||
raise click.UsageError("--no-project can only be used with skill projects.")
|
||||
if output_dir is not None and type != "template":
|
||||
raise click.UsageError("--output-dir can only be used with template projects.")
|
||||
if type == "tool":
|
||||
if declarative or classic or provider is not None or skip_provider:
|
||||
raise click.UsageError(
|
||||
"Crew and flow options cannot be used with tool projects."
|
||||
)
|
||||
from crewai_cli.tools.main import ToolCommand
|
||||
|
||||
ToolCommand().create(name)
|
||||
elif type == "skill":
|
||||
if declarative or classic or provider is not None or skip_provider:
|
||||
raise click.UsageError(
|
||||
"Crew and flow options cannot be used with skill projects."
|
||||
)
|
||||
from crewai_cli.skills.main import SkillCommand
|
||||
|
||||
SkillCommand().create(name, in_project=in_project)
|
||||
elif type == "template":
|
||||
if declarative or classic or provider is not None or skip_provider:
|
||||
raise click.UsageError(
|
||||
"Crew and flow options cannot be used with template projects."
|
||||
)
|
||||
template_cmd = TemplateCommand()
|
||||
template_cmd.add_template(name, output_dir)
|
||||
elif type == "crew":
|
||||
if type == "crew":
|
||||
if declarative:
|
||||
raise click.UsageError("--declarative can only be used with flow projects")
|
||||
if classic:
|
||||
@@ -259,10 +207,7 @@ def create(
|
||||
|
||||
create_flow(name, declarative=declarative)
|
||||
else:
|
||||
click.secho(
|
||||
"Error: Invalid type. Must be 'crew', 'flow', 'tool', 'skill', or 'template'.",
|
||||
fg="red",
|
||||
)
|
||||
click.secho("Error: Invalid type. Must be 'crew' or 'flow'.", fg="red")
|
||||
|
||||
|
||||
@crewai.command()
|
||||
@@ -707,8 +652,6 @@ def tool() -> None:
|
||||
@tool.command(name="create")
|
||||
@click.argument("handle")
|
||||
def tool_create(handle: str) -> None:
|
||||
"""[Deprecated: use `crewai create tool`] Create a custom tool project."""
|
||||
warn_deprecated_command(old="crewai tool create", new="crewai create tool")
|
||||
from crewai_cli.tools.main import ToolCommand
|
||||
|
||||
tool_cmd = ToolCommand()
|
||||
@@ -759,8 +702,6 @@ def skill() -> None:
|
||||
help="Create skill in current dir instead of ./skills/",
|
||||
)
|
||||
def skill_create(name: str, in_project: bool) -> None:
|
||||
"""[Deprecated: use `crewai create skill`] Create a new agent skill."""
|
||||
warn_deprecated_command(old="crewai skill create", new="crewai create skill")
|
||||
from crewai_cli.skills.main import SkillCommand
|
||||
|
||||
skill_cmd = SkillCommand()
|
||||
@@ -824,8 +765,7 @@ def template_list() -> None:
|
||||
help="Directory name for the template (defaults to template name)",
|
||||
)
|
||||
def template_add(name: str, output_dir: str | None) -> None:
|
||||
"""[Deprecated: use `crewai create template`] Add a template to the current directory."""
|
||||
warn_deprecated_command(old="crewai template add", new="crewai create template")
|
||||
"""Add a template to the current directory."""
|
||||
template_cmd = TemplateCommand()
|
||||
template_cmd.add_template(name, output_dir)
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ from __future__ import annotations
|
||||
from collections.abc import Callable
|
||||
import contextlib
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
@@ -626,7 +627,14 @@ def _cache_key(provider_key: str) -> str:
|
||||
api_key = _provider_api_key(provider_key)
|
||||
if not api_key:
|
||||
return f"{provider_key}#nokey"
|
||||
digest = hashlib.sha256(api_key.encode("utf-8")).hexdigest()[:12]
|
||||
# HMAC with the credential as the key (not as hash input). SHA-256 alone on
|
||||
# API-key material trips CodeQL py/weak-sensitive-data-hashing; keyed HMAC is
|
||||
# the right construction for a local cache partition id.
|
||||
digest = hmac.new(
|
||||
api_key.encode("utf-8"),
|
||||
b"crewai.model_catalog.cache_v1",
|
||||
hashlib.sha256,
|
||||
).hexdigest()[:12]
|
||||
return f"{provider_key}#{digest}"
|
||||
|
||||
|
||||
|
||||
@@ -40,14 +40,6 @@ This ensures generated code always matches the version actually installed, not s
|
||||
- ❌ `Agent(llm=ChatOpenAI(...))` → ✅ `Agent(llm="openai/gpt-4o")` or `Agent(llm=LLM(model="..."))`
|
||||
- ❌ Passing raw OpenAI client objects → ✅ Use `crewai.LLM` wrapper
|
||||
|
||||
### Deprecated CLI scaffolding aliases (still supported)
|
||||
|
||||
These commands remain supported but print a yellow deprecation warning. Prefer the canonical forms:
|
||||
|
||||
- ⚠️ `crewai tool create <handle>` → ✅ `crewai create tool <handle>`
|
||||
- ⚠️ `crewai skill create <name>` → ✅ `crewai create skill <name>`
|
||||
- ⚠️ `crewai template add <name>` → ✅ `crewai create template <name>`
|
||||
|
||||
### How to verify you're using current patterns:
|
||||
1. You ran the version check and docs lookup steps above before writing code
|
||||
2. All LLM references use `crewai.LLM` or string shorthand (`"openai/gpt-4o"`)
|
||||
@@ -145,26 +137,8 @@ uv sync # Sync dependencies
|
||||
uv lock # Lock dependencies
|
||||
|
||||
# Project scaffolding
|
||||
crewai create crew <name> --skip_provider # New crew project
|
||||
crewai create flow <name> # New flow project
|
||||
crewai create tool <handle> # Custom tool repository
|
||||
crewai create skill <name> # Agent skill (./skills/ in crew projects)
|
||||
crewai create skill <name> --no-project # Skill in current directory
|
||||
crewai create template <name> # Remote project template
|
||||
crewai create template <name> -o <output_dir> # Template with custom output directory
|
||||
|
||||
# Deprecated scaffolding aliases (still work; print a yellow warning)
|
||||
# crewai tool create <handle> → crewai create tool <handle>
|
||||
# crewai skill create <name> → crewai create skill <name>
|
||||
# crewai template add <name> → crewai create template <name>
|
||||
|
||||
# Tool, skill, and template lifecycle (unchanged)
|
||||
crewai tool install <handle>
|
||||
crewai tool publish
|
||||
crewai skill install @org/name
|
||||
crewai skill publish
|
||||
crewai skill list
|
||||
crewai template list
|
||||
crewai create crew <name> --skip_provider # New crew project
|
||||
crewai create flow <name> --skip_provider # New flow project
|
||||
|
||||
# Running
|
||||
crewai run # Run crew or flow (auto-detects from pyproject.toml)
|
||||
@@ -653,26 +627,6 @@ class MyFlow(Flow):
|
||||
| `@listen(method)` | Triggers when specified method completes. Receives output as argument |
|
||||
| `@router(method)` | Conditional branching. Returns string labels that trigger `@listen("label")` |
|
||||
|
||||
### `@listen` labels vs handler names
|
||||
|
||||
The string in `@listen("...")` is an **event or route label**, not the Python method name. Router return values, route labels, and method completion events share one trigger namespace.
|
||||
|
||||
**Never** use the same name for the `@listen` label and the handler method:
|
||||
|
||||
```python
|
||||
# ❌ Wrong — raises a validation error when the flow is instantiated
|
||||
@listen("create_video")
|
||||
def create_video(self):
|
||||
...
|
||||
|
||||
# ✅ Correct — distinct handler name (handle_* prefix is a common pattern)
|
||||
@listen("create_video")
|
||||
def handle_create_video(self):
|
||||
...
|
||||
```
|
||||
|
||||
If validation were bypassed, matching names would also cause the handler to re-trigger itself in a loop at runtime. This applies to all flows; it is especially common in **conversational flows** (`conversational = True`), where `@listen("...")` is a router intent name.
|
||||
|
||||
### Structured State
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
@@ -1159,9 +1113,7 @@ Python >=3.10, <3.14
|
||||
```bash
|
||||
uv tool install crewai # Install CrewAI CLI
|
||||
uv tool list # Verify installation
|
||||
crewai create crew my_crew --skip_provider # Scaffold a crew project
|
||||
crewai create tool my_tool # Scaffold a tool repository
|
||||
crewai create skill my_skill # Scaffold an agent skill
|
||||
crewai create crew my_crew --skip_provider # Scaffold a new project
|
||||
crewai install # Install project dependencies
|
||||
crewai run # Execute
|
||||
```
|
||||
@@ -1196,4 +1148,3 @@ crewai run # Execute
|
||||
- Using `process=Process.hierarchical` without setting `manager_llm` or `manager_agent`
|
||||
- Circular delegation: set `allow_delegation=False` on specialist agents
|
||||
- Not installing tools package: `uv add crewai-tools`
|
||||
- **Matching `@listen("label")` to the handler method name** — raises a validation error at flow instantiation; would re-trigger in an infinite loop at runtime only if validation is bypassed. Use a different method name (e.g. `handle_create_video` for `@listen("create_video")`)
|
||||
|
||||
@@ -44,19 +44,10 @@ __all__ = [
|
||||
"render_template",
|
||||
"tree_copy",
|
||||
"tree_find_and_replace",
|
||||
"warn_deprecated_command",
|
||||
"write_env_file",
|
||||
]
|
||||
|
||||
|
||||
def warn_deprecated_command(*, old: str, new: str) -> None:
|
||||
"""Print a yellow deprecation warning for a legacy CLI command path."""
|
||||
click.secho(
|
||||
f"Warning: The command '{old}' is deprecated. Use '{new}' instead.",
|
||||
fg="yellow",
|
||||
)
|
||||
|
||||
|
||||
console = Console()
|
||||
_TEMPLATE_TOKEN_RE = re.compile(r"{{([a-zA-Z_][a-zA-Z0-9_]*)}}")
|
||||
|
||||
|
||||
@@ -228,7 +228,6 @@ def test_create_requires_type_in_dmn_mode(runner):
|
||||
|
||||
assert result.exit_code == 2
|
||||
assert "TYPE is required when CREWAI_DMN is set" in result.output
|
||||
assert "crew, flow, tool, skill, template" in result.output
|
||||
|
||||
|
||||
def test_create_requires_name_in_dmn_mode(runner):
|
||||
|
||||
@@ -1,230 +0,0 @@
|
||||
"""Tests for unified `crewai create <resource>` scaffolding commands."""
|
||||
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
|
||||
from crewai_cli.cli import create, crewai
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def runner():
|
||||
return CliRunner()
|
||||
|
||||
|
||||
@mock.patch("crewai_cli.tools.main.ToolCommand")
|
||||
def test_create_tool_invokes_tool_command(mock_tool_command_cls, runner):
|
||||
result = runner.invoke(create, ["tool", "my_tool"])
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
mock_tool_command_cls.return_value.create.assert_called_once_with("my_tool")
|
||||
assert "deprecated" not in result.output.lower()
|
||||
|
||||
|
||||
@mock.patch("crewai_cli.tools.main.ToolCommand")
|
||||
def test_tool_create_is_deprecated_and_still_works(mock_tool_command_cls, runner):
|
||||
result = runner.invoke(crewai, ["tool", "create", "my_tool"])
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
mock_tool_command_cls.return_value.create.assert_called_once_with("my_tool")
|
||||
assert (
|
||||
"Warning: The command 'crewai tool create' is deprecated. "
|
||||
"Use 'crewai create tool' instead."
|
||||
in result.output
|
||||
)
|
||||
|
||||
|
||||
@mock.patch("crewai_cli.tools.main.ToolCommand")
|
||||
@pytest.mark.parametrize("extra_args", [["--classic"], ["--declarative"], ["--provider", "openai"], ["--skip_provider"]])
|
||||
def test_create_tool_rejects_crew_and_flow_flags(mock_tool_command_cls, runner, extra_args):
|
||||
result = runner.invoke(create, ["tool", "my_tool", *extra_args])
|
||||
|
||||
assert result.exit_code == 2, result.output
|
||||
assert "Crew and flow options cannot be used with tool projects." in result.output
|
||||
mock_tool_command_cls.return_value.create.assert_not_called()
|
||||
|
||||
|
||||
@mock.patch("crewai_cli.skills.main.SkillCommand")
|
||||
def test_create_skill_invokes_skill_command(mock_skill_command_cls, runner):
|
||||
result = runner.invoke(create, ["skill", "my-skill"])
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
mock_skill_command_cls.return_value.create.assert_called_once_with(
|
||||
"my-skill", in_project=True
|
||||
)
|
||||
assert "deprecated" not in result.output.lower()
|
||||
|
||||
|
||||
@mock.patch("crewai_cli.skills.main.SkillCommand")
|
||||
def test_create_skill_no_project_flag(mock_skill_command_cls, runner):
|
||||
result = runner.invoke(create, ["skill", "my-skill", "--no-project"])
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
mock_skill_command_cls.return_value.create.assert_called_once_with(
|
||||
"my-skill", in_project=False
|
||||
)
|
||||
|
||||
|
||||
@mock.patch("crewai_cli.skills.main.SkillCommand")
|
||||
def test_skill_create_is_deprecated_and_still_works(mock_skill_command_cls, runner):
|
||||
result = runner.invoke(crewai, ["skill", "create", "my-skill"])
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
mock_skill_command_cls.return_value.create.assert_called_once_with(
|
||||
"my-skill", in_project=True
|
||||
)
|
||||
assert (
|
||||
"Warning: The command 'crewai skill create' is deprecated. "
|
||||
"Use 'crewai create skill' instead."
|
||||
in result.output
|
||||
)
|
||||
|
||||
|
||||
@mock.patch("crewai_cli.skills.main.SkillCommand")
|
||||
@pytest.mark.parametrize(
|
||||
"extra_args",
|
||||
[["--classic"], ["--declarative"], ["--provider", "openai"], ["--skip_provider"]],
|
||||
)
|
||||
def test_create_skill_rejects_crew_and_flow_flags(
|
||||
mock_skill_command_cls, runner, extra_args
|
||||
):
|
||||
result = runner.invoke(create, ["skill", "my-skill", *extra_args])
|
||||
|
||||
assert result.exit_code == 2, result.output
|
||||
assert "Crew and flow options cannot be used with skill projects." in result.output
|
||||
mock_skill_command_cls.return_value.create.assert_not_called()
|
||||
|
||||
|
||||
@mock.patch("crewai_cli.skills.main.SkillCommand")
|
||||
def test_create_crew_rejects_no_project_flag(mock_skill_command_cls, runner):
|
||||
result = runner.invoke(create, ["crew", "my-crew", "--no-project"])
|
||||
|
||||
assert result.exit_code == 2, result.output
|
||||
assert "--no-project can only be used with skill projects." in result.output
|
||||
mock_skill_command_cls.return_value.create.assert_not_called()
|
||||
|
||||
|
||||
@mock.patch("crewai_cli.remote_template.main.TemplateCommand")
|
||||
def test_create_template_invokes_template_command(mock_template_command_cls, runner):
|
||||
result = runner.invoke(create, ["template", "my-template"])
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
mock_template_command_cls.return_value.add_template.assert_called_once_with(
|
||||
"my-template", None
|
||||
)
|
||||
assert "deprecated" not in result.output.lower()
|
||||
|
||||
|
||||
@mock.patch("crewai_cli.remote_template.main.TemplateCommand")
|
||||
def test_create_template_output_dir_flag(mock_template_command_cls, runner):
|
||||
result = runner.invoke(
|
||||
create, ["template", "my-template", "--output-dir", "custom_dir"]
|
||||
)
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
mock_template_command_cls.return_value.add_template.assert_called_once_with(
|
||||
"my-template", "custom_dir"
|
||||
)
|
||||
|
||||
|
||||
@mock.patch("crewai_cli.remote_template.main.TemplateCommand")
|
||||
def test_template_add_is_deprecated_and_still_works(mock_template_command_cls, runner):
|
||||
result = runner.invoke(
|
||||
crewai, ["template", "add", "my-template", "--output-dir", "custom_dir"]
|
||||
)
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
mock_template_command_cls.return_value.add_template.assert_called_once_with(
|
||||
"my-template", "custom_dir"
|
||||
)
|
||||
assert (
|
||||
"Warning: The command 'crewai template add' is deprecated. "
|
||||
"Use 'crewai create template' instead."
|
||||
in result.output
|
||||
)
|
||||
|
||||
|
||||
@mock.patch("crewai_cli.remote_template.main.TemplateCommand")
|
||||
@pytest.mark.parametrize(
|
||||
"extra_args",
|
||||
[["--classic"], ["--declarative"], ["--provider", "openai"], ["--skip_provider"]],
|
||||
)
|
||||
def test_create_template_rejects_crew_and_flow_flags(
|
||||
mock_template_command_cls, runner, extra_args
|
||||
):
|
||||
result = runner.invoke(create, ["template", "my-template", *extra_args])
|
||||
|
||||
assert result.exit_code == 2, result.output
|
||||
assert (
|
||||
"Crew and flow options cannot be used with template projects."
|
||||
in result.output
|
||||
)
|
||||
mock_template_command_cls.return_value.add_template.assert_not_called()
|
||||
|
||||
|
||||
@mock.patch("crewai_cli.remote_template.main.TemplateCommand")
|
||||
def test_create_crew_rejects_output_dir_flag(mock_template_command_cls, runner):
|
||||
result = runner.invoke(create, ["crew", "my-crew", "--output-dir", "custom_dir"])
|
||||
|
||||
assert result.exit_code == 2, result.output
|
||||
assert "--output-dir can only be used with template projects." in result.output
|
||||
mock_template_command_cls.return_value.add_template.assert_not_called()
|
||||
|
||||
|
||||
@mock.patch("crewai_cli.cli.enable_prompt_line_editing")
|
||||
@mock.patch("crewai_cli.cli.click.prompt", return_value="picked-tool")
|
||||
@mock.patch("crewai_cli.tui_picker.pick", return_value="tool")
|
||||
@mock.patch("crewai_cli.tools.main.ToolCommand")
|
||||
def test_create_picker_supports_tool_skill_and_template(
|
||||
mock_tool_command_cls,
|
||||
mock_pick,
|
||||
mock_prompt,
|
||||
mock_enable_prompt,
|
||||
runner,
|
||||
):
|
||||
result = runner.invoke(create, [])
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
mock_pick.assert_called_once()
|
||||
picker_options = mock_pick.call_args[0][1]
|
||||
assert {option[0] for option in picker_options} == {
|
||||
"crew",
|
||||
"flow",
|
||||
"tool",
|
||||
"skill",
|
||||
"template",
|
||||
}
|
||||
mock_prompt.assert_called_once()
|
||||
mock_tool_command_cls.return_value.create.assert_called_once_with("picked-tool")
|
||||
|
||||
|
||||
_DMN_ENV = {"CREWAI_DMN": "True"}
|
||||
|
||||
|
||||
@mock.patch("crewai_cli.tools.main.ToolCommand")
|
||||
def test_create_tool_works_in_dmn_mode(mock_tool_command_cls, runner):
|
||||
result = runner.invoke(create, ["tool", "my_tool"], env=_DMN_ENV)
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
mock_tool_command_cls.return_value.create.assert_called_once_with("my_tool")
|
||||
|
||||
|
||||
@mock.patch("crewai_cli.skills.main.SkillCommand")
|
||||
def test_create_skill_works_in_dmn_mode(mock_skill_command_cls, runner):
|
||||
result = runner.invoke(create, ["skill", "my-skill"], env=_DMN_ENV)
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
mock_skill_command_cls.return_value.create.assert_called_once_with(
|
||||
"my-skill", in_project=True
|
||||
)
|
||||
|
||||
|
||||
@mock.patch("crewai_cli.cli.TemplateCommand")
|
||||
def test_create_template_works_in_dmn_mode(mock_template_command_cls, runner):
|
||||
result = runner.invoke(create, ["template", "my-template"], env=_DMN_ENV)
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
mock_template_command_cls.return_value.add_template.assert_called_once_with(
|
||||
"my-template", None
|
||||
)
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import time
|
||||
|
||||
@@ -583,6 +585,14 @@ def test_cache_key_hashes_key_and_never_stores_it(monkeypatch):
|
||||
key = mc._cache_key("openai")
|
||||
assert key.startswith("openai#") and key != "openai#nokey"
|
||||
assert "sk-super-secret" not in key # only a digest, never the raw key
|
||||
# Credential is the HMAC key (not SHA-256 hash input) so CodeQL
|
||||
# py/weak-sensitive-data-hashing does not flag password-style hashing.
|
||||
expected = hmac.new(
|
||||
b"sk-super-secret",
|
||||
b"crewai.model_catalog.cache_v1",
|
||||
hashlib.sha256,
|
||||
).hexdigest()[:12]
|
||||
assert key == f"openai#{expected}"
|
||||
|
||||
|
||||
def test_dynamic_cache_expires_after_catalog_ttl(monkeypatch):
|
||||
|
||||
Reference in New Issue
Block a user