modular_cli_sdk 0.11.0
modular_cli_sdk: ^0.11.0 copied to clipboard
Command-centric SDK for building modular CLIs with Dart — Command/Input/Output contract, structured errors, output formatting, and automatic TTY detection. Built on cli_router.
/// example/example.dart
/// Minimal runnable example, mirrors example/example.dart from modular_api.
///
/// Run:
/// dart run example/example.dart # the root command
/// dart run example/example.dart help # the command catalog
/// dart run example/example.dart help --json # the catalog as JSON
/// dart run example/example.dart version
/// dart run example/example.dart version --json
/// dart run example/example.dart greetings hello --name World
/// dart run example/example.dart greetings hello --name World --json
/// dart run example/example.dart math add --a 3 --b 7
/// dart run example/example.dart math add --a 3 --b 7 --json
/// dart run example/example.dart math multiply --a 4 --b 5 --json
///
/// Everything above reads. The one route that writes is told which of the two
/// it is doing, and refuses to guess. `notes write` takes a positional (the
/// note's name), and `cli_router` requires every option to precede the first
/// positional on the command line, so `--plan`/`--apply`/`--json` come before
/// `today` here:
///
/// dart run example/example.dart notes write --plan today
/// dart run example/example.dart notes write --apply --autoapprove today
/// dart run example/example.dart notes write --plan --json today
library;
import 'dart:io';
import 'package:modular_cli_sdk/modular_cli_sdk.dart';
import 'commands/status.dart';
import 'modules/greetings/greetings_builder.dart';
import 'modules/math/math_builder.dart';
import 'modules/notes/notes_builder.dart';
// ─── CLI ─────────────────────────────────────────────────────────────────────
Future<void> main(List<String> args) async {
final code = await runExample(args);
exit(code);
}
Future<int> runExample(
List<String> args, {
IOSink? stdout,
IOSink? stderr,
Approver? approver,
PlanSink? planSink,
}) async {
// [approver] and [planSink] are the two decisions the SDK leaves to the host:
// how an approval is taken, and whether a plan is kept on disk. Passing them
// in is also what lets the suite exercise `--apply` without a terminal.
//
// name/version identify this CLI to its own plugins: VersionPlugin reads
// them back, and any plugin declaring a hostApiVersion constraint is
// checked against cliPluginHostApiVersion when the plugin set is built.
final cli = ModularCli(
approver: approver,
planSink: planSink,
suggestionDistance: 2,
name: 'example',
version: '0.2.0',
)..plugin(const VersionPlugin(version: '0.2.0'));
// The root command: what the bare invocation runs. Registering it means this
// CLI, not the help, owns the empty invocation.
cli.query<StatusInput, StatusOutput>(
'',
(req) => StatusQuery(StatusInput.fromCliRequest(req)),
globals: true,
contract: CliContract.none,
description: 'Show the CLI status',
);
// Module-scoped commands
cli.module('greetings', buildGreetingsModule);
cli.module('math', buildMathModule);
cli.module('notes', buildNotesModule);
return cli.run(args, stdout: stdout, stderr: stderr);
}