关于 dart-cli-creator
面向创建或修改 Dart 命令行工具的 Agent,提供项目结构、命令处理、日志、错误管理、静态分析和发布方面的规范。
主要功能
- 01Dart CLI 项目结构
- 02子命令与帮助文本
- 03日志和交互式提示
- 04错误处理与退出码
- 05静态分析和发布指导
适用场景
- 01搭建 Dart CLI
- 02添加命令和选项
- 03改进 CLI 错误处理与输出
- 04准备 CLI 分发
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ mono0926/ skills --skill dart-cli-creator安装后在 Claude Code 中输入/dart-cli-creator确认已加载。
详细介绍
Dart CLI Creator Skill
此 Skill 为使用 Dart 创建和改进优质命令行界面(CLI)工具提供指导和流程。 作为 Agent,当用户要求创建或修改 Dart CLI 时,你必须始终遵循这些最佳实践。
🎯 核心最佳实践
1. 项目结构与设计
bin/目录:将 CLI 的入口可执行文件放在此处(例如,bin/my_cli.dart)。让入口文件保持轻量,只调用lib/或src/中的顶层函数或类,避免在此处编写复杂逻辑。lib/目录:将业务逻辑、命令实现和可复用工具放在此处(例如,lib/src/commands/、lib/src/utils/)。- 关注点分离:始终将 CLI 输入/输出处理(参数解析、UI 渲染)与核心业务逻辑分开,以最大限度提升可测试性。
2. 命令行参数与路由
- 充分利用
args包:使用标准args包中的CommandRunner和Command类,构建带子命令的 Git 风格 CLI。 - 自文档化:认真编写每个命令、标志和选项的
description和help,确保--help提供足够的用法信息。
3. 丰富的 UX(用户体验)与日志记录
- 积极使用
mason_logger:避免使用标准print语句;改用mason_logger包。- 使用
logger.info()、logger.success()、logger.err()和logger.warn()输出带颜色的消息。 - 对耗时任务使用
logger.progress('Doing something...')显示进度指示器。 - 使用
logger.confirm()或logger.prompt()进行用户确认(y/n)或文本输入。如需更高级的菜单选择或进度指示器,可搭配interact包使用。 - **提示:**若要采用实用的配置方式,可在
lib/logger.dart顶层定义final logger = Logger();,并在整个项目中导入使用。这可以减少样板代码,非常方便(适用于不强制要求 DI 的小型到中型 CLI 工具)。
- 使用
cli_completion:可以考虑使用cli_completion包,通过扩展CompletionCommandRunner提供 Shell 补全功能。
4. 正确处理错误与退出策略
- 退出代码:始终为进程结果设置恰当的退出代码(成功时为
0,出错时为1或符合sysexits规范的错误代码)。 - 避免直接调用
exit():直接调用exit()会导致 Dart VM 立即终止,可能跳过资源清理(例如finally块)。应改为从CommandRunner.run()返回一个int,将其传递给main函数并设置为exitCode属性,然后让进程自然退出(例如,等待Process.stdout.close()完成后退出)。 - 自定义异常类与处理:定义领域专属的自定义异常类(例如,
AppException),并在调用堆栈深处抛出这些异常。 - 全局异常捕获:用
try-catch块包裹CommandRunner的整个执行过程,以处理AppException、UsageException和任何未捕获的异常。不要直接向用户显示堆栈跟踪,而应使用logger.err(e.message)妥善输出错误,并返回恰当的退出代码。
5. 静态分析与代码格式化
- 应用
pedantic_mono:在analysis_options.yaml中配置pedantic_mono,并遵循严格的静态分析规则。 - [ZERO TOLERANCE]:修改代码后,始终立即解决所有错误、警告和 info 级别的 lint。只有分析结果干净,项目才能被视为处于健康状态。
6. 分发与执行配置
- Shebang:在
bin/目录下的可执行文件首行始终添加#!/usr/bin/env dart。 - Pubspec 配置:在
pubspec.yaml的executables:部分定义 CLI 命令名称,以便通过dart pub global activate轻松安装。
7. 自动更新检查功能
- 利用
pub_updater:使用pub_updater检查pub.dev或其他注册表是否发布了新版本,可在每次执行命令时检查,也可定期检查。 - 如果有新版本可用,请提供交互式界面,提示用户更新,例如使用
logger.warn('Update available!')或基于interact的提示。
8. 版本管理和发布流程
- 稳健的版本操作:操作 SemVer 版本时,尽可能避免手动解析版本。始终使用
pub_semver包(例如Version.parse、nextMajor、nextMinor),以防止因预发布标签或构建元数据等边缘情况导致解析失败。 - 自动化发布:如需处理版本升级、生成 CHANGELOG、提交 Git,以及发布到 GitHub 或 pub.dev,请使用
release-pub技能。如果要创建高级 Dart CLI,建议使用此 Agentic 技能安全地协调复杂的 CI/CD 和发布流水线,而不是指导用户手动执行这些步骤。
🛠️ 项目骨架(基本实现示例)
创建 CLI 时,请使用与以下内容相似的结构和实现作为基础。
pubspec.yaml 摘录
environment:
sdk: ^3.11.0 # Always specify the latest stable version
dependencies:
args: ^2.5.0
cli_completion: ^0.4.0
mason_logger: ^0.2.16
pub_updater: ^0.4.0
dev_dependencies:
pedantic_mono: any
test: ^1.24.0
executables:
my_cli:
bin/my_cli.dart
#!/usr/bin/env dart
import 'dart:io';
import 'package:my_cli/command_runner.dart';
Future<void> main(List<String> arguments) async {
final exitCode = await MyCliCommandRunner().run(arguments);
await flushThenExit(exitCode ?? 0);
}
/// Helper method to set the [status] to exitCode, and wait for the standard output/error to flush before exiting
Future<void> flushThenExit(int status) async {
exitCode = status;
await Future.wait<void>([
stdout.close(),
stderr.close(),
]);
}
lib/logger.dart
import 'package:mason_logger/mason_logger.dart';
/// The [Logger] shared across the application.
final logger = Logger();
lib/command_runner.dart
import 'package:args/args.dart';
import 'package:cli_completion/cli_completion.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:my_cli/logger.dart';
import 'package:pub_updater/pub_updater.dart';
class MyCliCommandRunner extends CompletionCommandRunner<int> {
MyCliCommandRunner({PubUpdater? pubUpdater})
: _pubUpdater = pubUpdater ?? PubUpdater(),
super('my_cli', 'A highly robust Dart CLI tool.') {
argParser
..addFlag(
'version',
abbr: 'v',
negatable: false,
help: 'Print the current version.',
)
..addFlag(
'verbose',
help: 'Enable verbose logging.',
);
// TODO: addCommand(MyCustomCommand());
}
final PubUpdater _pubUpdater;
Future<void> _checkForUpdates() async {
try {
final isUpToDate = await _pubUpdater.isUpToDate(
packageName: 'my_cli',
currentVersion: '1.0.0', // Optionally link to a packageVersion constant
);
if (!isUpToDate) {
final latestVersion = await _pubUpdater.getLatestVersion('my_cli');
logger.info('\nUpdate available: $latestVersion');
}
} catch (_) {}
}
@override
Future<int> run(Iterable<String> args) async {
try {
final argResults = parse(args);
if (argResults['verbose'] == true) {
logger.level = Level.verbose;
}
// Check for version updates (consider asynchronous background execution)
await _checkForUpdates();
return await runCommand(argResults) ?? 0;
} on FormatException catch (e) {
logger
..err(e.message)
..info('')
..info(usage);
return 64; // usage error
} on UsageException catch (e) {
logger
..err(e.message)
..info('')
..info(usage);
return 64;
} on AppException catch (e) {
// Gracefully output custom domain exceptions
logger.err(e.message);
return 1;
} catch (e, stackTrace) {
logger
..err('An unexpected error occurred: $e')
..err('$stackTrace');
return 1;
}
}
@override
Future<int?> runCommand(ArgResults topLevelResults) async {
if (topLevelResults['version'] == true) {
logger.info('my_cli version: 1.0.0');
return 0;
}
return super.runCommand(topLevelResults);
}
}
/// A custom exception specific to the domain.
class AppException implements Exception {
const AppException(this.message);
final String message;
@override
String toString() => message;
}
lib/src/commands/my_custom_command.dart
import 'package:args/command_runner.dart';
import 'package:my_cli/logger.dart';
class MyCustomCommand extends Command<int> {
MyCustomCommand() {
argParser.addOption(
'name',
abbr: 'n',
help: 'Your name.',
mandatory: true,
);
}
@override
String get description => 'A custom command example.';
@override
String get name => 'hello';
@override
Future<int> run() async {
final name = argResults?['name'] as String?;
final progress = logger.progress('Saying hello to $name...');
// Simulate a time-consuming task
await Future<void>.delayed(const Duration(seconds: 1));
progress.complete('Hello, $name!');
return 0; // Success
}
}
🤖 给 Agent 的说明
当用户请求“创建一个用于执行 X 的 CLI 工具”等内容时:
- 遵循此技能的内容,构建以
args和mason_logger为核心的项目。始终在pubspec.yaml中指定最新的稳定 Dart SDK 版本(补丁版本为 0,例如^3.11.0)。 - 根据需求确定目录结构,并规划或提出一组命令。
- 引入
pedantic_mono,并生成符合严格静态分析要求的高质量代码。 - (如有必要)开发完成后,在
README.md中记录使用说明,并向用户提供安装步骤,例如使用dart pub global activate --source path .。
评论
支持 Markdown