Skip to content

symfonic.cli

cli

Symfonic scaffolding CLI package.

Provides scaffold() used by symfonic init <project-name> to generate a complete starter project (FastAPI backend + webapp + docker-compose + auth + orgs + admin + chat) and a StarterDomain plugin skeleton with clear customization checkpoints.

scaffold

scaffold(
    project_name: str,
    components: list[str],
    llm_provider: str,
    output_dir: Path,
    force: bool = False,
) -> Path

Generate a starter project under <output_dir>/<project_name>/.

Parameters:

Name Type Description Default
project_name str

Directory-safe slug (letters, digits, dashes, underscores).

required
components list[str]

Subset of ALL_COMPONENTS to include.

required
llm_provider str

One of anthropic, openai, deepseek, kimi, google, ollama, aws, openrouter.

required
output_dir Path

Parent directory where the project folder will be created.

required
force bool

If True, overwrite files in an existing target directory instead of failing.

False

Returns:

Type Description
Path

The absolute path of the newly-created project directory.

Raises:

Type Description
ScaffolderError

When inputs are invalid or the target directory already exists (and force is False).

Source code in src/symfonic/cli/init.py
def scaffold(
    project_name: str,
    components: list[str],
    llm_provider: str,
    output_dir: Path,
    force: bool = False,
) -> Path:
    """Generate a starter project under ``<output_dir>/<project_name>/``.

    Args:
        project_name: Directory-safe slug (letters, digits, dashes, underscores).
        components: Subset of ``ALL_COMPONENTS`` to include.
        llm_provider: One of ``anthropic``, ``openai``, ``deepseek``, ``kimi``,
            ``google``, ``ollama``, ``aws``, ``openrouter``.
        output_dir: Parent directory where the project folder will be created.
        force: If True, overwrite files in an existing target directory instead of failing.

    Returns:
        The absolute path of the newly-created project directory.

    Raises:
        ScaffolderError: When inputs are invalid or the target directory
            already exists (and force is False).
    """
    if not _VALID_NAME.match(project_name):
        raise ScaffolderError(
            f"Invalid project name {project_name!r}: must start with a letter "
            "and contain only letters, digits, dashes, or underscores."
        )

    try:
        validate_components(components)
    except ValueError as exc:
        raise ScaffolderError(str(exc)) from exc

    output_dir = Path(output_dir).resolve()
    target = output_dir / project_name
    if target.exists() and not force:
        raise ScaffolderError(
            f"Target directory already exists: {target}. "
            "Remove it first or choose a different name."
        )

    context = _build_context(project_name, components, llm_provider)
    templates_dir = _locate_templates_dir()

    target.mkdir(parents=True, exist_ok=True)

    # File paths ending in ``.j2`` are rendered; everything else is copied.
    # Path segments themselves may contain ``{{ python_pkg }}`` tokens that
    # we substitute with the render context.
    for rel_path, src_path in _iter_template_files(templates_dir):
        # Resolve any template tokens in the path segments first so that
        # _should_skip receives a concrete relative path.
        rendered_parts = [
            _render(part, context) if ("{{" in part or "{%" in part) else part
            for part in rel_path.parts
        ]
        out_name = Path(*rendered_parts)
        if out_name.name.endswith(".j2"):
            out_name = out_name.with_name(out_name.name[: -len(".j2")])

        if _should_skip(out_name, components):
            continue

        dest = target / out_name
        dest.parent.mkdir(parents=True, exist_ok=True)

        if src_path.suffix == ".j2":
            text = src_path.read_text(encoding="utf-8")
            dest.write_text(_render(text, context), encoding="utf-8")
        else:
            shutil.copy2(src_path, dest)

    _print_next_steps(project_name, target, context)
    return target