Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
Expand Down
19 changes: 19 additions & 0 deletions docs/edge/en/concepts/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <name>
crewai starter <name> <target_dir>
crewai starter <name> --output-dir <target_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.
Expand Down
93 changes: 93 additions & 0 deletions docs/edge/en/starter-packs.mdx
Original file line number Diff line number Diff line change
@@ -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 <name>
```

Install one into a specific directory:

```shell Terminal
crewai starter <name> <target_dir>
```

or:

```shell Terminal
crewai starter <name> --output-dir <target_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 <name>
```

The command resolves `<name>` 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 <name>` 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.
28 changes: 28 additions & 0 deletions lib/cli/src/crewai_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
47 changes: 37 additions & 10 deletions lib/cli/src/crewai_cli/remote_template/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand All @@ -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)
Expand All @@ -124,17 +153,15 @@ 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)

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"
)

Expand Down
61 changes: 61 additions & 0 deletions lib/cli/tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
login,
reset_memories,
run,
starter,
test,
train,
version,
Expand Down Expand Up @@ -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)
Expand Down