dart-cli-creator

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Dart CLI Creator Skill

Dart CLI Creator Skill

このスキルは、Dartを用いて最高品質のCommand-Line Interface (CLI) ツールを作成・改善するためのガイドラインと手順です。 あなた(エージェント)は、ユーザーからDart CLIの作成・改修を依頼された際、常にこのベストプラクティスに従って作業を進めてください。
本技能是使用Dart打造最高质量的命令行界面(CLI)工具,并对其进行开发优化的指南和操作步骤。 当你(Agent)收到用户提出的Dart CLI开发或修改需求时,请始终遵循本最佳实践开展工作。

🎯 コア・ベストプラクティス

🎯 核心最佳实践

1. プロジェクト構造・設計

1. 项目结构与设计

  • bin/
    ディレクトリ
    : CLIのエントリポイントとなる実行ファイル(例:
    bin/my_cli.dart
    )を配置します。ここには複雑なロジックは書かず、
    lib/
    または
    src/
    のトップレベル関数やクラスを呼び出すだけの薄いラッパーにします。
  • lib/
    ディレクトリ
    : ビジネスロジック、コマンドの実装、再利用可能なユーティリティを配置します(例:
    lib/src/commands/
    lib/src/utils/
    )。
  • 責務の分離: CLIの入出力部分(引数のパースやUI描画)とコアなビジネスロジックは必ず分離し、テスタビリティを高めます。
  • bin/
    目录
    : 存放作为CLI入口点的可执行文件(例如:
    bin/my_cli.dart
    )。不要在此处编写复杂逻辑,仅将其作为调用
    lib/
    src/
    顶层函数、类的薄封装层即可。
  • lib/
    目录
    : 存放业务逻辑、命令实现、可复用工具类(例如:
    lib/src/commands/
    lib/src/utils/
    )。
  • 职责分离: 务必将CLI的输入输出部分(参数解析、UI渲染)与核心业务逻辑分离,提升可测试性。

2. コマンドライン引数とコマンドルーティング

2. 命令行参数与命令路由

  • args
    パッケージを活用
    : 標準の
    args
    パッケージにある
    CommandRunner
    Command
    クラスを使用して、Gitライクなサブコマンド対応のCLIを構築します。
  • 自己文書化: 各コマンドやフラグ・オプションには
    description
    help
    を丁寧に記述し、
    --help
    で十分な使い方が表示されるようにします。
  • 活用
    args
    : 使用官方
    args
    包提供的
    CommandRunner
    Command
    类,构建支持Git风格子命令的CLI。
  • 自文档化: 为每个命令、flag、选项详细编写
    description
    help
    内容,确保使用
    --help
    命令时可以展示完整的使用说明。

3. リッチなUX (ユーザー体験) とロギング

3. 丰富的UX(用户体验)与日志记录

  • mason_logger
    の積極利用
    : 標準の
    print
    は避け、
    mason_logger
    パッケージを利用します。
    • logger.info()
      ,
      logger.success()
      ,
      logger.err()
      ,
      logger.warn()
      で色分けされた出力。
    • 時間のかかる処理には
      logger.progress('Doing something...')
      を用いてスピナーを表示。
    • ユーザーの確認(y/n)や文字入力には
      logger.confirm()
      logger.prompt()
      を活用。また、より高度なメニュー選択やスピナー等が必要な場合は
      interact
      パッケージを併用します。
    • Tip: 実用的な構成として、
      lib/logger.dart
      にトップレベルで
      final logger = Logger();
      と定義し、プロジェクト全体でimportして使うとボイラープレートが減り非常に便利です(DIを必須としない小〜中規模のCLIツールに最適)。
  • cli_completion
    : シェル補完機能を提供するために、
    cli_completion
    パッケージの利用を検討し、
    CompletionCommandRunner
    を継承して実装します。
  • 积极使用
    mason_logger
    : 避免使用标准
    print
    方法,改用
    mason_logger
    包。
    • 使用
      logger.info()
      logger.success()
      logger.err()
      logger.warn()
      实现彩色输出。
    • 针对耗时处理,使用
      logger.progress('Doing something...')
      展示加载动画。
    • 针对用户确认(y/n)、文字输入场景,活用
      logger.confirm()
      logger.prompt()
      。如果需要更高级的菜单选择、加载动画等能力,可以搭配使用
      interact
      包。
    • 提示: 推荐实用配置:在
      lib/logger.dart
      中顶层定义
      final logger = Logger();
      ,整个项目直接导入使用即可,可大幅减少样板代码,非常适合不需要强制依赖注入的中小规模CLI工具。
  • cli_completion
    : 如需提供shell补全功能,可以考虑使用
    cli_completion
    包,继承
    CompletionCommandRunner
    进行实现。

