Skip to content
Development
Skill

/dart-build-cli-app

Architectural patterns, entrypoint structure, exit codes, stream routing, and subprocess spawning for Dart command-line interface (CLI) applications. Use when building CLI tools, console utilities, scripts, argument parsing with `package:args` (ArgParser or CommandRunner),

From plugin
agent-plugins
3.1k25 skills

Claude Code

$ npx -y skills add flutter/agent-plugins --skill dart-build-cli-app --agent claude-code

SKILL.md

dart-build-cli-app.SKILL.md
name: dart-build-cli-app
description: >-
  Architectural patterns, entrypoint structure, exit codes, stream routing, and subprocess spawning for Dart command-line interface (CLI) applications. Use when building CLI tools, console utilities, scripts, argument parsing with `package:args` (ArgParser or CommandRunner), handling exit codes, configuring executables in pubspec.yaml, spawning Dart subprocesses, or compiling native CLI binaries. Don't use for Flutter UI widgets, web applications, or standalone HTTP backend servers.

Building Dart CLI Applications

Contents

  • [1. Core Architecture & Process Lifecycle](#1-core-architecture--process-lifecycle)
  • [2. Streams, Diagnostics & Formatting](#2-streams-diagnostics--formatting)
  • [3. Project Configuration & Packaging](#3-project-configuration--packaging)
  • [4. Argument Parsing & Command Routing](#4-argument-parsing--command-routing)
  • [5. Native Async & Modern Stack Traces](#5-native-async--modern-stack-traces)
  • [6. Subprocess Spawning & AOT Resilience](#6-subprocess-spawning--aot-resilience)
  • [7. Signal Handling & Terminal Teardown](#7-signal-handling--terminal-teardown)
  • [8. Testing CLI Applications](#8-testing-cli-applications)
  • [9. Modern Compilation & Distribution](#9-modern-compilation--distribution)
  • [10. Workflows & Audit Checklist](#10-workflows--audit-checklist)
  • [References & Examples](#references--examples)

---

1. Core Architecture & Process Lifecycle

Avoid Destructive Exits (`exit(N)`)

Calling `dart:io`'s `exit(int code)` invokes `Platform::Exit(code)` in the C++ runtime. It immediately terminates the OS process without unwinding the Dart stack:

  • **Debugger Disconnect**: When launched with `--pause-isolates-on-exit`, the VM Service pauses isolates before shutdown to allow IDE inspection. `exit()` terminates the OS process before the VM Service can pause or inspect state.
  • **Coverage Loss**: `package:coverage` queries execution lines over VM Service RPCs during the paused-on-exit state. `exit()` destroys the process before RPC extraction, yielding 0% coverage.
  • **Buffer Truncation**: `stdout` and `stderr` are buffered asynchronous `IOSink` streams. `exit()` drops unflushed bytes.
  • **Resource Leaks**: `finally` blocks (closing locks, deleting temp directories) are bypassed.

**Rule**: Avoid calling `exit(code)` directly during normal execution; set `exitCode = code` or return an integer exit code from `CommandRunner<int>` (from `package:args`) and allow the asynchronous `main()` function to return naturally. Do not call `exit()` on unhandled errors; throw an unhandled `Error` or exception so the runtime unwinds cleanly and exits with a non-zero status.

Standard POSIX exit codes (`/usr/include/sysexits.h`):

  • `0`: Success (`EX_OK` / `ExitCode.success.code`)
  • `64`: Command-line usage error (`EX_USAGE` / `ExitCode.usage.code`)
  • `65`: Data format error (`EX_DATAERR` / `ExitCode.data.code`)
  • `70`: Internal software crash (`EX_SOFTWARE` / `ExitCode.software.code`)
  • `78`: Configuration error (`EX_CONFIG` / `ExitCode.config.code`)

*Note*: Prefer importing `package:io/io.dart` and using `ExitCode` constants (e.g., `ExitCode.usage.code`, `ExitCode.software.code`) rather than magic integer literals. For minimal standalone scripts without package dependencies, standard POSIX integer literals (`0`, `64`, `70`) may be used.

import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:io/io.dart' show ExitCode; // Provides standard POSIX ExitCode constants

Future<void> main(List<String> args) async {
  final runner = CommandRunner<int>('tool', 'CLI tool description.');
  try {
    final status = await runner.run(args);
    exitCode = status ?? ExitCode.success.code;
  } on UsageException catch (e) {
    stderr
      ..writeln(e.message)
      ..writeln(e.usage);
    exitCode = ExitCode.usage.code;
  }
}

The Thin Entrypoint Pattern (`bin/` vs. `lib/src/`)

Keep `bin/*.dart` files strictly as minimal entrypoint trampolines (instantiate runner, pass `args`, await exit code). Place all command definitions, argument parsers, formatters, and business logic inside `lib/src/`.

  • **Rationale**: Code in `bin/` cannot be cleanly imported via `package:` URIs. Moving logic into `lib/src/` allows the entire command runner, subcommand hierarchy, and business logic to be unit-tested in-memory in milliseconds (`< 2ms`) without spawning OS subprocesses.
// bin/my_cli.dart — Thin entrypoint trampoline
import 'dart:io';
import 'package:my_cli/src/cli.dart';

Future<void> main(List<String> args) async {
  exitCode = await runCli(args);
}

---

2. Output, Diagnostics & Formatting

  • **Data vs. Diagnostics**: Write intended program results and machine-readable data exclusively to `stdout`. Write warnings, error messages, and debug logs exclusively to `stderr`.
  • **The Error Usage Rule**: When an argument parsing or mandatory option error

occurs (`FormatException`, `UsageException`, or `ArgumentError` thrown when accessing a missing `mandatory: true` option via `results.option(...)`), **both the error message and the usage text must write to `stderr`**, and exit code `64` (`EX_USAGE` / `ExitCode.usage.code`) must be returned. `stdout` should ONLY receive usage help when the user explicitly requests it via `--help` or `-h`.

  • **No `print()` in Error Handlers**: `print()` routes to `stdout`. Use `stderr.writeln()` for all failure notifications. For standard output, prefer `stdout.writeln()` over `print()` to comply with the [`avoid_print`](https://dart.dev/tools/linter-rules/avoid_print) lint rule (unless `analysis_options.yaml` explicitly configures `avoid_print: false`).
  • **Terminal Capability Detection & `NO_COLOR`**: Verify `stdout.hasTerminal`, `stdout.supportsAnsiEscapes`, and `!Platform.environment.containsKey('NO_COLOR')` before emitting ANSI color or cursor escape codes:
  bool get useAnsi =>
      stdout.hasTerminal &&
      stdout.supportsAnsiEscape
Read more
@flutter
Ships withagent-plugins

Agent plugins for Flutter, maintained by the Flutter team. A collection of plugins designed to extend AI agent capabilities for Flutter development.

Stars on GitHub

Momentum

stars in total
3.1k
Star history of flutter/agent-plugins on GitHub, a chart by star-history.com

Other skills on agent-plugins.

dart-add-unit-test

dart-add-unit-test

Write and organize unit tests for functions, methods, and classes using `package:test`. Use when creating new logic or fixing…

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills11d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc12d293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react1d252k
@facebook@facebookInstall Skill

Questions about dart-build-cli-app.

Which coding agents can use dart-build-cli-app?
dart-build-cli-app installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.