From 3706ab8f1c0f1ea63710b06f26f9390c8d796eaa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Malo=20L=C3=A9on?= <malo.leon@wyatt-studio.fr>
Date: Mon, 19 Feb 2024 12:57:59 +0000
Subject: [PATCH] chore(wyatt_continuous_deployment): migrate the all CLI as
 package

---
 .../example/lib/main.dart                     |   2 +-
 .../lib/src/bootstrap.dart                    |  26 ++++
 .../build_and_deploy_app_command.dart         |  36 +++++
 .../build_and_deploy_android_app_command.dart |  32 +++++
 .../build_and_deploy_android_app_task.dart    |  50 +++++++
 .../build_and_deploy_ios_app_command.dart     |  31 ++++
 .../build_and_deploy_ios_app_task.dart        |  50 +++++++
 .../commands/build_app/build_app_command.dart |  35 +++++
 .../commands/build_app/build_app_states.dart  |  32 +++++
 .../commands/build_app/build_app_task.dart    |  57 ++++++++
 .../build_app_bundle_command.dart             |  31 ++++
 .../build_app_bundle_state.dart               |  32 +++++
 .../build_app_bundle_task.dart                |  91 ++++++++++++
 .../commands/build_ipa/build_ipa_command.dart |  31 ++++
 .../commands/build_ipa/build_ipa_state.dart   |  47 +++++++
 .../commands/build_ipa/build_ipa_task.dart    | 133 ++++++++++++++++++
 .../lib/src/core/commands/commands.dart       |  20 +++
 .../deploy_android_command.dart               |  31 ++++
 .../deploy_android/deploy_android_state.dart  |  27 ++++
 .../deploy_android/deploy_android_task.dart   |  84 +++++++++++
 .../deploy_ios/deploy_ios_command.dart        |  31 ++++
 .../commands/deploy_ios/deploy_ios_state.dart |  42 ++++++
 .../commands/deploy_ios/deploy_ios_task.dart  | 118 ++++++++++++++++
 .../deploy_app/deploy_app_command.dart        |  35 +++++
 .../init_project/init_project_command.dart    |  39 +++++
 .../init_project/init_project_state.dart      |  28 ++++
 .../init_project/init_project_task.dart       |  85 +++++++++++
 .../lib/src/core/commands/task_command.dart   |  80 +++++++++++
 .../lib/src/core/constants/app_constants.dart |   7 +
 .../lib/src/core/core.dart                    |  17 +++
 .../lib/src/core/enums/flag.dart              |  50 +++++++
 .../src/core/extensions/object_extension.dart |  23 +++
 .../lib/src/core/task/task_cubit.dart         |  90 ++++++++++++
 .../lib/src/core/task/task_state.dart         |  85 +++++++++++
 .../lib/src/core/utils/logging.dart           |   5 +-
 .../lib/src/core/utils/task_observer.dart     |  36 +++++
 .../lib/src/domain/entities/context.dart      |  33 +++++
 .../lib/src/domain/entities/flutter.dart      |   6 +-
 .../config/get_android_config_usecase.dart    |  14 +-
 .../config/get_ios_config_usecase.dart        |  12 +-
 .../usecases/config/init_config_usecase.dart  |  15 +-
 .../decrypt_keys/decrypt_keys_usecase.dart    |  18 ++-
 .../usecases/find_last_ipa_file_usecase.dart  |  14 +-
 .../src/domain/usecases/process_usecase.dart  |  19 ++-
 .../check_config_file_usecase.dart            |  15 +-
 .../requirements/check_tools_usecase.dart     |   3 +-
 .../lib/src/domain/usecases/usecase.dart      |  34 +++++
 .../lib/src/version.dart                      |   1 +
 .../lib/src/wyatt_continuous_deployment.dart  |  22 +--
 ..._continuous_deployment_command_runner.dart | 110 +++++++++++++++
 .../lib/wyatt_continuous_deployment.dart      |   3 +-
 .../wyatt_continuous_deployment/pubspec.yaml  |   4 +
 52 files changed, 1924 insertions(+), 48 deletions(-)
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/bootstrap.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/build_and_deploy_app_command.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_android_app/build_and_deploy_android_app_command.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_android_app/build_and_deploy_android_app_task.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_ios_app/build_and_deploy_ios_app_command.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_ios_app/build_and_deploy_ios_app_task.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/build_app_command.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/build_app_states.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/build_app_task.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_command.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_state.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_task.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_ipa/build_ipa_command.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_ipa/build_ipa_state.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_ipa/build_ipa_task.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/commands.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_android/deploy_android_command.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_android/deploy_android_state.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_android/deploy_android_task.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_command.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_state.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_task.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/deploy_app_command.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/init_project/init_project_command.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/init_project/init_project_state.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/init_project/init_project_task.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/commands/task_command.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/core.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/enums/flag.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/extensions/object_extension.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/task/task_cubit.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/task/task_state.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/core/utils/task_observer.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/domain/entities/context.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/domain/usecases/usecase.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/version.dart
 create mode 100644 packages/wyatt_continuous_deployment/lib/src/wyatt_continuous_deployment_command_runner.dart

