diff --git a/docs/docs.json b/docs/docs.json index 4b49fae7c4..2ecd5a8771 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -80,6 +80,7 @@ "edge/en/introduction", "edge/en/guides/coding-tools/build-with-ai", "edge/en/skills", + "edge/en/starter-packs", "edge/en/installation", "edge/en/quickstart" ] diff --git a/docs/edge/en/concepts/cli.mdx b/docs/edge/en/concepts/cli.mdx index 367d34800f..fbff08f01a 100644 --- a/docs/edge/en/concepts/cli.mdx +++ b/docs/edge/en/concepts/cli.mdx @@ -54,6 +54,25 @@ 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`. +### Starter Packs + +Pull in a job-focused starter pack from the external template catalog. Starter packs are onboarding wrappers around project templates, so they do not add role-specific behavior to CrewAI's core runtime. + +```shell Terminal +crewai starter list +crewai starter +crewai starter +crewai starter --output-dir +``` + +Example: + +```shell Terminal +crewai starter analyst my_analyst +``` + +See [Starter Packs](/en/starter-packs) for the full workflow. + ### 2. Version Show the installed version of CrewAI. diff --git a/docs/edge/en/starter-packs.mdx b/docs/edge/en/starter-packs.mdx new file mode 100644 index 0000000000..6b3df30768 --- /dev/null +++ b/docs/edge/en/starter-packs.mdx @@ -0,0 +1,93 @@ +--- +title: Starter Packs +description: Pull job-focused CrewAI project templates into a new project. +icon: box-open +mode: "wide" +--- + +## Overview + +Starter packs are a beginner-friendly way to start from a job-to-be-done instead of from framework concepts. +They are installed from external CrewAI template repositories, so they do not add role-specific behavior to CrewAI's core agent, task, crew, or flow runtime. + +Use starter packs when you want a runnable baseline that you can customize after it lands in your workspace. +A starter pack can include agents, tasks, flows, tools, skills, examples, and README guidance. + +## Install a Starter Pack + +List the available starter packs: + +```shell Terminal +crewai starter list +``` + +Install one into the current directory: + +```shell Terminal +crewai starter +``` + +Install one into a specific directory: + +```shell Terminal +crewai starter +``` + +or: + +```shell Terminal +crewai starter --output-dir +``` + +For example, if an `analyst` starter pack is available: + +```shell Terminal +crewai starter analyst my_analyst +cd my_analyst +crewai install +crewai run +``` + +## How Starter Packs Work + +Starter packs use the same remote template mechanism as: + +```shell Terminal +crewai template add +``` + +The command resolves `` to an external template repository such as `template_analyst`, downloads it, and copies the project into your target directory. + +This keeps starter packs as an extended capability that gets pulled in when you need it. The generated project is ordinary CrewAI code and configuration, so you can edit it like any other crew or flow project. + +## Analyst Starter Pack Pattern + +An analyst starter pack can model a reusable workflow without baking analyst logic into CrewAI itself: + +```text +Understand schema -> draft query -> test query -> validate answer +``` + +A generated analyst project might include: + +```text +my_analyst/ +|-- crew.jsonc +|-- agents/ +| |-- schema_analyst.jsonc +| |-- query_writer.jsonc +| |-- query_tester.jsonc +| `-- validator.jsonc +|-- tools/ +| `-- database_tool.py +|-- skills/ +| `-- analyst-methodology/ +`-- README.md +``` + +The starter pack should give users a working baseline. From there, they can connect their data source, adjust roles, change validation criteria, or replace the default flow with their own. + +## When to Use Templates Instead + +Use `crewai template list` or `crewai template add ` when you already know you want to browse the raw project-template catalog. +Use `crewai starter` when you want onboarding language focused on the outcome you are trying to build. diff --git a/lib/cli/src/crewai_cli/cli.py b/lib/cli/src/crewai_cli/cli.py index 30eccdc617..462311ce3a 100644 --- a/lib/cli/src/crewai_cli/cli.py +++ b/lib/cli/src/crewai_cli/cli.py @@ -782,6 +782,34 @@ def template_add(name: str, output_dir: str | None) -> None: template_cmd.add_template(name, output_dir) +@crewai.command(name="starter") +@click.argument("name", required=False) +@click.argument("target_dir", required=False) +@click.option( + "-o", + "--output-dir", + type=str, + default=None, + help="Directory name for the starter pack (defaults to starter pack name)", +) +def starter(name: str | None, target_dir: str | None, output_dir: str | None) -> None: + """Browse or install starter packs backed by external templates.""" + template_cmd = TemplateCommand() + + if name is None or name == "list": + if target_dir or output_dir: + raise click.UsageError( + "Starter pack list does not accept an output directory." + ) + template_cmd.list_starter_packs() + return + + if target_dir and output_dir: + raise click.UsageError("Use either TARGET_DIR or --output-dir, not both.") + + template_cmd.add_starter_pack(name, output_dir or target_dir) + + @crewai.group() def flow() -> None: """Flow related commands.""" diff --git a/lib/cli/src/crewai_cli/remote_template/main.py b/lib/cli/src/crewai_cli/remote_template/main.py index a7db811919..6771f9fd33 100644 --- a/lib/cli/src/crewai_cli/remote_template/main.py +++ b/lib/cli/src/crewai_cli/remote_template/main.py @@ -44,18 +44,26 @@ def __init__(self) -> None: def list_templates(self) -> None: """List available templates with an interactive selector to install.""" + self._list_repos(kind="templates") + + def list_starter_packs(self) -> None: + """List available starter packs with an interactive selector to install.""" + self._list_repos(kind="starter packs") + + def _list_repos(self, kind: str) -> None: + """List available template repositories using a user-facing label.""" templates = self._fetch_templates() if not templates: - click.echo("No templates found.") + click.echo(f"No {kind} found.") return console.print(f"\n{BANNER}\n") - console.print(" [on cyan] templates [/on cyan]\n") + console.print(f" [on cyan] {kind} [/on cyan]\n") console.print(f" [green]o[/green] Source: https://github.com/{GITHUB_ORG}") console.print( - f" [green]o[/green] Found [bold]{len(templates)}[/bold] templates\n" + f" [green]o[/green] Found [bold]{len(templates)}[/bold] {kind}\n" ) - console.print(" [green]o[/green] Select a template to install") + console.print(f" [green]o[/green] Select a {kind[:-1]} to install") for idx, repo in enumerate(templates, start=1): name = repo["name"].removeprefix(TEMPLATE_PREFIX) @@ -88,7 +96,7 @@ def list_templates(self) -> None: selected = templates[selected_index] repo_name = selected["name"] - self._install_repo(repo_name) + self._install_repo(repo_name, kind=kind[:-1]) def add_template(self, name: str, output_dir: str | None = None) -> None: """Download a template and copy it into the current working directory. @@ -105,12 +113,33 @@ def add_template(self, name: str, output_dir: str | None = None) -> None: self._install_repo(repo_name, output_dir) - def _install_repo(self, repo_name: str, output_dir: str | None = None) -> None: + def add_starter_pack(self, name: str, output_dir: str | None = None) -> None: + """Download a starter pack template into the current working directory. + + Starter packs are a friendly onboarding layer over the same external + template repositories used by ``crewai template add``. + + Args: + name: Starter pack name (with or without the template_ prefix). + output_dir: Optional directory name. Defaults to the starter pack name. + """ + repo_name = self._resolve_repo_name(name) + if repo_name is None: + click.secho(f"Starter pack '{name}' not found.", fg="red") + click.echo("Run 'crewai starter list' to see available starter packs.") + raise SystemExit(1) + + self._install_repo(repo_name, output_dir, kind="starter pack") + + def _install_repo( + self, repo_name: str, output_dir: str | None = None, kind: str = "template" + ) -> None: """Download and extract a template repo into the current directory. Args: repo_name: Full GitHub repo name (e.g. template_deep_research). output_dir: Optional directory name. Defaults to the template name. + kind: User-facing install label. """ folder_name = output_dir or repo_name.removeprefix(TEMPLATE_PREFIX) dest = os.path.join(os.getcwd(), folder_name) @@ -124,9 +153,7 @@ def _install_repo(self, repo_name: str, output_dir: str | None = None) -> None: return dest = os.path.join(os.getcwd(), folder_name) - click.echo( - f"Downloading template '{repo_name.removeprefix(TEMPLATE_PREFIX)}'..." - ) + click.echo(f"Downloading {kind} '{repo_name.removeprefix(TEMPLATE_PREFIX)}'...") zip_bytes = self._download_zip(repo_name) self._extract_zip(zip_bytes, dest) @@ -134,7 +161,7 @@ def _install_repo(self, repo_name: str, output_dir: str | None = None) -> None: self._telemetry.template_installed_span(repo_name.removeprefix(TEMPLATE_PREFIX)) console.print( - f"\n [green]\u2713[/green] Installed template [bold white]{folder_name}[/bold white]" + f"\n [green]\u2713[/green] Installed {kind} [bold white]{folder_name}[/bold white]" f" [dim](source: github.com/{GITHUB_ORG}/{repo_name})[/dim]\n" ) diff --git a/lib/cli/tests/test_cli.py b/lib/cli/tests/test_cli.py index acdeee7ff0..1f779f557d 100644 --- a/lib/cli/tests/test_cli.py +++ b/lib/cli/tests/test_cli.py @@ -16,6 +16,7 @@ login, reset_memories, run, + starter, test, train, version, @@ -180,6 +181,66 @@ def test_run_passes_filename_to_project_runner(run_crew, runner): ) +@mock.patch("crewai_cli.cli.TemplateCommand") +def test_starter_without_name_lists_starter_packs(template_command, runner): + result = runner.invoke(starter) + + assert result.exit_code == 0 + template_command.return_value.list_starter_packs.assert_called_once_with() + template_command.return_value.add_starter_pack.assert_not_called() + + +@mock.patch("crewai_cli.cli.TemplateCommand") +def test_starter_list_alias_lists_starter_packs(template_command, runner): + result = runner.invoke(starter, ["list"]) + + assert result.exit_code == 0 + template_command.return_value.list_starter_packs.assert_called_once_with() + template_command.return_value.add_starter_pack.assert_not_called() + + +@mock.patch("crewai_cli.cli.TemplateCommand") +def test_starter_installs_named_pack(template_command, runner): + result = runner.invoke(starter, ["analyst"]) + + assert result.exit_code == 0 + template_command.return_value.add_starter_pack.assert_called_once_with( + "analyst", None + ) + template_command.return_value.list_starter_packs.assert_not_called() + + +@mock.patch("crewai_cli.cli.TemplateCommand") +def test_starter_installs_named_pack_to_target_dir(template_command, runner): + result = runner.invoke(starter, ["analyst", "my_analyst"]) + + assert result.exit_code == 0 + template_command.return_value.add_starter_pack.assert_called_once_with( + "analyst", "my_analyst" + ) + + +@mock.patch("crewai_cli.cli.TemplateCommand") +def test_starter_installs_named_pack_to_output_dir(template_command, runner): + result = runner.invoke(starter, ["analyst", "--output-dir", "my_analyst"]) + + assert result.exit_code == 0 + template_command.return_value.add_starter_pack.assert_called_once_with( + "analyst", "my_analyst" + ) + + +@mock.patch("crewai_cli.cli.TemplateCommand") +def test_starter_rejects_two_output_dirs(template_command, runner): + result = runner.invoke( + starter, ["analyst", "my_analyst", "--output-dir", "other"] + ) + + assert result.exit_code == 2 + assert "Use either TARGET_DIR or --output-dir, not both." in result.output + template_command.return_value.add_starter_pack.assert_not_called() + + @mock.patch("crewai_cli.cli.run_crew") def test_flow_kickoff_is_deprecated_and_uses_run_path(run_crew, runner): result = runner.invoke(flow_run)