4. 正しいエラーハンドリングと終了の手法

4. 正确的错误处理与程序退出方法

  • 終了コード(Exit Code): 処理結果は必ず適切な終了コードとして扱います(成功時は
    0
    、エラー時は
    1
    または
    sysexits
    準拠のエラーコード等)。
  • 直接の
    exit()
    は避ける
    :
    exit()
    を直接呼ぶと、Dart VMが即座に強制終了し、リソースのクリーンアップ(
    finally
    ブロックなど)が実行されない可能性があります。
    CommandRunner.run()
    の戻り値として
    int
    を返し、
    main
    関数に伝播させ、
    exitCode
    プロパティにセットして自然終了(
    Process.stdout.close()
    等の待機後)させます。
  • カスタム例外クラスの定義とハンドリング: 独自ドメインの例外クラス(例:
    AppException
    )を定義し、深い階層でスローします。
  • グローバルな例外捕捉:
    CommandRunner
    の実行全体を
    try-catch
    で囲み、
    AppException
    ,
    UsageException
    や未補足例外をハンドリングします。スタックトレースをユーザーに直接見せるのではなく、
    logger.err(e.message)
    のように丁寧にエラー出力し、適切な終了コードを返します。
  • 退出码(Exit Code): 处理结果务必对应合适的退出码(成功时返回
    0
    ,错误时返回
    1
    或符合
    sysexits
    规范的错误码等)。
  • 避免直接调用
    exit()
    : 直接调用
    exit()
    会导致Dart VM立即强制终止,可能无法执行资源清理逻辑(如
    finally
    代码块等)。应该将
    int
    类型的返回值作为
    CommandRunner.run()
    的返回值,向上传递到
    main
    函数,设置到
    exitCode
    属性中,等待
    Process.stdout.close()
    等操作完成后自然退出。
  • 自定义异常类的定义与处理: 定义业务域专属的异常类(例如
    AppException
    ),在深层逻辑中抛出。
  • 全局异常捕获: 使用
    try-catch
    包裹
    CommandRunner
    的全部执行逻辑,处理
    AppException
    UsageException
    以及未捕获的异常。不要直接向用户展示堆栈跟踪,而是通过
    logger.err(e.message)
    友好输出错误信息,返回合适的退出码。

5. 静的解析とコードフォーマット

5. 静态分析与代码格式化

  • pedantic_mono
    の適用
    :
    analysis_options.yaml
    pedantic_mono
    を設定し、厳格な静的解析ルールに従います。
  • コード変更後は必ずエラー・警告をゼロにします。
  • 应用
    pedantic_mono
    : 在
    analysis_options.yaml
    中配置
    pedantic_mono
    ,遵循严格的静态分析规则。
  • 代码修改后务必消除所有错误和警告。

6. 配布・実行のための設定

