dart-build-cli-app
Build Dart command-line applications with proper entrypoint structure, exit codes, and cross-platform scripting. Use when creating CLI utilities, automation scripts, or standalone Dart executables.
What this skill does
# Building Dart CLI Applications
## Contents
- [Project Setup & Architecture](#project-setup--architecture)
- [Argument Parsing & Command Routing](#argument-parsing--command-routing)
- [Execution & Error Handling](#execution--error-handling)
- [Testing CLI Applications](#testing-cli-applications)
- [Compilation & Distribution](#compilation--distribution)
- [Workflows](#workflows)
- [Examples](#examples)
## Project Setup & Architecture
Initialize new CLI projects using the official Dart template to ensure standard directory structures.
* Run `dart create -t cli <project_name>` to scaffold a console application with basic argument parsing.
* Place executable entry points (files containing `main()`) exclusively in the `bin/` directory.
* Place internal implementation logic in `lib/src/` and expose public APIs via `lib/<project_name>.dart`.
* Enforce formatting in CI environments by running `dart format . --set-exit-if-changed`. This returns exit code 1 if formatting violations exist.
## Argument Parsing & Command Routing
Implement the `args` package to manage command-line arguments, flags, and subcommands.
* If building a simple script: Use `ArgParser` directly to define flags (`addFlag`) and options (`addOption`).
* If building a complex, multi-command CLI (like `git`): Implement `CommandRunner` and extend `Command` for each subcommand.
* Define global arguments on the `CommandRunner.argParser` and command-specific arguments on the individual `Command.argParser`.
* Catch `UsageException` to gracefully handle invalid arguments and display the automatically generated help text.
## Execution & Error Handling
Use the `io` and `stack_trace` packages to build reliable, production-ready CLI tools.
* Use the `io` package's `ExitCode` enum to return standard POSIX exit codes (e.g., `ExitCode.success.code`, `ExitCode.usage.code`).
* Use `sharedStdIn` from the `io` package if multiple asynchronous listeners need sequential access to standard input.
* Wrap the application execution in `Chain.capture()` from the `stack_trace` package to track asynchronous stack chains.
* Format output stack traces using `Trace.terse` or `Chain.terse` to strip noisy core library frames and present readable errors to the user.
## Testing CLI Applications
Use `test_process` and `test_descriptor` to write high-fidelity integration tests for your CLI.
* Define expected filesystem states using `test_descriptor` (`d.dir`, `d.file`).
* Create the mock filesystem before execution using `await d.Descriptor.create()`.
* Spawn the CLI process using `TestProcess.start('dart', ['run', 'bin/cli.dart', ...args])`.
* Validate standard output and error streams using `StreamQueue` matchers (e.g., `emitsThrough`, `emits`).
* Assert the final exit code using `await process.shouldExit(0)`.
* Validate resulting filesystem mutations using `await d.Descriptor.validate()`.
## Compilation & Distribution
Select the appropriate compilation target based on your distribution requirements.
* **If testing locally during development:** Use `dart run bin/cli.dart`. This uses the JIT compiler for rapid iteration.
* **If bundling code assets and dynamic libraries:** Use `dart build cli`. This runs build hooks and outputs to `build/cli/_/bundle/`.
* **If distributing a standalone native executable:** Use `dart compile exe bin/cli.dart -o <output_path>`. This bundles the Dart runtime and machine code into a single file.
* **If distributing multiple apps with strict disk space limits:** Use `dart compile aot-snapshot bin/cli.dart`. Run the resulting `.aot` file using `dartaotruntime`.
<details>
<summary>Cross-Compilation Targets (Linux Only)</summary>
Dart supports cross-compiling to Linux from macOS, Windows, or Linux hosts.
Use the `--target-os` and `--target-arch` flags with `dart compile exe` or `dart compile aot-snapshot`.
* `--target-os=linux` (Only Linux is currently supported as a cross-compilation target)
* `--target-arch=arm64` (64-bit ARM)
* `--target-arch=x64` (x86-64)
* `--target-arch=arm` (32-bit ARM)
* `--target-arch=riscv64` (64-bit RISC-V)
Example: `dart compile exe --target-os=linux --target-arch=arm64 bin/cli.dart`
</details>
## Workflows
### Task Progress: Implement a New CLI Command
- [ ] Create a new class extending `Command` in `lib/src/commands/`.
- [ ] Define the `name` and `description` properties.
- [ ] Register command-specific flags in the constructor using `argParser.addFlag()` or `argParser.addOption()`.
- [ ] Implement the `run()` method with the core logic.
- [ ] Register the new command in the `CommandRunner` instance in `bin/cli.dart` using `addCommand()`.
- [ ] Run validator -> Execute `dart run bin/cli.dart help <command_name>` to verify help text generation.
### Task Progress: Compile and Release Native Executable
- [ ] Run validator -> Execute `dart format . --set-exit-if-changed` to ensure code formatting.
- [ ] Run validator -> Execute `dart analyze` to ensure no static analysis errors.
- [ ] Run validator -> Execute `dart test` to pass all integration tests.
- [ ] Compile for host OS: `dart compile exe bin/cli.dart -o build/cli-host`
- [ ] Compile for Linux (if host is macOS/Windows): `dart compile exe --target-os=linux --target-arch=x64 bin/cli.dart -o build/cli-linux-x64`
## Examples
### Example: CommandRunner Implementation
```dart
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:stack_trace/stack_trace.dart';
class CommitCommand extends Command {
@override
final String name = 'commit';
@override
final String description = 'Record changes to the repository.';
CommitCommand() {
argParser.addFlag('all', abbr: 'a', help: 'Commit all changed files.');
}
@override
Future<void> run() async {
final commitAll = argResults?['all'] as bool? ?? false;
print('Committing... (All: $commitAll)');
}
}
void main(List<String> args) {
Chain.capture(() async {
final runner = CommandRunner('dgit', 'Distributed version control.')
..addCommand(CommitCommand());
await runner.run(args);
}, onError: (error, chain) {
if (error is UsageException) {
stderr.writeln(error.message);
stderr.writeln(error.usage);
exit(64); // ExitCode.usage.code
} else {
stderr.writeln('Fatal error: $error');
stderr.writeln(chain.terse);
exit(1);
}
});
}
```
### Example: Integration Testing with Subprocesses
```dart
import 'package:test/test.dart';
import 'package:test_process/test_process.dart';
import 'package:test_descriptor/test_descriptor.dart' as d;
void main() {
test('CLI formats output correctly and modifies filesystem', () async {
// 1. Setup mock filesystem
await d.dir('project', [
d.file('config.json', '{"key": "value"}')
]).create();
// 2. Spawn the CLI process
final process = await TestProcess.start(
'dart',
['run', 'bin/cli.dart', 'process', '--path', '${d.sandbox}/project']
);
// 3. Validate stdout stream
await expectLater(process.stdout, emitsThrough('Processing complete.'));
// 4. Validate exit code
await process.shouldExit(0);
// 5. Validate filesystem mutations
await d.dir('project', [
d.file('config.json', '{"key": "value"}'),
d.file('output.log', 'Success')
]).validate();
});
}
```
Related in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.