Skip to content

Instantly share code, notes, and snippets.

@maxandersen
Created April 20, 2026 07:44
Show Gist options
  • Select an option

  • Save maxandersen/54c3e0b32b854519ae5c31b63d59da74 to your computer and use it in GitHub Desktop.

Select an option

Save maxandersen/54c3e0b32b854519ae5c31b63d59da74 to your computer and use it in GitHub Desktop.

Review: Aesh Migration Branch (stalep/feature/aesh-migration)

Branch: https://github.com/stalep/jbang/tree/feature/aesh-migration

What it does

Replaces picocli with Aesh (Advanced Extensible SHell) as the CLI parsing framework. This is a 2-commit, ~62-file, net -605 lines change across jbang's entire CLI layer.

Structural Changes

Aspect Picocli (current) Aesh (branch)
Annotations @Command, @Option, @Parameters @CommandDefinition, @GroupCommandDefinition, @Option, @Argument, @Arguments
Subcommand grouping Separate top-level classes (e.g. AliasAdd, ExportLocal) Inner static classes (e.g. Alias.AliasAdd, Export.ExportLocal)
Command base Callable<Integer> Command<CommandInvocation> + CommandLifecycle
Entry point CommandLine.execute() orchestrates everything AeshRuntimeRunner.builder().command(JBang.class).args(...).execute()
Option features negatable, arity, fallbackValue, scope=INHERIT, ArgGroup hasValue=false + manual negation pairs (--cds/--no-cds), inherited=true, custom OptionParser
Mixins @Mixin with @Spec injection @Mixin but no spec injection; fields read directly
Default values IDefaultValueProvider with config lookup JBangDefaultValueProvider (simpler, same idea)
Help Rich picocli help with grouped sections, custom CommandGroupRenderer generateHelp = true (basic auto-generated help)
Completion Full bash/fish auto-generation via picocli.AutoComplete Stubbed out — prints "not yet available"
Error handling IExecutionExceptionHandler, IExitCodeExceptionMapper Manual try/catch in Main.main() and JBang.execute()

What's Better

  1. Simpler command hierarchy — Inner static classes for subcommands is cleaner than scattered top-level classes
  2. Fewer framework abstractions — No CommandSpec, ParseResult, PrintWriter plumbing; more direct code
  3. Net code reduction — ~600 fewer lines despite some added boilerplate
  4. Lifecycle hooksbeforeParse()/afterParse() on CommandLifecycle is a clean separation vs. picocli's setter-driven approach
  5. Deleted complexity — Removes DeprecatedMessageHandler, KeyValueConsumer, FormatMixin, HelpMixin, ExportMixin, VersionProvider, CommaSeparatedConverter, TemplatePropertyConverter — all replaced by simpler inline code

What's Worse / Concerning

  1. Shell completion is gone — Bash and fish completion are stubbed out with TODO comments. This is a major regression — completion is a key CLI UX feature
  2. Help output degraded — The current picocli help has beautiful grouped sections (Essentials, Editing, Caching, Configuration, Other, External). Aesh's generateHelp = true produces flat, basic help. The entire CommandGroupRenderer + external command discovery is deleted
  3. No version check — The VersionChecker.newerVersionAsync() call (checks for newer jbang versions) is completely removed from the execution flow
  4. Negatable options require two fields--cds/--no-cds and --integrations/--no-integrations each need two boolean fields + a getter, vs picocli's single negatable = true
  5. Optional<String>String — The literalScript field loses Optional semantics (distinguishing "not set" from "set to empty"), replaced with null checks
  6. Global flags workaroundapplyParentFlags() manually scans raw args for --verbose, --quiet, etc. as a "workaround" because aesh's inherited option propagation doesn't work reliably in the buildExecutor path. This is fragile
  7. Test infrastructure changedcheckedRun() lost its generic Function<T, Integer> parameter and now catches CommandLineParserException to fall back to JBang.execute() — feels like a workaround for parse-vs-execute differences
  8. Default value provider is simpler but less capable — The picocli version checked system properties, then config, with hierarchical key lookup (app.list.formatformat). The aesh version only does commandName.optionName config lookup
  9. --enableassertions/--enablesystemassertions renamed to --ea/--esa — a breaking change for existing users/scripts
  10. External plugin command discovery is completely removed (the PATH scanning for jbang-* commands in help)
  11. Depends on unreleased aesh 3.6-dev — Not in Maven Central, so nothing builds yet

What's Different (Neutral)

  • handleDefaultRun no longer takes a CommandSpec, uses reflection on @CommandDefinition/@GroupCommandDefinition annotations instead
  • Custom option parsers (StrictOptionParser, DebugOptionParser) replace picocli's preprocessor/fallbackValue/parameterConsumer — roughly equivalent complexity
  • CatalogFileOptionsMixin extracted to replace the BaseAliasCommand abstract class pattern — reasonable refactor
  • Benchmark test suite added (TestStartupBenchmark) — useful for comparing startup overhead between frameworks
  • @OptionGroup replaces @Option(parameterConsumer = KeyValueConsumer.class) for maps
  • @OptionList replaces multi-value @Option for lists

Verdict

This is a work-in-progress proof of concept that shows aesh can replace picocli structurally, but has significant feature regressions:

  • Shell completion (critical for CLI tools) is unimplemented
  • Help formatting goes from excellent to basic
  • Version update checking is lost
  • Global flag handling relies on a fragile workaround
  • Several breaking changes to option names

The motivation appears to be startup performance (hence the benchmark tests) and possibly tighter integration with Red Hat's tooling ecosystem (aesh is a Red Hat project, same as the author). Whether the tradeoff is worth it depends on whether aesh can eventually match picocli's rich help/completion features, and whether startup time is actually a pain point for jbang users.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment