cliweave 0.2.2 copy "cliweave: ^0.2.2" to clipboard
cliweave: ^0.2.2 copied to clipboard

A typed CLI framework: command routing, argument scanning with kebab/camel aliasing, did-you-mean suggestions, rendered help, structured exit codes, shell-completion proposals, and a terminal logger, [...]

cliweave #

A typed CLI framework for Dart: command routing, argument scanning, rendered help, structured exit codes, and shell-completion proposals — plus a terminal logger, spinner, and colour theme.

Status: 0.x. The API is in production use but can still change before 1.0. Dotweave is its first consumer.

Why not package:args? #

package:args is the right choice for most Dart CLIs. Reach for this one only if you need what it does not provide:

  • Completion, end to end, for four shells. proposeCompletions computes the candidate list for any partial command line — including per-argument dynamic values (file paths, remote names) from your own callback — and CompletionScripts generates the bash, zsh, fish, and PowerShell scripts that call back into it. package:args has no completion API; cli_completion adds one for bash and zsh only.
  • Full control of help layout. Help is rendered by this package rather than a private class, so USAGE / FLAGS / ARGUMENTS / COMMANDS sections, column alignment, and the --flag/--no-flag presentation are all part of the contract you can pin in tests.
  • kebab⇄camelCase aliasing. --dry-run and --dryRun both bind to a dryRun flag, without per-option alias lists.
  • Structured exit codes. Distinct negative codes for unknown command, invalid argument, and command-load failure, plus a hook to derive an exit code from a thrown error.

Libraries #

import 'package:cliweave/cliweave.dart'; // commands, flags, help, completion
import 'package:cliweave/terminal.dart'; // logger, spinner, colour theme

They are separate so that a consumer who only needs argument parsing does not pull in the terminal layer.

Example #

See example/main.dart for a runnable two-command app. The shape is:

final greetCommand = buildCommand(
  docs: const CommandDocs(brief: 'Greet someone'),
  parameters: CommandParameters(
    flags: FlagSet.one(
      BooleanFlag.required<ApplicationContext>(
        name: 'loud',
        brief: 'Shout the greeting',
      ),
    ).map((loud) => (loud: loud)),
    positional: PositionalSet.one(
      Positional.required<String, ApplicationContext>(
        brief: 'Who to greet',
        parse: stringParser,
        placeholder: 'name',
      ),
    ).map((name) => (name: name)),
  ),
  func: (context, flags, args) {
    final greeting = 'Hello, ${args.name}!';
    context.process.stdout.write(
      '${flags.loud ? greeting.toUpperCase() : greeting}\n',
    );
  },
);

and builds typed record pairs and map turns the result into the named record or class your handler should receive:

final output = ParsedFlag.optional<String, MyContext>(
  name: 'output',
  brief: 'Output path',
  parse: (context, input) => context.resolvePath(input),
);
final force = BooleanFlag.required<MyContext>(
  name: 'force',
  brief: 'Overwrite output',
);

final flags = FlagSet.one(output)
    .and(force)
    .map((values) => CopyOptions(values.$1, values.$2));

Use RunContext.direct(context) for one prebuilt context, or RunContext.forCommands to load one asynchronously from CommandInfo.prefix. The same context reaches parsers, completion callbacks, hooks, and the handler.

Integrations #

Pass an ordered List<CliIntegration<C>> to buildApplication for validation, lifecycle hooks, and application-level flags. Supplying a list—including an empty list—replaces the defaults:

final app = buildApplication(
  root,
  const ApplicationConfiguration(name: 'example'),
  integrations: [
    helpIntegration<MyContext>(),
    versionIntegration<MyContext>(
      info: const VersionInformation(currentVersion: '2.0.0'),
    ),
    CliIntegration(
      name: 'diagnostics',
      hooks: LifecycleHooks(
        commandStart: (args) => args.context.trace(args.result.prefix),
      ),
      flag: ApplicationFlag(
        brief: 'Print diagnostics',
        aliases: const ['d'],
        global: true,
        run: (args) => args.context.process.stdout.write('ok\n'),
      ),
    ),
  ],
);

Omit integrations to install the default help/help-all integrations and the version integration configured through ApplicationConfiguration.versionInfo.

$ dart run example/main.dart greet --loud world
HELLO, WORLD!

$ dart run example/main.dart gret world
No command registered for `gret`, did you mean `greet`?

$ dart run example/main.dart --help
USAGE
  example greet [--loud] <name>
  example --help

Example CLI

FLAGS
  -h --help  Print help information and exit

COMMANDS
  greet  Greet someone

Shell completion #

Register a hidden route that answers with one completion<TAB>description line per candidate, then print the matching script from a user-facing command:

final scripts = CompletionScripts(executableName: 'example');

// Inside the hidden `__complete` command:
final candidates = await proposeCompletions(
  app,
  scripts.resolveCompletionInputs(positional.cast<String>()),
  context,
);

// Inside `example completion zsh`:
context.process.stdout.write(scripts.zsh);

resolveCompletionInputs normalizes what the shell passed: it prefers COMP_LINE when set (which preserves a trailing space, meaning "start a new word") and drops a leading executable token. Shell function names are derived from the executable with illegal characters replaced, so my-cli produces __my_cli_complete rather than an unparseable __my-cli_complete.

The package test suite compiles a standalone fixture CLI and loads the generated scripts in bash, zsh, fish, and PowerShell. To require locally installed shells rather than skip them, set CLIWEAVE_E2E_SHELLS:

CLIWEAVE_E2E_SHELLS=bash,zsh,fish dart test -t e2e

On Windows PowerShell:

$env:CLIWEAVE_E2E_SHELLS = 'powershell'
dart test -t e2e

Environment access #

Anywhere this package consults the environment (NO_COLOR, FORCE_COLOR, CI, TERM, STRICLI_NO_COLOR) it takes an EnvLookup — a String? Function(String name) — defaulting to lookupPlatformEnv, which reads Platform.environment with a case-insensitive fallback on Windows. Pass your own to read from a validated wrapper, a config overlay, or a test double.

Origin #

This is a Dart implementation of the design introduced by Bloomberg's TypeScript @stricli/core: its command model, help layout, scanner error messages, and completion approach were the reference. It is an independent project, not a binding, and is not affiliated with or endorsed by Bloomberg.

0
likes
150
points
885
downloads

Documentation

API reference

Publisher

verified publishertinyrack.net

Weekly Downloads

A typed CLI framework: command routing, argument scanning with kebab/camel aliasing, did-you-mean suggestions, rendered help, structured exit codes, shell-completion proposals, and a terminal logger, spinner, and colour theme.

Topics

#cli #command-line #argument-parser #shell-completion

License

MIT (license)

More

Packages that depend on cliweave