diff --git a/packages/wyatt_continuous_deployment/example/lib/main.dart b/packages/wyatt_continuous_deployment/example/lib/main.dart
index d3342cf8..6eb17eda 100644
--- a/packages/wyatt_continuous_deployment/example/lib/main.dart
+++ b/packages/wyatt_continuous_deployment/example/lib/main.dart
@@ -20,7 +20,7 @@ class WyattContinuousDeploymentExample {
   static Future<void> run(List<String> args) async {
     const useCase = CheckToolsUsecase();
 
-    final result = await useCase();
+    final result = await useCase(null);
 
     result.fold((value) => print('Success'), (error) => print('Error: $error'));
   }
diff --git a/packages/wyatt_continuous_deployment/lib/src/bootstrap.dart b/packages/wyatt_continuous_deployment/lib/src/bootstrap.dart
new file mode 100644
index 00000000..33660195
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/bootstrap.dart
@@ -0,0 +1,26 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'package:bloc/bloc.dart';
+import 'package:mason_logger/mason_logger.dart';
+import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart';
+import 'package:wyatt_continuous_deployment/src/core/utils/task_observer.dart';
+
+Future<void> bootstrap([Logger? logger]) async {
+  getIt.registerSingleton<Logger>(logger ?? Logger());
+  await Injectable.configureDependencies();
+  Bloc.observer = TaskObserver();
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/build_and_deploy_app_command.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/build_and_deploy_app_command.dart
new file mode 100644
index 00000000..e95b779d
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/build_and_deploy_app_command.dart
@@ -0,0 +1,36 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'package:args/command_runner.dart';
+import 'package:wyatt_continuous_deployment/src/core/commands/build_and_deploy_app/commands/build_and_deploy_android_app/build_and_deploy_android_app_command.dart';
+import 'package:wyatt_continuous_deployment/src/core/commands/build_and_deploy_app/commands/build_and_deploy_ios_app/build_and_deploy_ios_app_command.dart';
+
+class BuildAndDeployAppCommand extends Command<int> {
+  BuildAndDeployAppCommand() {
+    addSubcommand(BuildAndDeployIosAppCommand());
+    addSubcommand(BuildAndDeployAndroidAppCommand());
+  }
+
+  @override
+  String get description => 'Build and deploy app. '
+      'Same as running `build` and `deploy` commands separately.';
+
+  @override
+  String get name => 'build-deploy';
+
+  @override
+  List<String> get aliases => ['bd'];
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_android_app/build_and_deploy_android_app_command.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_android_app/build_and_deploy_android_app_command.dart
new file mode 100644
index 00000000..8aeb749c
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_android_app/build_and_deploy_android_app_command.dart
@@ -0,0 +1,32 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'package:wyatt_continuous_deployment/src/core/commands/build_and_deploy_app/commands/build_and_deploy_android_app/build_and_deploy_android_app_task.dart';
+import 'package:wyatt_continuous_deployment/src/core/commands/task_command.dart';
+
+class BuildAndDeployAndroidAppCommand extends TaskCommand {
+  BuildAndDeployAndroidAppCommand()
+      : super(task: BuildAndDeployAndroidAppTask());
+
+  @override
+  String get description => 'Build and deploy Android app';
+
+  @override
+  String get name => 'android';
+
+  @override
+  List<String> get aliases => ['a', 'playstore'];
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_android_app/build_and_deploy_android_app_task.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_android_app/build_and_deploy_android_app_task.dart
new file mode 100644
index 00000000..e52cd52d
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_android_app/build_and_deploy_android_app_task.dart
@@ -0,0 +1,50 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'package:wyatt_continuous_deployment/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_task.dart';
+import 'package:wyatt_continuous_deployment/src/core/commands/deploy_app/commands/deploy_android/deploy_android_task.dart';
+import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart';
+import 'package:wyatt_continuous_deployment/src/core/task/task_cubit.dart';
+import 'package:wyatt_continuous_deployment/src/domain/entities/context.dart';
+
+class BuildAndDeployAndroidAppTask extends TaskCubit {
+  factory BuildAndDeployAndroidAppTask() => BuildAndDeployAndroidAppTask._(
+        checkToolsUsecase: getIt(),
+        checkConfigFileUsecase: getIt(),
+      );
+
+  BuildAndDeployAndroidAppTask._({
+    required super.checkToolsUsecase,
+    required super.checkConfigFileUsecase,
+  });
+
+  @override
+  Future<void> onRun(Context ctx) async {
+    // 1. Build Android app
+    final buildAppBundleTask = BuildAppBundleTask();
+    await buildAppBundleTask.onRun(ctx);
+    if (buildAppBundleTask.status != 0) {
+      return emit(buildAppBundleTask.state);
+    }
+
+    // 2. Deploy it
+    final deployAndroidTask = DeployAndroidTask();
+    await deployAndroidTask.onRun(ctx);
+    if (deployAndroidTask.status != 0) {
+      return emit(deployAndroidTask.state);
+    }
+  }
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_ios_app/build_and_deploy_ios_app_command.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_ios_app/build_and_deploy_ios_app_command.dart
new file mode 100644
index 00000000..8b9b7dcd
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_ios_app/build_and_deploy_ios_app_command.dart
@@ -0,0 +1,31 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'package:wyatt_continuous_deployment/src/core/commands/build_and_deploy_app/commands/build_and_deploy_ios_app/build_and_deploy_ios_app_task.dart';
+import 'package:wyatt_continuous_deployment/src/core/commands/task_command.dart';
+
+class BuildAndDeployIosAppCommand extends TaskCommand {
+  BuildAndDeployIosAppCommand() : super(task: BuildAndDeployIosAppTask());
+
+  @override
+  String get description => 'Build and deploy IOS app';
+
+  @override
+  String get name => 'ios';
+
+  @override
+  List<String> get aliases => ['i', 'appstore'];
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_ios_app/build_and_deploy_ios_app_task.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_ios_app/build_and_deploy_ios_app_task.dart
new file mode 100644
index 00000000..3cb656b1
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_and_deploy_app/commands/build_and_deploy_ios_app/build_and_deploy_ios_app_task.dart
@@ -0,0 +1,50 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'package:wyatt_continuous_deployment/src/core/commands/build_app/commands/build_ipa/build_ipa_task.dart';
+import 'package:wyatt_continuous_deployment/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_task.dart';
+import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart';
+import 'package:wyatt_continuous_deployment/src/core/task/task_cubit.dart';
+import 'package:wyatt_continuous_deployment/src/domain/entities/context.dart';
+
+class BuildAndDeployIosAppTask extends TaskCubit {
+  factory BuildAndDeployIosAppTask() => BuildAndDeployIosAppTask._(
+        checkToolsUsecase: getIt(),
+        checkConfigFileUsecase: getIt(),
+      );
+
+  BuildAndDeployIosAppTask._({
+    required super.checkToolsUsecase,
+    required super.checkConfigFileUsecase,
+  });
+
+  @override
+  Future<void> onRun(Context ctx) async {
+    // 1. Build ipa
+    final buildIpaTask = BuildIpaTask();
+    await buildIpaTask.onRun(ctx);
+    if (buildIpaTask.status != 0) {
+      return emit(buildIpaTask.state);
+    }
+
+    // 2. Deploy it
+    final deployIosTask = DeployIosTask();
+    await deployIosTask.onRun(ctx);
+    if (deployIosTask.status != 0) {
+      return emit(deployIosTask.state);
+    }
+  }
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/build_app_command.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/build_app_command.dart
new file mode 100644
index 00000000..18c626f7
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/build_app_command.dart
@@ -0,0 +1,35 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'package:args/command_runner.dart';
+import 'package:wyatt_continuous_deployment/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_command.dart';
+import 'package:wyatt_continuous_deployment/src/core/commands/build_app/commands/build_ipa/build_ipa_command.dart';
+
+class BuildAppCommand extends Command<int> {
+  BuildAppCommand() {
+    addSubcommand(BuildAppBundleCommand());
+    addSubcommand(BuildIpaCommand());
+  }
+
+  @override
+  String get description => 'Build the app';
+
+  @override
+  String get name => 'build';
+
+  @override
+  List<String> get aliases => ['b'];
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/build_app_states.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/build_app_states.dart
new file mode 100644
index 00000000..34adf766
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/build_app_states.dart
@@ -0,0 +1,32 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+part of 'build_app_task.dart';
+
+class BuildAppTaskFlutterClean extends TaskStateRunning {
+  @override
+  String get description => 'Clean flutter workspace';
+}
+
+class BuildAppTaskFlutterPubGet extends TaskStateRunning {
+  @override
+  String get description => 'Get dependencies';
+}
+
+class BuildAppTaskBuildApp extends TaskStateRunning {
+  @override
+  String get description => 'Build app';
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/build_app_task.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/build_app_task.dart
new file mode 100644
index 00000000..6e42d086
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/build_app_task.dart
@@ -0,0 +1,57 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'dart:async';
+
+import 'package:wyatt_continuous_deployment/src/core/task/task_cubit.dart';
+import 'package:wyatt_continuous_deployment/src/domain/entities/context.dart';
+import 'package:wyatt_continuous_deployment/src/domain/entities/flutter.dart';
+
+part 'build_app_states.dart';
+
+abstract class BuildAppTask extends TaskCubit {
+  BuildAppTask({
+    required this.flutter,
+    required super.checkToolsUsecase,
+    required super.checkConfigFileUsecase,
+  });
+
+  final Flutter flutter;
+
+  FutureOr<void> buildApp(Context ctx);
+
+  @override
+  Future<void> onRun(Context ctx) async {
+    emit(BuildAppTaskFlutterClean());
+
+    final flutterCleanResult = await flutter.clean();
+    if (flutterCleanResult.isErr) {
+      return emit(TaskStateError(message: flutterCleanResult.err.toString()));
+    }
+
+    emit(BuildAppTaskFlutterPubGet());
+
+    final flutterGetDependenciesResult = await flutter.getDependencies();
+    if (flutterGetDependenciesResult.isErr) {
+      return emit(
+        TaskStateError(message: flutterGetDependenciesResult.err.toString()),
+      );
+    }
+
+    emit(BuildAppTaskBuildApp());
+    await buildApp(ctx);
+  }
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_command.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_command.dart
new file mode 100644
index 00000000..2f86db8d
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_command.dart
@@ -0,0 +1,31 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'package:wyatt_continuous_deployment/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_task.dart';
+import 'package:wyatt_continuous_deployment/src/core/commands/task_command.dart';
+
+class BuildAppBundleCommand extends TaskCommand {
+  BuildAppBundleCommand() : super(task: BuildAppBundleTask());
+
+  @override
+  String get description => 'Build the Android app bundle';
+
+  @override
+  String get name => 'appbundle';
+
+  @override
+  List<String> get aliases => ['a', 'aab', 'android'];
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_state.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_state.dart
new file mode 100644
index 00000000..952af2ff
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_state.dart
@@ -0,0 +1,32 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+part of 'build_app_bundle_task.dart';
+
+class BuildAppBundleStateGetAndroidConfig extends TaskStateRunning {
+  @override
+  String get description => 'Get android config';
+}
+
+class BuildAppBundleStateDecryptAndroidKeys extends TaskStateRunning {
+  @override
+  String get description => 'Decrypt android keys';
+}
+
+class BuildAppBundleStateBuildAppBundle extends TaskStateRunning {
+  @override
+  String get description => 'Build App Bundle';
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_task.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_task.dart
new file mode 100644
index 00000000..bd821b74
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_app_bundle/build_app_bundle_task.dart
@@ -0,0 +1,91 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'dart:async';
+
+import 'package:wyatt_continuous_deployment/src/core/commands/build_app/build_app_task.dart';
+import 'package:wyatt_continuous_deployment/src/core/constants/app_constants.dart';
+import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart';
+import 'package:wyatt_continuous_deployment/src/core/task/task_cubit.dart';
+import 'package:wyatt_continuous_deployment/src/domain/entities/context.dart';
+import 'package:wyatt_continuous_deployment/src/domain/entities/decrypt_keys_parameters.dart';
+import 'package:wyatt_continuous_deployment/src/domain/usecases/config/get_android_config_usecase.dart';
+import 'package:wyatt_continuous_deployment/src/domain/usecases/decrypt_keys/decrypt_keys_usecase.dart';
+
+part 'build_app_bundle_state.dart';
+
+class BuildAppBundleTask extends BuildAppTask {
+  factory BuildAppBundleTask() => BuildAppBundleTask._(
+        flutter: getIt(),
+        decryptKeysUsecase: getIt(),
+        getAndroidConfigUsecase: getIt(),
+        checkToolsUsecase: getIt(),
+        checkConfigFileUsecase: getIt(),
+      );
+
+  BuildAppBundleTask._({
+    required DecryptKeysUsecase decryptKeysUsecase,
+    required GetAndroidConfigUsecase getAndroidConfigUsecase,
+    required super.flutter,
+    required super.checkToolsUsecase,
+    required super.checkConfigFileUsecase,
+  })  : _decryptKeysUsecase = decryptKeysUsecase,
+        _getAndroidConfigUsecase = getAndroidConfigUsecase;
+
+  final DecryptKeysUsecase _decryptKeysUsecase;
+  final GetAndroidConfigUsecase _getAndroidConfigUsecase;
+
+  @override
+  FutureOr<void> buildApp(Context ctx) async {
+    emit(BuildAppBundleStateGetAndroidConfig());
+
+    // 1. Get Android config
+    final getAndroidConfigResult =
+        await _getAndroidConfigUsecase(ctx.configFile.path);
+    if (getAndroidConfigResult.isErr) {
+      return emit(
+        TaskStateError(message: getAndroidConfigResult.err.toString()),
+      );
+    }
+
+    // 2. Decrypt Android keys
+    emit(BuildAppBundleStateDecryptAndroidKeys());
+    final decryptAndroidkeysResult = await _decryptKeysUsecase(
+      DecryptKeysParameters(
+        encryptedFilePath: getAndroidConfigResult.ok!.encryptedKeyFile.path,
+        passphrase: ctx.environment[AppConstants.androidKeysSecretPassphrase]
+            .toString(),
+      ),
+    );
+    if (decryptAndroidkeysResult.isErr) {
+      return emit(
+        TaskStateError(message: decryptAndroidkeysResult.err.toString()),
+      );
+    }
+
+    // 3. Build Android App Bundle
+    emit(BuildAppBundleStateBuildAppBundle());
+    final buildAppBundleResult = await flutter
+        .buildAppBundle(getAndroidConfigResult.ok!.flutterArgs.split(' '));
+    if (buildAppBundleResult.isErr) {
+      return emit(
+        TaskStateError(message: decryptAndroidkeysResult.err.toString()),
+      );
+    }
+
+    emit(const TaskStateSucceed());
+  }
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_ipa/build_ipa_command.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_ipa/build_ipa_command.dart
new file mode 100644
index 00000000..814b6a2f
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_ipa/build_ipa_command.dart
@@ -0,0 +1,31 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'package:wyatt_continuous_deployment/src/core/commands/build_app/commands/build_ipa/build_ipa_task.dart';
+import 'package:wyatt_continuous_deployment/src/core/commands/task_command.dart';
+
+class BuildIpaCommand extends TaskCommand {
+  BuildIpaCommand() : super(task: BuildIpaTask());
+
+  @override
+  String get description => 'Build the iOS IPA';
+
+  @override
+  String get name => 'ipa';
+
+  @override
+  List<String> get aliases => ['i', 'ios'];
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_ipa/build_ipa_state.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_ipa/build_ipa_state.dart
new file mode 100644
index 00000000..9c36dd03
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_ipa/build_ipa_state.dart
@@ -0,0 +1,47 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+part of 'build_ipa_task.dart';
+
+class BuildIpaStateGetIosConfig extends TaskStateRunning {
+  @override
+  String get description => 'Get iOS config';
+}
+
+class BuildIpaStateDecryptIosKey extends TaskStateRunning {
+  @override
+  String get description => 'Decrypt ios keys';
+}
+
+class BuildIpaStateRetrieveIosKey extends TaskStateRunning {
+  @override
+  String get description => 'Retrieve ios keys';
+}
+
+class BuildIpaStateCreateKeychain extends TaskStateRunning {
+  @override
+  String get description => 'Create new keychain';
+}
+
+class BuildIpaStateRetrieveCertificates extends TaskStateRunning {
+  @override
+  String get description => 'Retrieve match certificates';
+}
+
+class BuildIpaStateBuildIpa extends TaskStateRunning {
+  @override
+  String get description => 'Build ipa';
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_ipa/build_ipa_task.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_ipa/build_ipa_task.dart
new file mode 100644
index 00000000..bf7f9402
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/build_app/commands/build_ipa/build_ipa_task.dart
@@ -0,0 +1,133 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'dart:async';
+
+import 'package:wyatt_continuous_deployment/src/core/commands/build_app/build_app_task.dart';
+import 'package:wyatt_continuous_deployment/src/core/constants/app_constants.dart';
+import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart';
+import 'package:wyatt_continuous_deployment/src/core/task/task_cubit.dart';
+import 'package:wyatt_continuous_deployment/src/domain/entities/context.dart';
+import 'package:wyatt_continuous_deployment/src/domain/entities/decrypt_keys_parameters.dart';
+import 'package:wyatt_continuous_deployment/src/domain/entities/fastlane.dart';
+import 'package:wyatt_continuous_deployment/src/domain/entities/fastlane/fastlane_create_keychain_parameters.dart';
+import 'package:wyatt_continuous_deployment/src/domain/entities/fastlane/fastlane_get_certificates_parameters.dart';
+import 'package:wyatt_continuous_deployment/src/domain/usecases/config/get_ios_config_usecase.dart';
+import 'package:wyatt_continuous_deployment/src/domain/usecases/decrypt_keys/decrypt_keys_usecase.dart';
+
+part 'build_ipa_state.dart';
+
+class BuildIpaTask extends BuildAppTask {
+  factory BuildIpaTask() => BuildIpaTask._(
+        flutter: getIt(),
+        fastlane: getIt(),
+        decryptKeysUsecase: getIt(),
+        getIosConfigUsecase: getIt(),
+        checkToolsUsecase: getIt(),
+        checkConfigFileUsecase: getIt(),
+      );
+
+  BuildIpaTask._({
+    required DecryptKeysUsecase decryptKeysUsecase,
+    required GetIosConfigUsecase getIosConfigUsecase,
+    required Fastlane fastlane,
+    required super.flutter,
+    required super.checkToolsUsecase,
+    required super.checkConfigFileUsecase,
+  })  : _decryptKeysUsecase = decryptKeysUsecase,
+        _getIosConfigUsecase = getIosConfigUsecase,
+        _fastlane = fastlane;
+
+  final DecryptKeysUsecase _decryptKeysUsecase;
+  final GetIosConfigUsecase _getIosConfigUsecase;
+  final Fastlane _fastlane;
+
+  @override
+  FutureOr<void> buildApp(Context ctx) async {
+    // 1. Get iOS Config
+    emit(BuildIpaStateGetIosConfig());
+    final getIosConfigResult = await _getIosConfigUsecase(ctx.configFile.path);
+    if (getIosConfigResult.isErr) {
+      return emit(
+        TaskStateError(message: getIosConfigResult.err.toString()),
+      );
+    }
+
+    // 2. Decrypt iOS keys
+    emit(BuildIpaStateDecryptIosKey());
+    final decryptIosKeyResult = await _decryptKeysUsecase(
+      DecryptKeysParameters(
+        encryptedFilePath: getIosConfigResult.ok!.encryptedKeyFile.path,
+        passphrase:
+            ctx.environment[AppConstants.iosKeysSecretPassphrase].toString(),
+      ),
+    );
+    if (decryptIosKeyResult.isErr) {
+      return emit(
+        TaskStateError(message: decryptIosKeyResult.err.toString()),
+      );
+    }
+
+    // 5. Create Keychain
+    emit(BuildIpaStateCreateKeychain());
+    final fastlaneCreateKeychainResult = await _fastlane.createKeychain(
+      FastlaneCreateKeychainParameters(
+        name: 'match',
+        password: 'flutter-cd-cli',
+        gitPassphrase: getIosConfigResult.ok!.gitPassphrase,
+      ),
+    );
+    if (fastlaneCreateKeychainResult.isErr) {
+      return emit(
+        TaskStateError(message: fastlaneCreateKeychainResult.err.toString()),
+      );
+    }
+
+    // 6. Retrieve certificates
+    emit(BuildIpaStateRetrieveCertificates());
+    final fastlaneGetCertificateResult = await _fastlane.getCertificates(
+      FastlaneGetCertificatesParameters(
+        appIdentifier: getIosConfigResult.ok!.developerAppIdentifier,
+        gitPrivateKey:
+            '${getIosConfigResult.ok!.iosFolderPath}/${getIosConfigResult.ok!.gitCertsPrivateKeyFileName}',
+        keychainName: 'match',
+        keychainPassword: 'flutter-cd-cli',
+        userName: getIosConfigResult.ok!.userName,
+        teamId: getIosConfigResult.ok!.teamId,
+        teamName: getIosConfigResult.ok!.teamName,
+        gitUrl: getIosConfigResult.ok!.gitUrl,
+        gitPassphrase: getIosConfigResult.ok!.gitPassphrase,
+      ),
+    );
+    if (fastlaneGetCertificateResult.isErr) {
+      return emit(
+        TaskStateError(message: fastlaneGetCertificateResult.err.toString()),
+      );
+    }
+
+    // 7. Build IPA
+    emit(BuildIpaStateBuildIpa());
+    final flutterBuildIpaResult =
+        await flutter.buildIpa(getIosConfigResult.ok!.flutterArgs.split(' '));
+    if (flutterBuildIpaResult.isErr) {
+      return emit(
+        TaskStateError(message: flutterBuildIpaResult.err.toString()),
+      );
+    }
+
+    emit(const TaskStateSucceed());
+  }
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/commands.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/commands.dart
new file mode 100644
index 00000000..4bbeb7a8
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/commands.dart
@@ -0,0 +1,20 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+export './build_and_deploy_app/build_and_deploy_app_command.dart';
+export './build_app/build_app_command.dart';
+export './deploy_app/deploy_app_command.dart';
+export './init_project/init_project_command.dart';
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_android/deploy_android_command.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_android/deploy_android_command.dart
new file mode 100644
index 00000000..6b2f97bd
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_android/deploy_android_command.dart
@@ -0,0 +1,31 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'package:wyatt_continuous_deployment/src/core/commands/deploy_app/commands/deploy_android/deploy_android_task.dart';
+import 'package:wyatt_continuous_deployment/src/core/commands/task_command.dart';
+
+class DeployAndroidCommand extends TaskCommand {
+  DeployAndroidCommand() : super(task: DeployAndroidTask());
+
+  @override
+  String get description => 'Deploy the android app on the play store';
+
+  @override
+  String get name => 'android';
+
+  @override
+  List<String> get aliases => ['a', 'playstore'];
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_android/deploy_android_state.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_android/deploy_android_state.dart
new file mode 100644
index 00000000..96f1a90e
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_android/deploy_android_state.dart
@@ -0,0 +1,27 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+part of 'deploy_android_task.dart';
+
+class DeployAndroidStateDeployToPlayStore extends TaskStateRunning {
+  @override
+  String get description => 'Upload appbundle to Play Store';
+}
+
+class DeployAndroidStateGetAndroidConfig extends TaskStateRunning {
+  @override
+  String get description => 'Get android options';
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_android/deploy_android_task.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_android/deploy_android_task.dart
new file mode 100644
index 00000000..9fd1b266
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_android/deploy_android_task.dart
@@ -0,0 +1,84 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'dart:async';
+
+import 'package:wyatt_continuous_deployment/src/core/constants/app_constants.dart';
+import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart';
+import 'package:wyatt_continuous_deployment/src/core/task/task_cubit.dart';
+import 'package:wyatt_continuous_deployment/src/domain/entities/context.dart';
+import 'package:wyatt_continuous_deployment/src/domain/entities/fastlane.dart';
+import 'package:wyatt_continuous_deployment/src/domain/entities/fastlane/fastlane_deploy_supply_parameters.dart';
+import 'package:wyatt_continuous_deployment/src/domain/usecases/config/get_android_config_usecase.dart';
+
+part 'deploy_android_state.dart';
+
+class DeployAndroidTask extends TaskCubit {
+  factory DeployAndroidTask() => DeployAndroidTask._(
+        getAndroidConfigUsecase: getIt(),
+        fastlane: getIt(),
+        checkToolsUsecase: getIt(),
+        checkConfigFileUsecase: getIt(),
+      );
+
+  DeployAndroidTask._({
+    required GetAndroidConfigUsecase getAndroidConfigUsecase,
+    required Fastlane fastlane,
+    required super.checkToolsUsecase,
+    required super.checkConfigFileUsecase,
+  })  : _getAndroidConfigUsecase = getAndroidConfigUsecase,
+        _fastlane = fastlane;
+
+  final GetAndroidConfigUsecase _getAndroidConfigUsecase;
+  final Fastlane _fastlane;
+
+  @override
+  Future<void> onRun(Context ctx) async {
+    // 1. Get Android Config
+    emit(DeployAndroidStateGetAndroidConfig());
+
+    final getAndroidConfigResult =
+        await _getAndroidConfigUsecase(ctx.configFile.path);
+    if (getAndroidConfigResult.isErr) {
+      return emit(
+        TaskStateError(
+          message: getAndroidConfigResult.err.toString(),
+        ),
+      );
+    }
+
+    // 2. Deploy to Play Store
+    emit(DeployAndroidStateDeployToPlayStore());
+    final deploySupplyResult = await _fastlane.deployGooglePlay(
+      FastlaneDeploySupplyParameters(
+        packageName: getAndroidConfigResult.ok!.packageName.toString(),
+        track: getAndroidConfigResult.ok!.track,
+        serviceAccountPath:
+            '${getAndroidConfigResult.ok!.androidFolderPath}/${getAndroidConfigResult.ok!.googleServiceAccountFileName}',
+        appBundlePath: AppConstants.androidBundleBuildPath,
+        releaseStatus: getAndroidConfigResult.ok!.releaseStatus,
+      ),
+    );
+
+    if (deploySupplyResult.isErr) {
+      return emit(
+        TaskStateError(
+          message: deploySupplyResult.err.toString(),
+        ),
+      );
+    }
+  }
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_command.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_command.dart
new file mode 100644
index 00000000..72be263c
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_command.dart
@@ -0,0 +1,31 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'package:wyatt_continuous_deployment/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_task.dart';
+import 'package:wyatt_continuous_deployment/src/core/commands/task_command.dart';
+
+class DeployIosCommand extends TaskCommand {
+  DeployIosCommand() : super(task: DeployIosTask());
+
+  @override
+  String get description => 'Deploy the ios app on the AppStore connect';
+
+  @override
+  String get name => 'ios';
+
+  @override
+  List<String> get aliases => ['i', 'appstore'];
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_state.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_state.dart
new file mode 100644
index 00000000..9bee3d42
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_state.dart
@@ -0,0 +1,42 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+part of 'deploy_ios_task.dart';
+
+class DeployIosStateGetIosConfig extends TaskStateRunning {
+  @override
+  String get description => 'Get iOS config';
+}
+
+class DeployIosStatedecryptIosKeys extends TaskStateRunning {
+  @override
+  String get description => 'Decrypt ios keys';
+}
+
+class DeployIosStateRetrieveIosKey extends TaskStateRunning {
+  @override
+  String get description => 'Retrieve ios keys';
+}
+
+class DeployIosStateCheckRequiredIosKey extends TaskStateRunning {
+  @override
+  String get description => 'Check if all required keys exist';
+}
+
+class DeployIosStateDeployToAppStoreConnect extends TaskStateRunning {
+  @override
+  String get description => 'Upload ipa to AppStore Connect';
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_task.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_task.dart
new file mode 100644
index 00000000..46ce7b67
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_task.dart
@@ -0,0 +1,118 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:wyatt_continuous_deployment/src/core/constants/app_constants.dart';
+import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart';
+import 'package:wyatt_continuous_deployment/src/core/task/task_cubit.dart';
+import 'package:wyatt_continuous_deployment/src/core/utils/logging.dart';
+import 'package:wyatt_continuous_deployment/src/domain/entities/config/appstore_connect_config.dart';
+import 'package:wyatt_continuous_deployment/src/domain/entities/context.dart';
+import 'package:wyatt_continuous_deployment/src/domain/entities/decrypt_keys_parameters.dart';
+import 'package:wyatt_continuous_deployment/src/domain/entities/fastlane.dart';
+import 'package:wyatt_continuous_deployment/src/domain/entities/fastlane/fastlane_deploy_pilot_parameters.dart';
+import 'package:wyatt_continuous_deployment/src/domain/usecases/config/get_ios_config_usecase.dart';
+import 'package:wyatt_continuous_deployment/src/domain/usecases/decrypt_keys/decrypt_keys_usecase.dart';
+
+part 'deploy_ios_state.dart';
+
+class DeployIosTask extends TaskCubit {
+  factory DeployIosTask() => DeployIosTask._(
+        getIosConfigUsecase: getIt(),
+        decryptKeysUsecase: getIt(),
+        fastlane: getIt(),
+        checkToolsUsecase: getIt(),
+        checkConfigFileUsecase: getIt(),
+      );
+
+  DeployIosTask._({
+    required GetIosConfigUsecase getIosConfigUsecase,
+    required DecryptKeysUsecase decryptKeysUsecase,
+    required Fastlane fastlane,
+    required super.checkToolsUsecase,
+    required super.checkConfigFileUsecase,
+  })  : _getIosConfigUsecase = getIosConfigUsecase,
+        _decryptKeysUsecase = decryptKeysUsecase,
+        _fastlane = fastlane;
+
+  final GetIosConfigUsecase _getIosConfigUsecase;
+  final DecryptKeysUsecase _decryptKeysUsecase;
+  final Fastlane _fastlane;
+
+  @override
+  Future<void> onRun(Context ctx) async {
+    // 1. Get iOS config
+    emit(DeployIosStateGetIosConfig());
+    final getIosConfigResult = await _getIosConfigUsecase(ctx.configFile.path);
+    if (getIosConfigResult.isErr) {
+      return emit(
+        TaskStateError(message: getIosConfigResult.err.toString()),
+      );
+    }
+
+    // 2. Decrypt iOS keys
+    emit(DeployIosStatedecryptIosKeys());
+    final decryptIosKeyResult = await _decryptKeysUsecase(
+      DecryptKeysParameters(
+        encryptedFilePath: getIosConfigResult.ok!.encryptedKeyFile.path,
+        passphrase:
+            ctx.environment[AppConstants.iosKeysSecretPassphrase].toString(),
+      ),
+    );
+    if (decryptIosKeyResult.isErr) {
+      return emit(TaskStateError(message: decryptIosKeyResult.err.toString()));
+    }
+
+    // 5. Deploy to AppStore Connect
+    emit(DeployIosStateDeployToAppStoreConnect());
+    final deployResult = await _fastlane.deployAppStore(
+      FastlaneDeployPilotParameters(
+        appStoreConnectConfig: AppStoreConnectConfig.fromConfig(
+          getIosConfigResult.ok!,
+        ),
+        developerAppId: getIosConfigResult.ok!.developerAppId,
+        developerAppIdentifier: getIosConfigResult.ok!.developerAppIdentifier,
+        ipaPath: _findFirstIpaFilePath('build/ios/ipa') ?? '',
+      ),
+    );
+    if (deployResult.isErr) {
+      return emit(TaskStateError(message: deployResult.err.toString()));
+    }
+  }
+
+  String? _findFirstIpaFilePath(String folderPath) {
+    final Directory directory = Directory(folderPath);
+
+    if (!directory.existsSync()) {
+      logger.detail('No IPA file found in $folderPath');
+      return null;
+    }
+
+    final List<FileSystemEntity> files = directory.listSync();
+
+    for (final FileSystemEntity file in files) {
+      if (file is File && file.path.endsWith('.ipa')) {
+        logger.detail('Found IPA file: ${file.path}');
+        return file.path;
+      }
+    }
+
+    logger.detail('No IPA file found in $folderPath');
+    return null;
+  }
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/deploy_app_command.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/deploy_app_command.dart
new file mode 100644
index 00000000..e30c05e8
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/deploy_app/deploy_app_command.dart
@@ -0,0 +1,35 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'package:args/command_runner.dart';
+import 'package:wyatt_continuous_deployment/src/core/commands/deploy_app/commands/deploy_android/deploy_android_command.dart';
+import 'package:wyatt_continuous_deployment/src/core/commands/deploy_app/commands/deploy_ios/deploy_ios_command.dart';
+
+class DeployAppCommand extends Command<int> {
+  DeployAppCommand() {
+    addSubcommand(DeployAndroidCommand());
+    addSubcommand(DeployIosCommand());
+  }
+
+  @override
+  String get description => 'Deploy the app';
+
+  @override
+  String get name => 'deploy';
+
+  @override
+  List<String> get aliases => ['d'];
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/init_project/init_project_command.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/init_project/init_project_command.dart
new file mode 100644
index 00000000..a7be2ab7
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/init_project/init_project_command.dart
@@ -0,0 +1,39 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'package:wyatt_continuous_deployment/src/core/commands/init_project/init_project_task.dart';
+import 'package:wyatt_continuous_deployment/src/core/commands/task_command.dart';
+import 'package:wyatt_continuous_deployment/src/core/enums/flag.dart';
+
+class InitProjectCommand extends TaskCommand {
+  InitProjectCommand() : super(task: InitProjectTask()) {
+    argParser.addFlag(
+      Flag.force.name,
+      abbr: Flag.force.abbr,
+      help: 'Force the initialization',
+      negatable: false,
+    );
+  }
+
+  @override
+  String get description => 'Initialize the project';
+
+  @override
+  String get name => 'init';
+
+  @override
+  List<String> get aliases => ['i', 'initialize'];
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/init_project/init_project_state.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/init_project/init_project_state.dart
new file mode 100644
index 00000000..f80d2464
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/init_project/init_project_state.dart
@@ -0,0 +1,28 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+part of 'init_project_task.dart';
+
+class InitProjectState extends TaskStateRunning {
+  const InitProjectState({required this.path, required this.force});
+
+  final String path;
+  final bool force;
+
+  @override
+  String get description =>
+      'Writing config file to `$path`${force ? ' (forced)' : ''}...';
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/init_project/init_project_task.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/init_project/init_project_task.dart
new file mode 100644
index 00000000..eb37bbc8
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/init_project/init_project_task.dart
@@ -0,0 +1,85 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'dart:async';
+
+import 'package:wyatt_continuous_deployment/src/core/enums/flag.dart';
+import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart';
+import 'package:wyatt_continuous_deployment/src/core/task/task_cubit.dart';
+import 'package:wyatt_continuous_deployment/src/domain/entities/config/init_config_parameters.dart';
+import 'package:wyatt_continuous_deployment/src/domain/entities/context.dart';
+import 'package:wyatt_continuous_deployment/src/domain/usecases/config/init_config_usecase.dart';
+
+part 'init_project_state.dart';
+
+class InitProjectTask extends TaskCubit {
+  factory InitProjectTask() => InitProjectTask._(
+        initConfigUsecase: getIt(),
+        checkToolsUsecase: getIt(),
+        checkConfigFileUsecase: getIt(),
+      );
+
+  InitProjectTask._({
+    required InitConfigUsecase initConfigUsecase,
+    required super.checkToolsUsecase,
+    required super.checkConfigFileUsecase,
+  }) : _initConfigUsecase = initConfigUsecase;
+
+  final InitConfigUsecase _initConfigUsecase;
+
+  @override
+  bool get checks => false; // No need to check config file (not created yet)
+
+  @override
+  Future<void> onRun(Context ctx) async {
+    // 1. Init config
+    emit(
+      InitProjectState(
+        path: ctx.configFile.path,
+        force: ctx.flags.contains(Flag.force),
+      ),
+    );
+    final initResult = await _initConfigUsecase(
+      InitConfigParameters(
+        path: ctx.configFile.path,
+        force: ctx.flags.contains(Flag.force),
+      ),
+    );
+
+    if (initResult.isErr) {
+      return emit(TaskStateError.fromRes(initResult));
+    }
+
+    // 2. Check tools
+    emit(TaskStateCheckTools());
+
+    final checkToolsResult = await checkToolsUsecase(null);
+    if (checkToolsResult.isErr) {
+      return emit(TaskStateError.fromRes(checkToolsResult));
+    }
+
+    // 3. Check config file
+    emit(TaskStateCheckConfigFile());
+
+    final checkConfigFileResult =
+        await checkConfigFileUsecase(ctx.configFile.path);
+    if (checkConfigFileResult.isErr) {
+      return emit(TaskStateError.fromRes(checkConfigFileResult));
+    }
+
+    return emit(const TaskStateSucceed(message: 'Project initialized'));
+  }
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/commands/task_command.dart b/packages/wyatt_continuous_deployment/lib/src/core/commands/task_command.dart
new file mode 100644
index 00000000..4504d9ae
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/commands/task_command.dart
@@ -0,0 +1,80 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:args/command_runner.dart';
+import 'package:meta/meta.dart';
+import 'package:wyatt_continuous_deployment/src/core/constants/app_constants.dart';
+import 'package:wyatt_continuous_deployment/src/core/enums/flag.dart';
+import 'package:wyatt_continuous_deployment/src/core/task/task_cubit.dart';
+import 'package:wyatt_continuous_deployment/src/domain/entities/context.dart';
+
+abstract class TaskCommand extends Command<int> {
+  TaskCommand({
+    required this.task,
+  });
+
+  final TaskCubit task;
+
+  /// [ArgResults] used for testing purposes only.
+  @visibleForTesting
+  ArgResults? testArgResults;
+
+  /// [ArgResults] for the current command.
+  ArgResults get results => testArgResults ?? argResults!;
+
+  /// Context object for the current command.
+  Context get context => Context(
+        currentDirectory: Directory.current,
+        configFile: () {
+          // Check if config flag is set.
+          final wyattPath = globalResults![AppConstants.configArg] as String?;
+
+          final wyattFile = File(wyattPath ?? AppConstants.configFileName);
+
+          return wyattFile;
+        }.call(),
+        flags: Flag.fromArgs(results: results, globalResults: globalResults!),
+        environment: () {
+          final env = <String, String>{}..addAll(Platform.environment);
+
+          // Load environment variables from the dot file.
+          final dotFile = File('.env');
+          if (dotFile.existsSync()) {
+            final dotFileContent = dotFile.readAsStringSync();
+            final lines = dotFileContent.split('\n');
+            for (final line in lines) {
+              final parts = line.split('=');
+              if (parts.length == 2) {
+                env[parts[0].trim()] = parts[1].trim();
+              }
+            }
+          }
+
+          return env;
+        }.call(),
+      );
+
+  @override
+  FutureOr<int>? run() async {
+    await task.run(context);
+    await task.awaitForFinished();
+    return task.status;
+  }
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/constants/app_constants.dart b/packages/wyatt_continuous_deployment/lib/src/core/constants/app_constants.dart
index 350c2708..bb26b700 100644
--- a/packages/wyatt_continuous_deployment/lib/src/core/constants/app_constants.dart
+++ b/packages/wyatt_continuous_deployment/lib/src/core/constants/app_constants.dart
@@ -15,6 +15,9 @@
 // along with this program. If not, see <https://www.gnu.org/licenses/>.
 
 abstract class AppConstants {
+  static const packageName = 'studio_cli';
+  static const executableName = 'wyatt';
+
   static const configFileName = 'wyatt.yaml';
   static const requiredTools = ['fastlane', 'flutter', 'unzip', 'gpg'];
 
@@ -27,4 +30,8 @@ abstract class AppConstants {
   static const iosFolder = 'ios';
   static const iosKeysSecretPassphrase = 'IOS_KEYS_SECRET_PASSPHRASE';
   static const iosConfigKey = 'ios';
+
+  static const configArg = 'config';
+  static const verboseArg = 'verbose';
+  static const versionArg = 'version';
 }
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/core.dart b/packages/wyatt_continuous_deployment/lib/src/core/core.dart
new file mode 100644
index 00000000..4bee53ec
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/core.dart
@@ -0,0 +1,17 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+export './commands/commands.dart';
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/enums/flag.dart b/packages/wyatt_continuous_deployment/lib/src/core/enums/flag.dart
new file mode 100644
index 00000000..2aa9025c
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/enums/flag.dart
@@ -0,0 +1,50 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'package:args/args.dart';
+import 'package:wyatt_continuous_deployment/src/core/utils/logging.dart';
+
+enum Flag {
+  verbose('v'),
+  force('f');
+
+  const Flag(this.abbr);
+
+  final String abbr;
+
+  static List<Flag> fromArgs({
+    required ArgResults results,
+    required ArgResults globalResults,
+  }) {
+    final flags = <Flag>[];
+
+    for (final flag in Flag.values) {
+      if (results.options.contains(flag.name)) {
+        logger.detail('Flag: ${flag.name} was parsed in results');
+        if (results[flag.name] as bool) {
+          flags.add(flag);
+        }
+      } else if (globalResults.options.contains(flag.name)) {
+        logger.detail('Flag: ${flag.name} was parsed in globalResults');
+        if (globalResults[flag.name] as bool) {
+          flags.add(flag);
+        }
+      }
+    }
+
+    return flags;
+  }
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/extensions/object_extension.dart b/packages/wyatt_continuous_deployment/lib/src/core/extensions/object_extension.dart
new file mode 100644
index 00000000..252fadcb
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/extensions/object_extension.dart
@@ -0,0 +1,23 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+// 
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+// 
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+// 
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'package:wyatt_architecture/wyatt_architecture.dart';
+
+extension ObjectExtension on Object? {
+  AppException toException() => this is AppException
+      ? this! as AppException
+      : ClientException(this?.toString());
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/task/task_cubit.dart b/packages/wyatt_continuous_deployment/lib/src/core/task/task_cubit.dart
new file mode 100644
index 00000000..51a36466
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/task/task_cubit.dart
@@ -0,0 +1,90 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'dart:async';
+
+import 'package:bloc/bloc.dart';
+import 'package:wyatt_architecture/wyatt_architecture.dart';
+import 'package:wyatt_continuous_deployment/src/domain/entities/context.dart';
+import 'package:wyatt_continuous_deployment/src/domain/usecases/usecases.dart';
+import 'package:wyatt_type_utils/wyatt_type_utils.dart';
+
+part 'task_state.dart';
+
+abstract class TaskCubit extends Cubit<TaskState> {
+  TaskCubit({
+    required CheckToolsUsecase checkToolsUsecase,
+    required CheckConfigFileUsecase checkConfigFileUsecase,
+  })  : _checkToolsUsecase = checkToolsUsecase,
+        _checkConfigFileUsecase = checkConfigFileUsecase,
+        super(TaskStateInitial());
+
+  final CheckToolsUsecase _checkToolsUsecase;
+  final CheckConfigFileUsecase _checkConfigFileUsecase;
+
+  int get status => switch (state) {
+        TaskStateFinished(:final exitCode) => exitCode,
+        _ => 0,
+      };
+
+  /// Whether to check the tools before running the task
+  bool get checks => true;
+
+  CheckToolsUsecase get checkToolsUsecase => _checkToolsUsecase;
+  CheckConfigFileUsecase get checkConfigFileUsecase => _checkConfigFileUsecase;
+
+  Future<void> awaitForFinished() async {
+    if (state is TaskStateFinished) {
+      return;
+    }
+
+    await for (final state in stream) {
+      if (state is TaskStateFinished) {
+        return;
+      }
+    }
+  }
+
+  Future<void> run(Context ctx) async {
+    if (checks) {
+      emit(TaskStateCheckTools());
+
+      final checkToolsResult = await _checkToolsUsecase(null);
+      if (checkToolsResult.isErr) {
+        return emit(TaskStateError.fromRes(checkToolsResult));
+      }
+
+      emit(TaskStateCheckConfigFile());
+
+      final checkConfigFileResult =
+          await _checkConfigFileUsecase(ctx.configFile.path);
+      if (checkConfigFileResult.isErr) {
+        return emit(TaskStateError.fromRes(checkConfigFileResult));
+      }
+    }
+
+    // Run the task
+    await onRun(ctx);
+
+    if (state is! TaskStateError && state is! TaskStateSucceed) {
+      // If no error was emitted, and no success state was emitted,
+      // then emit a success state to indicate that the task has finished
+      return emit(const TaskStateSucceed());
+    }
+  }
+
+  Future<void> onRun(Context ctx);
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/task/task_state.dart b/packages/wyatt_continuous_deployment/lib/src/core/task/task_state.dart
new file mode 100644
index 00000000..aea643bb
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/task/task_state.dart
@@ -0,0 +1,85 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+part of 'task_cubit.dart';
+
+abstract class TaskState {
+  const TaskState();
+
+  String get description;
+}
+
+class TaskStateInitial extends TaskState {
+  @override
+  String get description => '';
+}
+
+class TaskStateCheckTools extends TaskState {
+  @override
+  String get description => 'Check that needed tools are installed';
+}
+
+class TaskStateCheckConfigFile extends TaskState {
+  @override
+  String get description => 'Check that config file exists';
+}
+
+class TaskStateRunning extends TaskState {
+  const TaskStateRunning();
+
+  @override
+  String get description => '';
+}
+
+abstract class TaskStateFinished extends TaskState {
+  const TaskStateFinished({
+    this.exitCode = 0,
+  });
+
+  final int exitCode;
+}
+
+class TaskStateError extends TaskStateFinished {
+  const TaskStateError({required String? message, super.exitCode = 1})
+      : _message = message ?? 'Unknown error';
+
+  factory TaskStateError.unknown() => const TaskStateError(message: null);
+
+  factory TaskStateError.fromException(AppException e, {int? exitCode}) =>
+      TaskStateError(message: e.message, exitCode: exitCode ?? 1);
+
+  factory TaskStateError.fromRes(
+    Result<dynamic, AppException> res, {
+    int? exitCode,
+  }) =>
+      TaskStateError(message: res.err?.message, exitCode: exitCode ?? 1);
+
+  final String _message;
+
+  @override
+  String get description =>
+      'An error occured${_message.isNotEmpty ? ': $_message' : ''}';
+}
+
+class TaskStateSucceed extends TaskStateFinished {
+  const TaskStateSucceed({this.message, super.exitCode});
+
+  final String? message;
+
+  @override
+  String get description =>
+      message ?? 'The task ended with success (exit code: $exitCode)';
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/utils/logging.dart b/packages/wyatt_continuous_deployment/lib/src/core/utils/logging.dart
index b3050073..234ac19e 100644
--- a/packages/wyatt_continuous_deployment/lib/src/core/utils/logging.dart
+++ b/packages/wyatt_continuous_deployment/lib/src/core/utils/logging.dart
@@ -14,6 +14,7 @@
 // You should have received a copy of the GNU General Public License
 // along with this program. If not, see <https://www.gnu.org/licenses/>.
 
-import 'package:wyatt_continuous_deployment/wyatt_continuous_deployment.dart';
+import 'package:mason_logger/mason_logger.dart';
+import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart';
 
-final logger = WyattContinuousDeployment.logger;
+final logger = getIt<Logger>();
diff --git a/packages/wyatt_continuous_deployment/lib/src/core/utils/task_observer.dart b/packages/wyatt_continuous_deployment/lib/src/core/utils/task_observer.dart
new file mode 100644
index 00000000..7b076367
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/core/utils/task_observer.dart
@@ -0,0 +1,36 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'package:bloc/bloc.dart';
+import 'package:wyatt_continuous_deployment/src/core/task/task_cubit.dart';
+import 'package:wyatt_continuous_deployment/src/core/utils/logging.dart';
+
+class TaskObserver extends BlocObserver {
+  @override
+  void onChange(BlocBase<dynamic> bloc, Change<dynamic> change) {
+    super.onChange(bloc, change);
+
+    return switch (change.nextState) {
+      TaskStateError(:final description) when description.isNotEmpty =>
+        logger.err(description),
+      TaskStateSucceed(:final description) when description.isNotEmpty =>
+        logger.success(description),
+      final TaskState taskState when taskState.description.isNotEmpty =>
+        logger.detail(taskState.description),
+      _ => logger.detail(change.nextState.toString()),
+    };
+  }
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/domain/entities/context.dart b/packages/wyatt_continuous_deployment/lib/src/domain/entities/context.dart
new file mode 100644
index 00000000..526a93da
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/domain/entities/context.dart
@@ -0,0 +1,33 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'dart:io';
+
+import 'package:wyatt_continuous_deployment/src/core/enums/flag.dart';
+
+class Context {
+  const Context({
+    required this.currentDirectory,
+    required this.configFile,
+    required this.flags,
+    this.environment = const {},
+  });
+
+  final Directory currentDirectory;
+  final File configFile;
+  final List<Flag> flags;
+  final Map<String, String> environment;
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/domain/entities/flutter.dart b/packages/wyatt_continuous_deployment/lib/src/domain/entities/flutter.dart
index 9d912310..d2c59a18 100644
--- a/packages/wyatt_continuous_deployment/lib/src/domain/entities/flutter.dart
+++ b/packages/wyatt_continuous_deployment/lib/src/domain/entities/flutter.dart
@@ -49,9 +49,9 @@ class Flutter {
   final FlutterBuildIpaUsecase _flutterBuildIpaUsecase;
   final FlutterBuildAppBundleUsecase _flutterBuildAppBundleUsecase;
 
-  FutureOrResult<void> clean() => _flutterCleanUsecase();
-  FutureOrResult<void> getDependencies() => _flutterPubGetUsecase();
-  FutureOrResult<void> createXcArchive() => _flutterBuildXcarchiveUsecase();
+  FutureOrResult<void> clean() => _flutterCleanUsecase(null);
+  FutureOrResult<void> getDependencies() => _flutterPubGetUsecase(null);
+  FutureOrResult<void> createXcArchive() => _flutterBuildXcarchiveUsecase(null);
   FutureOrResult<void> buildIpa(List<String> args) =>
       _flutterBuildIpaUsecase(args);
   FutureOrResult<void> buildAppBundle(List<String> args) =>
diff --git a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/config/get_android_config_usecase.dart b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/config/get_android_config_usecase.dart
index 688824c1..28ab950c 100644
--- a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/config/get_android_config_usecase.dart
+++ b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/config/get_android_config_usecase.dart
@@ -21,6 +21,7 @@ import 'package:checked_yaml/checked_yaml.dart';
 import 'package:wyatt_architecture/wyatt_architecture.dart';
 import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart';
 import 'package:wyatt_continuous_deployment/src/domain/entities/config/android_config.dart';
+import 'package:wyatt_continuous_deployment/src/domain/usecases/usecase.dart';
 import 'package:yaml/yaml.dart';
 
 @usecase
@@ -28,8 +29,17 @@ class GetAndroidConfigUsecase extends AsyncUseCase<String, AndroidConfig> {
   const GetAndroidConfigUsecase() : super();
 
   @override
-  FutureOr<Res<AndroidConfig>> execute(String params) => unsafe(() {
-        final file = File(params);
+  FutureOr<void> onStart(String? params) {
+    if (params == null) {
+      throw const ClientException(
+        'GetAndroidConfigUsecase: You must provide path',
+      );
+    }
+  }
+
+  @override
+  FutureOrResult<AndroidConfig> execute(String? params) => unsafeAsync(() {
+        final file = File(params!);
         final content = file.readAsStringSync();
 
         return checkedYamlDecode(
diff --git a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/config/get_ios_config_usecase.dart b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/config/get_ios_config_usecase.dart
index c7d0766b..baed66b9 100644
--- a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/config/get_ios_config_usecase.dart
+++ b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/config/get_ios_config_usecase.dart
@@ -21,6 +21,7 @@ import 'package:checked_yaml/checked_yaml.dart';
 import 'package:wyatt_architecture/wyatt_architecture.dart';
 import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart';
 import 'package:wyatt_continuous_deployment/src/domain/entities/config/ios_config.dart';
+import 'package:wyatt_continuous_deployment/src/domain/usecases/usecase.dart';
 import 'package:yaml/yaml.dart';
 
 @usecase
@@ -28,8 +29,15 @@ class GetIosConfigUsecase extends AsyncUseCase<String, IosConfig> {
   const GetIosConfigUsecase() : super();
 
   @override
-  FutureOr<Res<IosConfig>> execute(String params) => unsafe(() {
-        final file = File(params);
+  FutureOr<void> onStart(String? params) {
+    if (params == null) {
+      throw const ClientException('GetIosConfigUsecase: You must provide path');
+    }
+  }
+
+  @override
+  FutureOrResult<IosConfig> execute(String? params) => unsafeAsync(() {
+        final file = File(params!);
         final content = file.readAsStringSync();
 
         return checkedYamlDecode(
diff --git a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/config/init_config_usecase.dart b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/config/init_config_usecase.dart
index 87ba935e..d4a751fd 100644
--- a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/config/init_config_usecase.dart
+++ b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/config/init_config_usecase.dart
@@ -24,6 +24,7 @@ import 'package:wyatt_continuous_deployment/src/core/utils/logging.dart';
 import 'package:wyatt_continuous_deployment/src/domain/entities/config/android_config.dart';
 import 'package:wyatt_continuous_deployment/src/domain/entities/config/init_config_parameters.dart';
 import 'package:wyatt_continuous_deployment/src/domain/entities/config/ios_config.dart';
+import 'package:wyatt_continuous_deployment/src/domain/usecases/usecase.dart';
 import 'package:yaml_edit/yaml_edit.dart';
 
 @usecase
@@ -31,8 +32,18 @@ class InitConfigUsecase extends AsyncUseCase<InitConfigParameters, void> {
   const InitConfigUsecase() : super();
 
   @override
-  FutureOr<Res<void>> execute(InitConfigParameters params) => unsafe(() async {
-        final file = File(params.path);
+  FutureOr<void> onStart(InitConfigParameters? params) {
+    if (params == null) {
+      throw const ClientException(
+        'InitConfigUsecase: You must provide parameter',
+      );
+    }
+  }
+
+  @override
+  FutureOrResult<void> execute(InitConfigParameters? params) =>
+      unsafeAsync(() async {
+        final file = File(params!.path);
         if (!file.existsSync()) {
           logger.detail('Creating config file at `${params.path}`');
           file.createSync();
diff --git a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/decrypt_keys/decrypt_keys_usecase.dart b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/decrypt_keys/decrypt_keys_usecase.dart
index c96d667e..96b73b61 100644
--- a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/decrypt_keys/decrypt_keys_usecase.dart
+++ b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/decrypt_keys/decrypt_keys_usecase.dart
@@ -21,6 +21,7 @@ import 'package:wyatt_architecture/wyatt_architecture.dart';
 import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart';
 import 'package:wyatt_continuous_deployment/src/core/utils/logging.dart';
 import 'package:wyatt_continuous_deployment/src/domain/entities/decrypt_keys_parameters.dart';
+import 'package:wyatt_continuous_deployment/src/domain/usecases/usecase.dart';
 
 @usecase
 class DecryptKeysUsecase extends AsyncUseCase<DecryptKeysParameters, void> {
@@ -29,21 +30,28 @@ class DecryptKeysUsecase extends AsyncUseCase<DecryptKeysParameters, void> {
   @override
   FutureOr<void> onStart(DecryptKeysParameters? params) {
     if (params == null) {
-      throw const ClientException('DecryptKeysParameters is null');
+      throw const ClientException(
+        'DecryptKeysUsecase: DecryptKeysParameters is null',
+      );
     }
     if (params.encryptedFilePath.isEmpty) {
-      throw const ClientException('Encrypted file path is empty');
+      throw const ClientException(
+        'DecryptKeysUsecase: Encrypted file path is empty',
+      );
     }
     if (!params.encryptedFilePath.endsWith('.gpg')) {
-      throw const ClientException('Enncrypted file is not a .gpg file');
+      throw const ClientException(
+        'DecryptKeysUsecase: Enncrypted file is not a .gpg file',
+      );
     }
   }
 
   @override
-  FutureOr<Res<void>> execute(DecryptKeysParameters params) => unsafe(() async {
+  FutureOrResult<void> execute(DecryptKeysParameters? params) =>
+      unsafeAsync(() async {
         // Define gpg command
         final gpgFilePath =
-            params.encryptedFilePath.replaceAll(RegExp(r'\.gpg$'), '');
+            params!.encryptedFilePath.replaceAll(RegExp(r'\.gpg$'), '');
         final outputGpgFilePath =
             gpgFilePath.endsWith('.zip') ? gpgFilePath : '$gpgFilePath.zip';
 
diff --git a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/find_last_ipa_file_usecase.dart b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/find_last_ipa_file_usecase.dart
index 8e19fdc4..0634e0ef 100644
--- a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/find_last_ipa_file_usecase.dart
+++ b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/find_last_ipa_file_usecase.dart
@@ -14,6 +14,7 @@
 // You should have received a copy of the GNU General Public License
 // along with this program. If not, see <https://www.gnu.org/licenses/>.
 
+import 'dart:async';
 import 'dart:io';
 
 import 'package:wyatt_architecture/wyatt_architecture.dart';
@@ -26,8 +27,17 @@ class FindLastIpaFileUsecase extends AsyncUseCase<String, String> {
   const FindLastIpaFileUsecase() : super();
 
   @override
-  FutureOrResult<String> execute(String params) {
-    final Directory directory = Directory(params);
+  FutureOr<void> onStart(String? params) {
+    if (params == null) {
+      throw const ClientException(
+        'FindLastIpaFileUsecase: You must provide a path',
+      );
+    }
+  }
+
+  @override
+  FutureOrResult<String> execute(String? params) {
+    final Directory directory = Directory(params!);
 
     if (!directory.existsSync()) {
       logger.detail('No IPA file found in $params');
diff --git a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/process_usecase.dart b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/process_usecase.dart
index 2632e8bb..99c8fc89 100644
--- a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/process_usecase.dart
+++ b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/process_usecase.dart
@@ -19,6 +19,8 @@ import 'dart:io';
 
 import 'package:wyatt_architecture/wyatt_architecture.dart';
 import 'package:wyatt_continuous_deployment/src/core/utils/logging.dart';
+import 'package:wyatt_continuous_deployment/src/domain/usecases/usecase.dart';
+import 'package:wyatt_type_utils/wyatt_type_utils.dart';
 
 abstract class ProcessUsecase<T> extends AsyncUseCase<T, void> {
   const ProcessUsecase();
@@ -36,9 +38,16 @@ abstract class ProcessUsecase<T> extends AsyncUseCase<T, void> {
   String get onErrorMessage;
 
   @override
-  FutureOr<Res<void>> execute(T params) async {
+  FutureOr<void> onStart(T? params) {
+    if (params == null) {
+      throw const ClientException('ProcessUsecase: You must provide a param');
+    }
+  }
+
+  @override
+  FutureOrResult<void> execute(T? params) async {
     final progress = logger.progress(onStartedMessage);
-    final processResult = await run(params);
+    final processResult = await run(params as T);
     if (processResult != null) {
       if (processResult.exitCode != 0) {
         progress.fail(onErrorMessage);
@@ -49,7 +58,7 @@ abstract class ProcessUsecase<T> extends AsyncUseCase<T, void> {
     }
     progress.complete(onCompletedMessage);
     logger.detail(processResult?.stdout.toString().trim());
-    return Res.ok(null);
+    return const Ok(null);
   }
 
   /// Run the process with the given parameters.
@@ -72,7 +81,7 @@ abstract class ProcessNoParamsUsecase extends NoParamsAsyncUseCase<void> {
   String get onErrorMessage;
 
   @override
-  FutureOr<Res<void>> execute() async {
+  FutureOrResult<void> execute(void params) async {
     final progress = logger.progress(onStartedMessage);
     final processResult = await run();
     if (processResult != null) {
@@ -85,7 +94,7 @@ abstract class ProcessNoParamsUsecase extends NoParamsAsyncUseCase<void> {
     }
     progress.complete(onCompletedMessage);
     logger.detail(processResult?.stdout.toString().trim());
-    return Res.ok(null);
+    return const Ok(null);
   }
 
   /// Run the process with no parameters.
diff --git a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/requirements/check_config_file_usecase.dart b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/requirements/check_config_file_usecase.dart
index c6d11b54..ad317dfe 100644
--- a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/requirements/check_config_file_usecase.dart
+++ b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/requirements/check_config_file_usecase.dart
@@ -14,18 +14,29 @@
 // You should have received a copy of the GNU General Public License
 // along with this program. If not, see <https://www.gnu.org/licenses/>.
 
+import 'dart:async';
 import 'dart:io';
 
 import 'package:wyatt_architecture/wyatt_architecture.dart';
 import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart';
+import 'package:wyatt_continuous_deployment/src/domain/usecases/usecase.dart';
 
 @usecase
 class CheckConfigFileUsecase extends AsyncUseCase<String, void> {
   const CheckConfigFileUsecase() : super();
 
   @override
-  FutureOrResult<void> execute(String params) => unsafe(() {
-        final configFile = File(params);
+  FutureOr<void> onStart(String? params) {
+    if (params == null) {
+      throw const ClientException(
+        'CheckConfigFileUsecase: You must provide path',
+      );
+    }
+  }
+
+  @override
+  FutureOrResult<void> execute(String? params) => unsafeAsync(() {
+        final configFile = File(params!);
         if (!configFile.existsSync()) {
           throw ClientException(
             'CheckConfigFileUsecase: Cannot find $params',
diff --git a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/requirements/check_tools_usecase.dart b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/requirements/check_tools_usecase.dart
index b826b2f6..d4ba6dca 100644
--- a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/requirements/check_tools_usecase.dart
+++ b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/requirements/check_tools_usecase.dart
@@ -20,13 +20,14 @@ import 'dart:io';
 import 'package:wyatt_architecture/wyatt_architecture.dart';
 import 'package:wyatt_continuous_deployment/src/core/constants/app_constants.dart';
 import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart';
+import 'package:wyatt_continuous_deployment/src/domain/usecases/usecase.dart';
 
 @usecase
 class CheckToolsUsecase extends NoParamsAsyncUseCase<void> {
   const CheckToolsUsecase() : super();
 
   @override
-  FutureOr<Res<void>> execute() async => unsafe(() async {
+  FutureOrResult<void> execute(void params) async => unsafeAsync(() async {
         final missingTools = <String>[];
         for (final tool in AppConstants.requiredTools) {
           final isInstalled = await checkToolInstalled(tool);
diff --git a/packages/wyatt_continuous_deployment/lib/src/domain/usecases/usecase.dart b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/usecase.dart
new file mode 100644
index 00000000..a458817f
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/domain/usecases/usecase.dart
@@ -0,0 +1,34 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'dart:async';
+
+import 'package:wyatt_architecture/wyatt_architecture.dart';
+import 'package:wyatt_continuous_deployment/src/core/extensions/object_extension.dart';
+import 'package:wyatt_type_utils/wyatt_type_utils.dart';
+
+typedef Res<T> = Result<T, AppException>;
+
+/// A wrapper around [Result] to make it easier to use with AppException.
+FutureOrResult<T> unsafeAsync<T>(FutureOr<T> Function() fn) =>
+    Result.tryCatchAsync(
+      () async => fn.call(),
+      (error) => error.toException(),
+    );
+
+abstract class NoParamsAsyncUseCase<T> extends AsyncUseCase<void, T> {
+  const NoParamsAsyncUseCase();
+}
diff --git a/packages/wyatt_continuous_deployment/lib/src/version.dart b/packages/wyatt_continuous_deployment/lib/src/version.dart
new file mode 100644
index 00000000..af0834a9
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/version.dart
@@ -0,0 +1 @@
+const packageVersion = '0.0.1';
diff --git a/packages/wyatt_continuous_deployment/lib/src/wyatt_continuous_deployment.dart b/packages/wyatt_continuous_deployment/lib/src/wyatt_continuous_deployment.dart
index dc040b78..8ac51f30 100644
--- a/packages/wyatt_continuous_deployment/lib/src/wyatt_continuous_deployment.dart
+++ b/packages/wyatt_continuous_deployment/lib/src/wyatt_continuous_deployment.dart
@@ -14,23 +14,5 @@
 // You should have received a copy of the GNU General Public License
 // along with this program. If not, see <https://www.gnu.org/licenses/>.
 
-import 'package:get_it/get_it.dart';
-import 'package:mason_logger/mason_logger.dart';
-import 'package:wyatt_continuous_deployment/src/core/injection/injectable.dart';
-
-export 'domain/domain.dart';
-
-abstract class WyattContinuousDeployment {
-  /// The logger used by the package
-  static Logger logger = Logger();
-
-  /// The service locator used by the package
-  static GetIt getIt = GetIt.instance;
-
-  /// Configure the dependencies
-  /// This method should be called before WyattContinuousDeployment.getIt
-  /// to ensure that all dependencies are registered
-  static Future<void> configureDependencies() async {
-    await Injectable.configureDependencies();
-  }
-}
+// TODO(mleon): export all layers
+export '';
diff --git a/packages/wyatt_continuous_deployment/lib/src/wyatt_continuous_deployment_command_runner.dart b/packages/wyatt_continuous_deployment/lib/src/wyatt_continuous_deployment_command_runner.dart
new file mode 100644
index 00000000..51d6eb9b
--- /dev/null
+++ b/packages/wyatt_continuous_deployment/lib/src/wyatt_continuous_deployment_command_runner.dart
@@ -0,0 +1,110 @@
+// Copyright (C) 2024 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:args/command_runner.dart';
+import 'package:cli_completion/cli_completion.dart';
+import 'package:mason_logger/mason_logger.dart';
+import 'package:wyatt_continuous_deployment/src/core/commands/commands.dart';
+import 'package:wyatt_continuous_deployment/src/core/constants/app_constants.dart';
+import 'package:wyatt_continuous_deployment/src/core/enums/flag.dart';
+import 'package:wyatt_continuous_deployment/src/core/utils/logging.dart';
+import 'package:wyatt_continuous_deployment/src/version.dart';
+
+class WyattContinuousDeploymentCommandRunner
+    extends CompletionCommandRunner<int> {
+  WyattContinuousDeploymentCommandRunner()
+      : super(
+          AppConstants.executableName,
+          '🍺 Where all your dreams come to life',
+        ) {
+    argParser.addFlags();
+    addCommand(InitProjectCommand());
+    addCommand(BuildAppCommand());
+    addCommand(DeployAppCommand());
+    addCommand(BuildAndDeployAppCommand());
+  }
+
+  @override
+  Future<int> run(Iterable<String> args) async {
+    try {
+      final results = parse(args);
+
+      logger.level = results['verbose'] == true ? Level.verbose : Level.info;
+
+      return await runCommand(results) ?? ExitCode.success.code;
+    } on FormatException catch (e) {
+      logger
+        ..err(e.message)
+        ..info('')
+        ..info(usage);
+      return ExitCode.usage.code;
+    } on UsageException catch (e) {
+      logger
+        ..err(e.message)
+        ..info('')
+        ..info(e.usage);
+      return ExitCode.usage.code;
+    } on ProcessException catch (error) {
+      logger.err(error.message);
+      return ExitCode.unavailable.code;
+    } catch (error) {
+      logger.err('$error');
+      return ExitCode.software.code;
+    }
+  }
+
+  @override
+  Future<int?> runCommand(ArgResults topLevelResults) async {
+    if (topLevelResults.command?.name == 'completion') {
+      await super.runCommand(topLevelResults);
+      return ExitCode.success.code;
+    }
+
+    int? exitCode = ExitCode.unavailable.code;
+    if (topLevelResults['version'] == true) {
+      logger.info(packageVersion);
+      exitCode = ExitCode.success.code;
+    } else {
+      exitCode = await super.runCommand(topLevelResults);
+    }
+    return exitCode;
+  }
+}
+
+extension on ArgParser {
+  void addFlags() {
+    addFlag(
+      AppConstants.versionArg,
+      negatable: false,
+      help: 'Print the current version.',
+    );
+    addFlag(
+      Flag.verbose.name,
+      abbr: Flag.verbose.abbr,
+      negatable: false,
+      help: 'Print verbose output.',
+    );
+    addOption(
+      AppConstants.configArg,
+      abbr: 'c',
+      help: 'Path to the wyatt.yaml file.',
+      valueHelp: 'path',
+    );
+  }
+}
diff --git a/packages/wyatt_continuous_deployment/lib/wyatt_continuous_deployment.dart b/packages/wyatt_continuous_deployment/lib/wyatt_continuous_deployment.dart
index fc963fd9..40bef808 100644
--- a/packages/wyatt_continuous_deployment/lib/wyatt_continuous_deployment.dart
+++ b/packages/wyatt_continuous_deployment/lib/wyatt_continuous_deployment.dart
@@ -17,4 +17,5 @@
 /// Wyatt CD
 library wyatt_continuous_deployment;
 
-export 'src/wyatt_continuous_deployment.dart';
+export './src/core/core.dart';
+export './src/domain/domain.dart';
diff --git a/packages/wyatt_continuous_deployment/pubspec.yaml b/packages/wyatt_continuous_deployment/pubspec.yaml
index 5398fb8b..189d9466 100644
--- a/packages/wyatt_continuous_deployment/pubspec.yaml
+++ b/packages/wyatt_continuous_deployment/pubspec.yaml
@@ -9,11 +9,15 @@ environment:
   sdk: ">=3.0.0 <4.0.0"
 
 dependencies:
+  args: ^2.4.2
+  bloc: ^8.1.3
   checked_yaml: ^2.0.3
+  cli_completion: ^0.5.0
   get_it: ^7.6.7
   injectable: ^2.3.2
   json_annotation: ^4.8.1
   mason_logger: ^0.2.12
+  meta: ^1.12.0
   wyatt_architecture:
     hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
     version: ^0.2.0+1