Search by

sugarcraft / candy-scriptui

detain

Interactive terminal widgets for shell scripts — composer-installable CLI of SugarCraft TUI primitives, modeled on charmbracelet/gum. 13 subcommands: choose, confirm, file, filter, format, input, join, log, pager, spin, style, table, write.

dev-master 2026-10-17 07:19 UTC

This package is auto-updated.

Last update: 2026-10-17 07:24:20 UTC


README

candy-scriptui

CandyScriptui

Formerly published as sugarcraft/candy-shell (renamed 2026-10). Namespace changed: SugarCraft\Shell\ → SugarCraft\ScriptUi\.

CI codecov Packagist Version License PHP

demo

composer require sugarcraft/candy-scriptui

candy-scriptui — a composer-installable CLI of SugarCraft TUI primitives for shell scripts, for PHP 8.3+.

# Apply styling.
candy-scriptui style --foreground "#ff5f87" --bold "Hello, candy!"

# Pick one item.
choice=$(candy-scriptui choose Pizza Burger Salad)

# Read a single line.
name=$(candy-scriptui input --placeholder "Your name?")

# Confirm a destructive action.
candy-scriptui confirm "Really delete $file?" && rm "$file"

Subcommands

All 13 gum subcommands ship. Run candy-scriptui <cmd> --help for the full flag list per command.

Auto-discovery

CandyScriptui uses PHP attributes to auto-discover commands at runtime. Mark any class extending Symfony\Component\Console\Command\Command with #[Command] and it will be picked up by Application::scan().

Help attributes

Two additional attributes enrich the --help output of your commands:

  • #[Alias('name')] — registers an alternative name for the command (e.g. choose → cho). Multiple aliases are supported via repeated attributes.
  • #[Example('usage', 'description')] — adds an example line to the command's help block. The description parameter is optional. Multiple examples are supported via repeated attributes.

The HelpFormatter class renders these automatically when --help is invoked. It reads #[Alias] and #[Example] attributes via ReflectionClass::getAttributes() and formats them alongside the standard Symfony description and help text.

Typo suggestion

When a user types an unknown command name, Application::find() runs it through a TypoSuggester that computes Levenshtein distance against all registered command names. If a match is found within distance ≤ 2, the error message suggests the nearest alternative (e.g. "Did you mean choose?"). Beyond distance 2 the original error propagates silently.

use SugarCraft\ScriptUi\Attribute\Command;
use SugarCraft\ScriptUi\Attribute\Flag;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

#[Command(name: 'mycmd', description: 'Does something useful.', descriptionSection: 'Longer help text.')]
final class MyCommand extends Command
{
    #[Flag(name: 'format', short: 'f', description: 'Output format.', enum: FormatType::class)]
    protected function configure(): void
    {
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $format = $input->getOption('format');
        // ...
        return self::SUCCESS;
    }
}

enum FormatType: string
{
    case Json = 'json';
    case Yaml = 'yaml';
}

Register the namespace in your bootstrap:

$app = new \SugarCraft\ScriptUi\Application();
$app->scan('My\\Namespace\\');  // discovers all #[Command] classes
$app->run();

Application::scan($namespace) iterates all already-loaded classes under that namespace, picks those bearing #[Command], and registers them into the application. Classes must be autoloaded (or required) before scan() can find them — the scanner uses get_declared_classes() and does not trigger autoloading.

Command Role
choose Pick one or many items from a list.
completion Emit shell completion script (bash · zsh · fish).
confirm Yes/no prompt — exit 0 on confirm, 1 on cancel.
file Interactive file picker.
filter Fuzzy filter over stdin lines (single- or multi-select).
format Render Markdown / code / template / emoji to the terminal.
input Read a single line; supports --password masking.
join Concatenate two styled fragments side-by-side or stacked.
log Levelled / structured log output (text · json · logfmt).
pager Scrollable viewer for long input.
spin Run an external command behind a spinner.
style Apply Sprinkles styling to argv (or stdin).
table Render a CSV / TSV table.
write Multi-line text editor.

