Skip to content

Index

flavor.commands

Command modules for the flavor CLI.

Functions

clean_command

clean_command(
    all: bool, helpers: bool, dry_run: bool, yes: bool
) -> None

Clean work environment cache (default) or helpers.

Source code in flavor/commands/utils.py
@click.command("clean")
@click.option(
    "--all",
    is_flag=True,
    help="Clean both work environment and helpers",
)
@click.option(
    "--helpers",
    is_flag=True,
    help="Clean only helper binaries",
)
@click.option(
    "--dry-run",
    is_flag=True,
    help="Show what would be removed without removing",
)
@click.option(
    "--yes",
    "-y",
    is_flag=True,
    help="Skip confirmation prompt",
)
def clean_command(all: bool, helpers: bool, dry_run: bool, yes: bool) -> None:
    """Clean work environment cache (default) or helpers."""
    log.debug(
        "Clean command started",
        all=all,
        helpers=helpers,
        dry_run=dry_run,
        yes=yes,
    )

    # Determine what to clean
    clean_workenv = not helpers or all
    clean_helpers = helpers or all

    if dry_run:
        pout("🔍 DRY RUN - Nothing will be removed\n")

    total_freed = 0

    if clean_workenv:
        total_freed += _clean_workenv_cache(dry_run, yes)

    if clean_helpers:
        total_freed += _clean_helper_binaries(dry_run, yes)

    _show_total_freed(dry_run, total_freed)

helper_group

helper_group() -> None

Manage Flavor helper binaries (launchers and builders).

Source code in flavor/commands/helpers.py
@click.group("helpers")
def helper_group() -> None:
    """Manage Flavor helper binaries (launchers and builders)."""
    pass

inspect_command

inspect_command(
    package_file: str, output_json: bool
) -> None

Quick inspection of a flavor package.

Source code in flavor/commands/inspect.py
@click.command("inspect")
@click.argument(
    "package_file",
    type=click.Path(exists=True, dir_okay=False, resolve_path=True),
    required=True,
)
@click.option(
    "--json",
    "output_json",
    is_flag=True,
    help="Output as JSON",
)
def inspect_command(package_file: str, output_json: bool) -> None:
    """Quick inspection of a flavor package."""
    package_path = Path(package_file)
    log.debug("Inspecting package", package=str(package_path), output_json=output_json)

    try:
        with PSPFReader(package_path) as reader:
            index = reader.read_index()
            metadata = reader.read_metadata()
            slot_descriptors = reader.read_slot_descriptors()
            slots_metadata = metadata.get("slots", [])

            log.debug(
                "Package inspection completed",
                format_version=f"0x{index.format_version:08x}",
                slot_count=len(slot_descriptors),
            )

            if output_json:
                _output_json_format(package_path, index, metadata, slot_descriptors, slots_metadata)
            else:
                _output_human_format(package_path, index, metadata, slot_descriptors, slots_metadata)

    except FileNotFoundError as e:
        log.error("Package not found", package=package_file)
        perr(f"❌ Package not found: {package_file}")
        raise click.Abort() from e
    except Exception as e:
        log.error("Error inspecting package", package=package_file, error=str(e))
        perr(f"❌ Error inspecting package: {e}")
        raise click.Abort() from e

keygen_command

keygen_command(out_dir: str) -> None

Generates an Ed25519 key pair for package integrity signing.

Source code in flavor/commands/keygen.py
@click.command("keygen")
@click.option(
    "--out-dir",
    default="keys",
    type=click.Path(file_okay=False, writable=True, resolve_path=True),
    help="Directory to save the Ed25519 key pair.",
)
def keygen_command(out_dir: str) -> None:
    """Generates an Ed25519 key pair for package integrity signing."""
    log.debug("Generating key pair", out_dir=out_dir)

    try:
        generate_key_pair(Path(out_dir))
        log.info("Key pair generated successfully", out_dir=out_dir)
        pout(f"✅ Package integrity key pair generated in '{out_dir}'.")
    except BuildError as e:
        log.error("Keygen failed", error=str(e), out_dir=out_dir)
        perr(f"❌ Keygen failed: {e}")
        raise click.Abort() from e

pack_command

pack_command(
    pyproject_toml_path: str,
    output_path: str | None,
    launcher_bin: str | None,
    builder_bin: str | None,
    verify: bool,
    strip: bool,
    progress: bool,
    quiet: bool,
    private_key: str | None,
    public_key: str | None,
    key_seed: str | None,
    workenv_base: str | None,
    output_format: str | None,
    output_file: str | None,
) -> None

Pack the application for one or more target platforms.

