mcpField
D
dart-cli-creator

dart-cli-creator

github.com/mono0926开发286 次安装2 周前更新
代码仓库

关于 dart-cli-creator

面向创建或修改 Dart 命令行工具的 Agent,提供项目结构、命令处理、日志、错误管理、静态分析和发布方面的规范。

主要功能

  1. 01Dart CLI 项目结构
  2. 02子命令与帮助文本
  3. 03日志和交互式提示
  4. 04错误处理与退出码
  5. 05静态分析和发布指导

适用场景

  1. 01搭建 Dart CLI
  2. 02添加命令和选项
  3. 03改进 CLI 错误处理与输出
  4. 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 工具”等内容时:

  1. 遵循此技能的内容,构建以 args 和 mason_logger 为核心的项目。始终在 pubspec.yaml 中指定最新的稳定 Dart SDK 版本(补丁版本为 0,例如 ^3.11.0)。
  2. 根据需求确定目录结构,并规划或提出一组命令。
  3. 引入 pedantic_mono,并生成符合严格静态分析要求的高质量代码。
  4. (如有必要)开发完成后,在 README.md 中记录使用说明,并向用户提供安装步骤,例如使用 dart pub global activate --source path .。

评论

支持 Markdown