Flag reference (selected highlights)

Some flags from the original gum CLI surface are not wired here yet; the shipped surface covers the 80 % case for shell scripts. Common flags across commands:

  • --limit N / --no-limit / --ordered / --selected="a,b" — multi-select on choose and filter.
  • --header "Email:" / --prompt "> " / --value "$LAST" / --char-limit N / --width N / --max-lines N / --show-line-numbers on input / write.
  • --affirmative "Yes" / --negative "No" / --default=yes|no / --show-output on confirm.
  • --show-output / --show-error plus 12 spinner styles (dot · line · pulse · globe · points · monkey · moon · meter · mini-dot · hamburger · ellipsis · jump) on spin.
  • --min-level info / --prefix / --time RFC3339 / --file out.log / --formatter text|json|logfmt / --structured on log.
  • --border / --border-foreground "#ff0" / --height N / --trim on style.

Environment

CandyScriptui respects standard CLI colour conventions:

  • NO_COLOR=1 disables every SGR escape — output is plain ASCII.
  • CLICOLOR=0 disables colour when stdout is not a TTY (otherwise defaults to colour).
  • CLICOLOR_FORCE=1 keeps colour even when stdout is piped or redirected.
  • FORCE_COLOR=1|2|3 forces a specific tier (16 / 256 / TrueColor).

CANDY_SCRIPTUI_* env var fallbacks

Any command option can be given via an environment variable using the CANDY_SCRIPTUI_ prefix followed by the option name in uppercase with non-alphanumeric characters replaced by _. The pre-rename CANDYSHELL_* prefix still works as a deprecated fallback (it prints a notice and the new name wins if both are set). For example:

# Equivalent to: candy-scriptui style --foreground=#ff0000 --bold "Hello"
CANDY_SCRIPTUI_FOREGROUND=#ff0000 CANDY_SCRIPTUI_BOLD=1 candy-scriptui style "Hello"

The fallback is applied when no explicit CLI option is provided. An explicit flag on the command line always takes precedence over the env var.

Shell completion

candy-scriptui completion --shell=bash
candy-scriptui completion --shell=zsh
candy-scriptui completion --shell=fish

Emit a shell completion script for bash, zsh, or fish. Source the output directly or drop it into the appropriate completion directory.

Version

candy-scriptui --version reports the version read from the monorepo root composer.json via Application::versionFromComposer(). The version is discovered by walking up from the package directory to find the nearest composer.json with a non-empty version field.

Exit codes

  • 0 — normal completion. For confirm, this means the user picked the affirmative answer.
  • 1 — confirm declined; or non-zero exit forwarded from the external command run by spin.
  • 130 — interrupted (Ctrl-C / SIGINT). Matches POSIX shell convention.

Compatibility with gum invocations

Most gum X invocations work as candy-scriptui X verbatim. Known behavioural differences:

  • format accepts -t/--type (markdown, code, template, emoji) alongside --theme. Template support is the lightweight {{VAR}} expansion — Go template-function helpers are not implemented.
  • --timeout, --show-help, --strip-ansi, and the --cursor-mode flags now accept their gum-equivalent values on every command. Where a flag is meaningless to a non-interactive command (format, join, log, style, table) it is still accepted but treated as a no-op.
  • confirm --default=yes|no is the form to use; the older --default-yes alias is preserved.

Test

cd candy-scriptui && composer install && vendor/bin/phpunit

Demos

choose

choose

Confirm

confirm

file

file

filter

filter

format

format

Input

input

join

join

log

log

pager

pager

spin

spin

Style

style

Table

table

write

write

Shared foundations

The filter command uses sugar-fuzzy for fuzzy-matching via FilterModel → SmithWatermanMatcher. The shell-style flag parser (SubStyleParser) operates on style-flag strings in isolation and has no fuzzy logic.

Related

Credits & inspiration

Originally inspired by the Go Charm ecosystem; SugarCraft is developed as a native PHP project.