Source code in flavor/commands/package.py
@click.command("pack")
@click.option(
    "--manifest",
    "pyproject_toml_path",
    default="pyproject.toml",
    type=click.Path(exists=True, dir_okay=False, resolve_path=True),
    help="Path to the pyproject.toml manifest file.",
)
@click.option(
    "--output",
    "output_path",
    type=click.Path(dir_okay=False, resolve_path=True),
    help="Custom output path for the package (defaults to dist/<name>.psp).",
)
@click.option(
    "--launcher-bin",
    type=click.Path(exists=True, dir_okay=False, resolve_path=True),
    help="Path to launcher binary to embed in the package.",
)
@click.option(
    "--builder-bin",
    type=click.Path(exists=True, dir_okay=False, resolve_path=True),
    help="Path to builder binary (overrides default builder selection).",
)
@click.option(
    "--verify/--no-verify",
    default=True,
    help="Verify the package after building (default: verify).",
)
@click.option(
    "--strip",
    is_flag=True,
    help="Strip debug symbols from launcher binary for size reduction.",
)
@click.option(
    "--progress",
    is_flag=True,
    help="Show progress bars during packaging.",
)
@click.option(
    "--quiet",
    is_flag=True,
    help="Suppress progress output.",
)
@click.option(
    "--private-key",
    type=click.Path(exists=True, dir_okay=False, resolve_path=True),
    help="Path to private key (PEM format) for signing.",
)
@click.option(
    "--public-key",
    type=click.Path(exists=True, dir_okay=False, resolve_path=True),
    help="Path to public key (PEM format, optional if private key provided).",
)
@click.option(
    "--key-seed",
    type=str,
    help="Seed for deterministic key generation.",
)
@click.option(
    "--workenv-base",
    type=click.Path(exists=True, file_okay=False, resolve_path=True),
    help="Base directory for {workenv} resolution (defaults to CWD).",
)
@click.option(
    "--output-format",
    type=click.Choice(["text", "json"], case_sensitive=False),
    help="Output format (or set FLAVOR_OUTPUT_FORMAT env var).",
)
@click.option(
    "--output-file",
    type=str,
    help="Output file path, STDOUT, or STDERR (or set FLAVOR_OUTPUT_FILE env var).",
)
def pack_command(
    pyproject_toml_path: str,
    output_path: str | None,
    launcher_bin: str | None,
    builder_bin: str | None,
    verify: bool,
    strip: bool,
    progress: bool,
    quiet: bool,
    private_key: str | None,
    public_key: str | None,
    key_seed: str | None,
    workenv_base: str | None,
    output_format: str | None,
    output_file: str | None,
) -> None:
    """Pack the application for one or more target platforms."""
    log.debug(
        "Starting package command",
        manifest=pyproject_toml_path,
        output_path=output_path,
        quiet=quiet,
    )

    if not quiet:
        pout("🚀 Packaging application...")

    _setup_workenv_base(workenv_base)

    try:
        if not quiet:
            pass

        built_artifacts = _build_package_artifacts(
            pyproject_toml_path,
            output_path,
            launcher_bin,
            builder_bin,
            strip,
            progress,
            quiet,
            private_key,
            public_key,
            key_seed,
        )

        if not quiet:
            pout("🔍 Processing and verifying artifacts...")

        _process_built_artifacts(built_artifacts, verify, strip, quiet)
        _show_final_results(built_artifacts, quiet)

        log.info("Packaging completed successfully", artifact_count=len(built_artifacts))

    except (BuildError, PackagingError, click.UsageError) as e:
        log.error("Packaging failed", error=str(e), manifest=pyproject_toml_path)
        perr(f"❌ Packaging Failed:\n{e}")
        raise click.Abort() from e

verify_command

verify_command(package_file: str) -> None

Verifies a flavor package.

Source code in flavor/commands/verify.py
@click.command("verify")
@click.argument(
    "package_file",
    type=click.Path(exists=True, dir_okay=False, resolve_path=True),
    required=True,
)
def verify_command(package_file: str) -> None:
    """Verifies a flavor package."""
    final_package_file = Path(package_file)
    log.debug("Starting package verification", package=str(final_package_file))
    pout(f"🔍 Verifying package '{final_package_file}'...")

    try:
        result = verify_package(final_package_file)
        log.debug(
            "Package verification completed",
            format=result.get("format"),
            signature_valid=result.get("signature_valid"),
        )

        _display_basic_info(result)
        if result["format"] == "PSPF/2025":
            _display_pspf_info(result)
        _display_signature_status(result)

    except Exception as e:
        log.error("Verification failed", error=str(e), package=str(final_package_file))
        perr(f"❌ Verification failed: {e}")
        raise click.Abort() from e

workenv_group

workenv_group() -> None

Manage the Flavor work environment cache.

Source code in flavor/commands/workenv.py
@click.group("workenv")
def workenv_group() -> None:
    """Manage the Flavor work environment cache."""
    pass