| name | playground-cli |
| description | Add or change a command in the `playground` CLI of kafka-docker-playground.
Use when editing `scripts/cli/src/bashly.yml`, adding or renaming a command /
subcommand / flag / argument, writing a command partial under
`scripts/cli/src/commands/`, adding a shared bash helper in
`scripts/cli/src/lib/`, adding a validator or filter, wiring shell
completions, or regenerating the CLI. The CLI is generated by Bashly —
`scripts/cli/playground` must never be hand-edited.
|
playground CLI (Bashly)
The playground CLI is generated by Bashly from
scripts/cli/src/. Editing the generated output is always wrong: the next
regeneration silently discards it.
Never hand-edit these — all generated, all committed
| File | Produced by |
|---|
scripts/cli/playground (~50k lines) | bashly generate |
scripts/cli/completions.bash | bashly add completions_script + a post-processing fix |
scripts/cli/playground.yaml | bashly render templates/shell-script-command-completion . |
scripts/cli/playground.json | yq -o=json playground.yaml |
docs/cli.md and friends | playground update-docs |
They are tracked in git, so a CLI change is only complete once they are
regenerated and committed alongside the source edit.
The loop
- Declare the command/flag in
scripts/cli/src/bashly.yml (~5000 lines,
the single source of truth for the whole command tree).
- Implement it in
scripts/cli/src/commands/<command>/<subcommand>.sh —
one file per (sub)command, path mirroring the command tree exactly.
- Regenerate:
playground bashly-reload
(requires Docker; pulls and runs dannyben/bashly).
- Verify:
playground <cmd> --help, one success path, one error path.
- Refresh docs if user-facing help text changed:
playground update-docs.
- Commit source and regenerated artifacts together.
Skipping step 3 is the classic failure: the flag exists in bashly.yml, the
partial reads ${args[--new-flag]}, and the CLI ignores it entirely because
the parser was never rebuilt.
Where does the code go?
| What you're adding | Where |
|---|
| Logic for one command | src/commands/<cmd>/<sub>.sh |
| Helper used only by the CLI (fzf pickers, prompts, connector/topic lookups) | src/lib/cli_function.sh (~2400 lines) |
| Helper also used by example scripts | src/lib/utils_function.sh (~4200 lines — the real core library) |
playground.ini read/write | src/lib/ini.sh |
| Argument validator | src/lib/validations/validate_<name>.sh |
| Command precondition | filter_<name>() in src/lib/cli_function.sh |
Version / image defaults (TAG, CP_*_IMAGE, …) | scripts/utils.sh, on the line ending # default tag |
Everything under src/lib/ is inlined into the generated script — no sourcing
needed, and any function there is callable from any command partial.
Do not put general-purpose helpers in scripts/utils.sh. That file is only
a thin entrypoint for example scripts: it sources utils_function.sh and sets
version defaults.
Writing a command partial
Partials are function bodies, not standalone scripts. No shebang, no
set -e, no sourcing:
connector="${args[--connector]}"
verbose="${args[--verbose]}"
connector_type=$(playground state get run.connector_type)
if [[ ! -n "$connector" ]]
then
connector=$(playground get-connector-list)
fi
log "⏸️ Pausing $connector_type connector $connector"
- Parsed flags/args arrive in the
args associative array, keyed by the long
flag including dashes (${args[--connector]}) or the arg name.
- Use
log / logwarn / logerror, never bare echo.
$root_folder (repo root) is set by src/before.sh and available everywhere.
- Call the CLI recursively —
playground state get …, playground connector status …. That is the normal idiom, not a smell.
src/before.sh runs before every command; it handles --vvv and
--output-level.
bashly.yml conventions in this repo
Read references/bashly-yml.md for the full vocabulary as it is actually used
here (anchors, validators, filters, groups, completions). The essentials:
- Reuse the YAML anchors. Common flags are defined once with
&tag,
&connector, &verbose, &environment, &enable-ksqldb, … and referenced
as *tag, *connector. Never re-declare a flag that already has an anchor.
- Help text is emoji-prefixed (
🔗 Connector name, 🗺️ Show a status) and
written for end users. Multi-line help uses |- with a blank line between
paragraphs; 🎓 Tip: marks hints.
group: places a command under a heading in playground --help
(Connector, Container, Debug, Kafka, Run, Schema, Topic, Tools, …).
private: true hides a command from help and docs. Used for the
get-*-list completion providers and for internal commands like
bashly-reload and state.
- Completions call those private commands:
completions: [$(playground get-connector-list)].
filters: declare preconditions (docker_running,
connect_running, not_mdc_environment, ccloud_environment, …).
validate: attaches a validator to an arg/flag (validate_not_empty,
validate_file_exists, validate_json, validate_minimal_cp_version, …).
Pitfalls
- Docker must be running for
bashly-reload, update-docs, and the
docker_running filter.
bashly-reload applies a perl fix to completions.bash because Bashly
escapes "$cur" in some completion helpers, and it fails loudly if the fix
didn't take. Don't patch completions.bash by hand — fix it in
bashly-reload.sh if it regresses.
- To test completions after a change:
source scripts/cli/completions.bash.
- A partial whose path doesn't match the command tree is silently never called.
- Private commands produce no documentation, so
update-docs won't show them.
- Renaming or removing a flag is a breaking change for the ~300 example scripts
that call
playground …; grep the repo before doing it.