6. 分发与运行配置

  • Shebang:
    bin/
    直下の実行ファイルの1行目には、必ず
    #!/usr/bin/env dart
    を記述します。
  • Pubspecの設定:
    pubspec.yaml
    executables:
    セクションでCLIコマンド名を定義し、
    dart pub global activate
    で簡単にインストールできるようにします。
  • Shebang:
    bin/
    目录下的可执行文件首行必须编写
    #!/usr/bin/env dart
  • Pubspec配置: 在
    pubspec.yaml
    executables:
    段定义CLI命令名,方便用户通过
    dart pub global activate
    一键安装。

7. アップデート通知機能 (Auto-update check)

7. 更新通知功能(自动更新检查)

  • pub_updater
    を活用
    : コマンド実行の都度または定期的に
    pub_updater
    を用いて、
    pub.dev
    等に新しいバージョンがリリースされていないかチェックします。
  • 新バージョンがある場合は、
    logger.warn('Update available!')
    や、
    interact
    ベースのプロンプトで更新を促すインタラクティブなUIを提供します。

  • 活用
    pub_updater
    : 每次执行命令或定期使用
    pub_updater
    检查
    pub.dev
    等平台是否有新版本发布。
  • 如果存在新版本,可以通过
    logger.warn('Update available!')
    提示,或者提供基于
    interact
    的交互式弹窗引导用户更新。

🛠️ プロジェクトのスケルトン(基本実装例)

🛠️ 项目骨架(基础实现示例)

CLIを作成する際は、以下のような構造と実装をベースにします。
创建CLI时,可以参考以下结构和实现作为基础。

pubspec.yaml
の一部

pubspec.yaml
部分内容

yaml
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:
yaml
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

bin/my_cli.dart

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);
}

/// [status]をexitCodeに設定し、標準出力/標準エラーのフラッシュを待って終了するヘルパー
Future<void> flushThenExit(int status) async {
  exitCode = status;
  await Future.wait<void>([
    stdout.close(),
    stderr.close(),
  ]);
}
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);
}

/// 将[status]设置为exitCode,等待标准输出/标准错误冲刷完成后退出的工具方法
Future<void> flushThenExit(int status) async {
  exitCode = status;
  await Future.wait<void>([
    stdout.close(),
    stderr.close(),
  ]);
}

lib/logger.dart

lib/logger.dart

dart
import 'package:mason_logger/mason_logger.dart';

/// アプリケーション共通の [Logger]。
final logger = Logger();
dart
import 'package:mason_logger/mason_logger.dart';

/// 应用全局通用[Logger]。
final logger = Logger();

lib/command_runner.dart

lib/command_runner.dart

dart
import 'package:args/args.dart';
import 'package:cli_completion/cli_completion.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:pub_updater/pub_updater.dart';
import 'package:my_cli/logger.dart';

class MyCliCommandRunner extends CompletionCommandRunner<int> {
  final PubUpdater _pubUpdater;

  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());
  }

  Future<void> _checkForUpdates() async {
    try {
      final isUpToDate = await _pubUpdater.isUpToDate(
        packageName: 'my_cli',
        currentVersion: '1.0.0', // 本来は packageVersion
      );
      if (!isUpToDate) {
        final latestVersion = await _pubUpdater.getLatestVersion('my_cli');
        logger.info('\nUpdate available: $latestVersion');
      }
    } catch (_) {}
  }

  
  Future<int> run(Iterable<String> args) async {
    try {
      final argResults = parse(args);
      if (argResults['verbose'] == true) {
        logger.level = Level.verbose;
      }

      // バージョン更新チェック (非同期でバックグラウンド実行も検討)
      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) {
      // カスタム例外はきれいにエラー出力
      logger.err(e.message);
      return 1;
    } catch (e, stackTrace) {
      logger
        ..err('An unexpected error occurred: $e')
        ..err('$stackTrace');
      return 1;
    }
  }

  
  Future<int?> runCommand(ArgResults topLevelResults) async {
    if (topLevelResults['version'] == true) {
      logger.info('my_cli version: 1.0.0');
      return 0;
    }
    return super.runCommand(topLevelResults);
  }
}

