cliweave 0.2.2
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.
proposeCompletionscomputes the candidate list for any partial command line — including per-argument dynamic values (file paths, remote names) from your own callback — andCompletionScriptsgenerates the bash, zsh, fish, and PowerShell scripts that call back into it.package:argshas no completion API;cli_completionadds 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/COMMANDSsections, column alignment, and the--flag/--no-flagpresentation are all part of the contract you can pin in tests. - kebab⇄camelCase aliasing.
--dry-runand--dryRunboth bind to adryRunflag, 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.