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',
aliases: const ['ex'],
);
// 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);
The generated scripts invoke the hidden command without forwarding the
flag-like completion words as arguments. They pass the raw command line in
COMP_LINE, which preserves a trailing space (meaning "start a new word") and
keeps prefixes such as --wit out of the application's argument scanner.
resolveCompletionInputs drops the first token from COMP_LINE as the
invocation name, so aliases, symlinks, paths, and renamed executables complete
the same as the configured executable. It still accepts positional tokens as a
fallback for direct callers and applies the stricter executable-name check to
that path.
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.
aliases registers additional command names with the generated completion
scripts without defining the aliases themselves. The canonical
executableName is always registered first and duplicate aliases are ignored.
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.
Libraries
- cliweave
- A typed CLI framework: command routing, argument scanning, rendered help, structured exit codes, and shell-completion proposals.
- terminal
- Terminal output for command-line applications: a levelled logger, a
spinner that degrades to plain lines off a TTY, and a colour theme that
honours
NO_COLOR,FORCE_COLOR, andCI.