/// ドメイン固有のカスタム例外
class AppException implements Exception {
  const AppException(this.message);
  final String message;
  
  String toString() => message;
}
dart
import 'package:args/args.dart';
import 'package:cli_completion/cli_completion.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:pub_updater/pub_updater.dart';
import 'package:my_cli/logger.dart';

class MyCliCommandRunner extends CompletionCommandRunner<int> {
  final PubUpdater _pubUpdater;

  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());
  }

  Future<void> _checkForUpdates() async {
    try {
      final isUpToDate = await _pubUpdater.isUpToDate(
        packageName: 'my_cli',
        currentVersion: '1.0.0', // 实际使用时读取package版本
      );
      if (!isUpToDate) {
        final latestVersion = await _pubUpdater.getLatestVersion('my_cli');
        logger.info('\nUpdate available: $latestVersion');
      }
    } catch (_) {}
  }

  
  Future<int> run(Iterable<String> args) async {
    try {
      final argResults = parse(args);
      if (argResults['verbose'] == true) {
        logger.level = Level.verbose;
      }

      // 版本更新检查(也可考虑异步后台执行)
      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) {
      // 自定义异常友好输出错误
      logger.err(e.message);
      return 1;
    } catch (e, stackTrace) {
      logger
        ..err('An unexpected error occurred: $e')
        ..err('$stackTrace');
      return 1;
    }
  }

  
  Future<int?> runCommand(ArgResults topLevelResults) async {
    if (topLevelResults['version'] == true) {
      logger.info('my_cli version: 1.0.0');
      return 0;
    }
    return super.runCommand(topLevelResults);
  }
}

/// 业务域自定义异常
class AppException implements Exception {
  const AppException(this.message);
  final String message;
  
  String toString() => message;
}

lib/src/commands/my_custom_command.dart

lib/src/commands/my_custom_command.dart

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,
    );
  }

  
  String get description => 'A custom command example.';

  
  String get name => 'hello';

  
  Future<int> run() async {
    final name = argResults?['name'] as String?;
    final progress = logger.progress('Saying hello to \$name...');

    // 時間のかかる処理のシミュレート
    await Future<void>.delayed(const Duration(seconds: 1));

    progress.complete('Hello, \$name!');
    return 0; // Success
  }
}
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,
    );
  }

  
  String get description => 'A custom command example.';

  
  String get name => 'hello';

  
  Future<int> run() async {
    final name = argResults?['name'] as String?;
    final progress = logger.progress('Saying hello to \$name...');

    // 模拟耗时操作
    await Future<void>.delayed(const Duration(seconds: 1));

    progress.complete('Hello, \$name!');
    return 0; // Success
  }
}

🤖 エージェントへの指示

🤖 给Agent的指示

ユーザーから「〇〇をするツールのCLIを作って」等と依頼された場合:
  1. このスキルの内容に沿って、
    args
    mason_logger
    を軸としたプロジェクトを構成してください。
  2. ディレクトリ構成を決定し、要件定義に基づくコマンド群を整理・提案してください。
  3. pedantic_mono
    を導入し、厳格な静的解析を前提とした高品質なコードを生成してください。
  4. (必要なら) 開発完了後に、
    README.md
    にCLIの使い方( Usage )を記載し、
    dart pub global activate --source path .
    によるインストール手順などをユーザーに提示してください。
当用户提出「帮我做一个实现XX功能的CLI工具」等需求时:
  1. 遵循本技能的内容,搭建以
    args
    mason_logger
    为核心的项目结构。
  2. 确定目录结构,基于需求整理并建议对应的命令集合。
  3. 引入
    pedantic_mono
    ,生成符合严格静态分析要求的高质量代码。 4.(必要时)开发完成后,在
    README.md
    中编写CLI的使用说明(Usage),向用户提供
    dart pub global activate --source path .
    等安装步骤指引。