Seedling by Leaf

leafs/seedling — a console framework for PHP

Ship CLI tools,
not boilerplate.

One command scaffolds a console app with commands, interactive prompts, styled output and argument parsing — and what you build installs like any real binary: vendor/bin/your-tool.

GreetCommand.php leaf
class GreetCommand extends Command
{
    protected $signature = 'greet
        {name : The name of the person}
        {--greeting=Hello : The greeting to use}';

    protected $description = 'Greet someone properly';

    protected function handle(): int
    {
        $this->info("{$this->option('greeting')}, {$this->argument('name')}!");

        return 0;
    }
}

~/greeter

Hi, Mika!

 

php leaf greet --help

Greet someone properly

Usage: greet [name] [--options...]

  name: The name of the person

  --greeting: The greeting to use

 

echo $?

0 // honest exit codes, always

// proven in production

The engine already runs the Leaf toolchain.

leaf

Leaf CLI

Scaffolds, serves and manages every Leaf app

alchemy

Alchemy

Tests, style, refactors and CI from one file

php leaf

Leaf MVC console

Generators, migrations, scaffolds — all of it

your-tool

Your tool 🌱

The next thing your terminal can't live without

// batteries, no bloat

Everything a CLI needs. Nothing it doesn't.

Seedling stays out of your way: a handful of primitives that compose, instead of a framework you fight.

Interactive prompts

Text, confirm and select prompts with defaults and inline validation — arrow keys and Windows support included.

? Pick your coffee

❯ Espresso

  Cortado

Styled output

info(), error(), comment() and inline markup tags — consistent color on every terminal.

✔ Build finished in 1.2s

// 2 files skipped

✗ deploy failed: missing token

Run real processes

sprout()->run() plus composer and npm wrappers — built on the battle-tested symfony/process.

sprout()->composer()

  ->install();

Honest exit codes

Handlers return ints and the app propagates them — your tool plays correctly with CI, pipes and shell scripts from day one.

Generators included

php leaf g:command scaffolds commands into app/console, auto-registered — no manual wiring.

A real binary at the end

Publish to Packagist and users run vendor/bin/your-tool — or install globally and call it like any system command.

// ask like you mean it

Prompts that feel like a modern CLI.

The same prompt engine that powers leaf create is yours: chain questions, validate inline, branch on answers — declared as plain arrays, no builder gymnastics.

$answers = sprout()->prompt([[
    'type'    => 'select',
    'name'    => 'flavor',
    'message' => 'Pick your coffee',
    'choices' => [
        ['title' => 'Espresso', 'value' => 'esp'],
        ['title' => 'Cortado',  'value' => 'cor'],
    ],
]]);

? Pick your coffee · use arrows

❯ Espresso

  Cortado

 

? Project name (my-tool) brew-buddy

? Initialize git? Yes / no

 

brew-buddy is ready

// the engine underneath

Powered by Sprout.

Seedling is a thin, structured layer over Sprout — a tiny console engine with exactly two dependencies: symfony/process for shelling out and Leaf's own fs for files. No framework baggage, nothing else in your tree.

Building a library and just need to expose a few commands? Skip the framework and require the engine directly.

Sprout on GitHub →

embed commands in any package

# the engine, standalone
composer require leafs/sprout

# a command, functional style
$app->command('cache:clear', function () {
    $this->info('Cache cleared!');
});

Plant one now.

You're one command away from a working console app — and an afternoon away from shipping it.

get started

leaf create my-tool --console

# or: composer create-project leafs/seedling my-tool