You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ag...@apache.org on 2022/06/06 19:28:38 UTC

[ignite-3] branch main updated: IGNITE-16971 Command line interface with REPL support (MVP)

This is an automated email from the ASF dual-hosted git repository.

agura pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 56059d2d5 IGNITE-16971 Command line interface with REPL support (MVP)
56059d2d5 is described below

commit 56059d2d57d46f74079a858f541b30cb50fb1f93
Author: Mikhail Pochatkin <mp...@unison.team>
AuthorDate: Mon Jun 6 22:23:09 2022 +0300

    IGNITE-16971 Command line interface with REPL support (MVP)
    
    Co-authored-by: Aleksandr Pakhomov <ap...@unison.team>
    Signed-off-by: Andrey Gura <ag...@apache.org>
---
 assembly/assembly.xml                              |   5 +
 .../ignite/example/rebalance/RebalanceExample.java |   2 +-
 .../ignite/example/sql/jdbc/SqlJdbcExample.java    |   2 +-
 .../storage/VolatilePageMemoryStorageExample.java  |   2 +-
 .../ignite/example/table/KeyValueViewExample.java  |   2 +-
 .../example/table/KeyValueViewPojoExample.java     |   2 +-
 .../ignite/example/table/RecordViewExample.java    |   2 +-
 .../example/table/RecordViewPojoExample.java       |   2 +-
 .../ignite/example/tx/TransactionsExample.java     |   2 +-
 modules/cli/DEVNOTES.md                            |  25 +
 modules/cli/README.md                              |  37 --
 modules/cli/ignite.bat                             |  16 +
 modules/cli/pom.xml                                | 312 +++++++++---
 .../org/apache/ignite/cli/IntegrationTestBase.java | 344 +++++++++++++
 .../ignite/cli/call/CallIntegrationTestBase.java}  |  10 +-
 .../configuration/ItShowConfigurationCallTest.java | 151 ++++++
 .../ItUpdateConfigurationCallTest.java             |  78 +++
 .../commands/CliCommandTestIntegrationBase.java    | 133 +++++
 .../configuration/ItConfigCommandTest.java         |  93 ++++
 .../cli/commands/connect/ItConnectCommandTest.java | 112 +++++
 .../ignite/cli/commands/sql/ItSqlCommandTest.java  |  85 ++++
 .../commands/status/ItStatusReplCommandTest.java}  |  44 +-
 .../commands/topology/ItTopologyCommandTest.java   |  50 ++
 .../commands/version/ItVersionCommandTest.java}    |  33 +-
 .../AbstractCliIntegrationTest.java                |   6 +-
 .../cli/{ => deprecated}/ItClusterCommandTest.java |   5 +-
 .../cli/{ => deprecated}/ItConfigCommandTest.java  |  66 ++-
 .../ignite/cli/{ => deprecated}/NoOpHandler.java   |   2 +-
 .../ignite/rest/ItGeneratedRestClientTest.java     | 236 +++++++++
 .../java/org/apache/ignite/cli/CliVersionInfo.java |   1 +
 .../java/org/apache/ignite/cli/ErrorHandler.java   |  66 ---
 .../org/apache/ignite/cli/HelpFactoryImpl.java     | 174 -------
 .../java/org/apache/ignite/cli/IgniteCliApp.java   |  80 ---
 .../org/apache/ignite/cli/InteractiveWrapper.java  | 116 -----
 .../src/main/java/org/apache/ignite/cli/Main.java  | 169 +++++++
 .../org/apache/ignite/cli/VersionProvider.java     |   7 +
 .../cli/builtins/config/ConfigurationClient.java   | 168 -------
 .../cliconfig/CliConfigCall.java}                  |  32 +-
 .../cliconfig/CliConfigGetCall.java}               |  33 +-
 .../cliconfig/CliConfigSetCall.java}               |  36 +-
 .../cli/call/cliconfig/CliConfigSetCallInput.java} |  22 +-
 .../call/configuration/ClusterConfigShowCall.java  |  58 +++
 .../configuration/ClusterConfigShowCallInput.java  |  87 ++++
 .../configuration/ClusterConfigUpdateCall.java     |  67 +++
 .../ClusterConfigUpdateCallInput.java              |  91 ++++
 .../cli/call/configuration/NodeConfigShowCall.java |  58 +++
 .../configuration/NodeConfigShowCallInput.java     |  89 ++++
 .../call/configuration/NodeConfigUpdateCall.java   |  67 +++
 .../configuration/NodeConfigUpdateCallInput.java   |  91 ++++
 .../ignite/cli/call/connect/ConnectCall.java       |  86 ++++
 .../connect/ConnectCallInput.java}                 |  49 +-
 .../connect/DisconnectCall.java}                   |  41 +-
 .../org/apache/ignite/cli/call/status/Status.java  | 117 +++++
 .../apache/ignite/cli/call/status/StatusCall.java  |  99 ++++
 .../ignite/cli/call/status/StatusReplCall.java     |  86 ++++
 .../CommandSpec.java => commands/BaseCommand.java} |  19 +-
 .../ignite/cli/commands/TopLevelCliCommand.java    |  60 +++
 .../cli/commands/TopLevelCliReplCommand.java       |  53 ++
 .../cliconfig/CliCommand.java}                     |  19 +-
 .../cliconfig/CliConfigGetSubCommand.java}         |  42 +-
 .../commands/cliconfig/CliConfigSetSubCommand.java |  49 ++
 .../commands/cliconfig/CliConfigSubCommand.java    |  52 ++
 .../configuration/cluster/ClusterCommand.java}     |  24 +-
 .../cluster/ClusterConfigReplSubCommand.java}      |  15 +-
 .../cluster/ClusterConfigShowReplSubCommand.java   | 100 ++++
 .../cluster/ClusterConfigShowSubCommand.java       |  72 +++
 .../cluster/ClusterConfigSubCommand.java}          |  15 +-
 .../cluster/ClusterConfigUpdateReplSubCommand.java |  79 +++
 .../cluster/ClusterConfigUpdateSubCommand.java     |  72 +++
 .../configuration/cluster/ClusterReplCommand.java} |  18 +-
 .../configuration/node/NodeCommand.java}           |  19 +-
 .../node/NodeConfigReplSubCommand.java}            |  15 +-
 .../node/NodeConfigShowReplSubCommand.java         | 100 ++++
 .../node/NodeConfigShowSubCommand.java             |  72 +++
 .../configuration/node/NodeConfigSubCommand.java}  |  19 +-
 .../node/NodeConfigUpdateReplSubCommand.java       |  79 +++
 .../node/NodeConfigUpdateSubCommand.java           |  70 +++
 .../configuration/node/NodeReplCommand.java}       |  18 +-
 .../cli/commands/connect/ConnectCommand.java       |  63 +++
 .../connect/DisconnectCommand.java}                |  40 +-
 .../cli/commands/decorators/ConfigDecorator.java   |  42 ++
 .../cli/commands/decorators/DefaultDecorator.java} |  21 +-
 .../decorators/JsonDecorator.java}                 |  41 +-
 .../decorators/SqlQueryResultDecorator.java}       |  25 +-
 .../decorators/StatusDecorator.java}               |  40 +-
 .../commands/decorators/StatusReplDecorator.java   |  40 ++
 .../decorators/TableDecorator.java}                |  34 +-
 .../decorators/core/Decorator.java}                |  27 +-
 .../decorators/core/TerminalOutput.java}           |  14 +-
 .../apache/ignite/cli/commands/sql/SqlCommand.java | 125 +++++
 .../ignite/cli/commands/sql/SqlCompleter.java      |  72 +++
 .../ignite/cli/commands/sql/SqlMetaData.java       |  96 ++++
 .../sql/SqlReplTopLevelCliCommand.java}            |  19 +-
 .../ignite/cli/commands/status/StatusCommand.java  |  68 +++
 .../cli/commands/status/StatusReplCommand.java     |  65 +++
 .../cli/commands/topology/TopologyCommand.java     |  53 ++
 .../version/VersionCommand.java}                   |  31 +-
 .../java/org/apache/ignite/cli/config/Config.java  | 123 +++++
 .../ConfigFactory.java}                            |  16 +-
 .../CallExecutionPipelineProvider.java}            |  28 +-
 .../{spec/CommandSpec.java => core/call/Call.java} |  16 +-
 .../cli/core/call/CallExecutionPipeline.java       | 183 +++++++
 .../package-info.java => core/call/CallInput.java} |   9 +-
 .../call/CallOutput.java}                          |  43 +-
 .../call/CallOutputStatus.java}                    |   9 +-
 .../ignite/cli/core/call/DefaultCallOutput.java    | 187 +++++++
 .../call/EmptyCallInput.java}                      |   8 +-
 .../ignite/cli/core/call/StatusCallInput.java}     |  22 +-
 .../ignite/cli/core/call/StringCallInput.java}     |  23 +-
 .../exception/CommandExecutionException.java}      |  42 +-
 .../core/exception/ConnectCommandException.java}   |  29 +-
 .../cli/core/exception/ExceptionHandler.java       |  58 +++
 .../cli/core/exception/ExceptionHandlers.java      |  79 +++
 .../exception/ExceptionWriter.java}                |  31 +-
 .../cli/core/exception/WrappedException.java}      |  18 +-
 .../exception/handler/ApiExceptionHandler.java}    |  19 +-
 .../handler/CommandExecutionExceptionHandler.java  |  41 ++
 .../handler/ConnectCommandExceptionHandler.java}   |  19 +-
 .../handler/ConnectExceptionHandler.java}          |  19 +-
 .../handler/DefaultExceptionHandlers.java          |  41 ++
 .../handler/EndOfFileExceptionHandler.java         |  55 +++
 .../handler/IgniteCliExceptionHandler.java}        |  19 +-
 .../handler/IgniteClientExceptionHandler.java}     |  23 +-
 .../handler/PicocliExecutionExceptionHandler.java} |  19 +-
 .../exception/handler/ReplExceptionHandlers.java}  |  26 +-
 .../exception/handler/SqlExceptionHandler.java     |  67 +++
 .../handler/TimeoutExceptionHandler.java}          |  23 +-
 .../handler/UnknownCommandExceptionHandler.java}   |  21 +-
 .../handler/UserInterruptExceptionHandler.java}    |  19 +-
 .../java/org/apache/ignite/cli/core/repl/Repl.java | 150 ++++++
 .../apache/ignite/cli/core/repl/ReplBuilder.java   | 139 ++++++
 .../repl/Session.java}                             |  54 +-
 .../cli/core/repl/SessionDefaultValueProvider.java |  57 +++
 .../repl/config/ClientConnectorConfig.java}        |  14 +-
 .../repl/config/RootConfig.java}                   |  12 +-
 .../repl/executor/RegistryCommandExecutor.java     |  68 +++
 .../cli/core/repl/executor/ReplExecutor.java       | 145 ++++++
 .../repl/executor/ReplExecutorProvider.java}       |  34 +-
 .../cli/core/repl/executor/SqlQueryCall.java       |  54 ++
 .../cli/core/repl/expander/NoopExpander.java}      |  20 +-
 .../repl/prompt/PromptProvider.java}               |  12 +-
 .../repl/prompt/ReplPromptProvider.java}           |  32 +-
 .../repl/terminal/TerminalCustomizer.java}         |  17 +-
 .../repl/terminal}/TerminalFactory.java            |   2 +-
 .../cli/{ => deprecated}/CliPathsConfigLoader.java |   9 +-
 .../cli/{ => deprecated}/CommandFactory.java       |   2 +-
 .../cli/{ => deprecated}/IgniteCliException.java   |   4 +-
 .../ignite/cli/{ => deprecated}/IgnitePaths.java   |   7 +-
 .../apache/ignite/cli/{ => deprecated}/Table.java  |   2 +-
 .../builtins/SystemPathResolver.java               |   8 +-
 .../builtins/cluster/ClusterApiClient.java         |   4 +-
 .../builtins/cluster/InitClusterRequest.java       |   2 +-
 .../builtins/config/HttpClientFactory.java         |   2 +-
 .../builtins/config/package-info.java              |   2 +-
 .../builtins/init/InitIgniteCommand.java           |  14 +-
 .../builtins/init/package-info.java                |   2 +-
 .../builtins/module/MavenArtifactResolver.java     |   8 +-
 .../builtins/module/MavenCoordinates.java          |   5 +-
 .../builtins/module/ModuleManager.java             |   6 +-
 .../builtins/module/ModuleRegistry.java            |   6 +-
 .../builtins/module/ResolveResult.java             |   2 +-
 .../builtins/module/StandardModuleDefinition.java  |   2 +-
 .../builtins/module/package-info.java              |   2 +-
 .../builtins/node/NodeManager.java                 |   8 +-
 .../builtins/node/package-info.java                |   2 +-
 .../{ => deprecated}/builtins/package-info.java    |   2 +-
 .../ignite/cli/{ => deprecated}/package-info.java  |   2 +-
 .../spec/BootstrapIgniteCommandSpec.java}          |  11 +-
 .../{ => deprecated}/spec/ClusterCommandSpec.java  |   9 +-
 .../spec/ClusterReplCommandSpec.java}              |  36 +-
 .../cli/{ => deprecated}/spec/NodeCommandSpec.java |  23 +-
 .../{ => deprecated}/spec/NodeEndpointOptions.java |   4 +-
 .../cli/{ => deprecated}/spec/package-info.java    |   2 +-
 .../cli/{ => deprecated}/ui/ProgressBar.java       |   2 +-
 .../ignite/cli/{ => deprecated}/ui/Spinner.java    |   2 +-
 .../apache/ignite/cli/spec/ConfigCommandSpec.java  | 117 -----
 .../org/apache/ignite/cli/spec/IgniteCliSpec.java  | 142 ------
 .../apache/ignite/cli/spec/ModuleCommandSpec.java  | 231 ---------
 .../org/apache/ignite/cli/spec/SpecAdapter.java    |  64 ---
 .../apache/ignite/cli/sql/MetadataSupplier.java}   |  20 +-
 .../package-info.java => sql/SchemaProvider.java}  |  14 +-
 .../java/org/apache/ignite/cli/sql/SqlManager.java |  73 +++
 .../org/apache/ignite/cli/sql/SqlQueryResult.java  |  71 +++
 .../java/org/apache/ignite/cli/sql/SqlSchema.java  |  88 ++++
 .../org/apache/ignite/cli/sql/SqlSchemaLoader.java |  71 +++
 .../apache/ignite/cli/sql/SqlSchemaProvider.java   |  55 +++
 .../org/apache/ignite/cli/sql/table/Table.java     | 115 +++++
 .../org/apache/ignite/cli/sql/table/TableRow.java  |  71 +++
 modules/cli/src/main/resources/application.yml     |  21 +
 .../resources/cli.java.util.logging.properties     |  14 +-
 .../cli/builtins/module/ModuleMangerTest.java      | 162 ------
 .../ignite/cli/builtins/module/package-info.java   |  22 -
 .../ignite/cli/commands/CliCommandTestBase.java}   |  52 +-
 .../cliconfig/CliConfigGetSubCommandTest.java      |  81 +++
 .../cliconfig/CliConfigSetSubCommandTest.java      |  92 ++++
 .../cliconfig/CliConfigSubCommandTest.java         |  47 ++
 .../ignite/cli/commands/cliconfig/ConfigTest.java} |  30 +-
 .../cli/commands/cliconfig/TestConfigFactory.java} |  33 +-
 .../configuration/ShowConfigSubCommandTest.java    |  48 ++
 .../sql/SchemaProviderMock.java}                   |  21 +-
 .../ignite/cli/commands/sql/SqlCompleterTest.java  |  63 +++
 .../cli/{ => deprecated}/AbstractCliTest.java      |   2 +-
 .../{ => deprecated}/IgniteCliInterfaceTest.java   | 544 +++++++++------------
 .../builtins/init/InitIgniteCommandTest.java       |  12 +-
 .../builtins/init/package-info.java                |   2 +-
 .../ignite/cli/{ => deprecated}/package-info.java  |   2 +-
 .../cli/{ => deprecated}/ui/ProgressBarTest.java   |   4 +-
 .../cli/{ => deprecated}/ui/SpinnerTest.java       |   2 +-
 .../cli/{ => deprecated}/ui/package-info.java      |   2 +-
 .../ignite/cli/sql/SqlSchemaProviderTest.java      |  59 +++
 .../apache/ignite/cli/sql/table/TableTest.java}    |  41 +-
 .../apache/ignite/internal/jdbc/JdbcStatement.java |  30 +-
 parent/pom.xml                                     | 117 ++++-
 213 files changed, 8092 insertions(+), 2726 deletions(-)

diff --git a/assembly/assembly.xml b/assembly/assembly.xml
index e10c872e0..0eb01c0bf 100644
--- a/assembly/assembly.xml
+++ b/assembly/assembly.xml
@@ -47,6 +47,11 @@
             <outputDirectory/>
         </file>
 
+        <file>
+            <source>modules/cli/target/ignite_completion.sh</source>
+            <outputDirectory/>
+        </file>
+
         <file>
             <source>modules/cli/target/ignite.exe</source>
             <outputDirectory/>
diff --git a/examples/src/main/java/org/apache/ignite/example/rebalance/RebalanceExample.java b/examples/src/main/java/org/apache/ignite/example/rebalance/RebalanceExample.java
index e463cf0e2..4a2db3991 100644
--- a/examples/src/main/java/org/apache/ignite/example/rebalance/RebalanceExample.java
+++ b/examples/src/main/java/org/apache/ignite/example/rebalance/RebalanceExample.java
@@ -41,7 +41,7 @@ import org.apache.ignite.table.Tuple;
  *     <li>Import the examples project into you IDE.</li>
  *     <li>
  *         Download and prepare artifacts for running an Ignite node using the CLI tool (if not done yet):<br>
- *         {@code ignite init}
+ *         {@code ignite bootstrap}
  *     </li>
  *     <li>
  *         Start <b>two</b> nodes using the CLI tool:<br>
diff --git a/examples/src/main/java/org/apache/ignite/example/sql/jdbc/SqlJdbcExample.java b/examples/src/main/java/org/apache/ignite/example/sql/jdbc/SqlJdbcExample.java
index f873b879c..ed6e4f622 100644
--- a/examples/src/main/java/org/apache/ignite/example/sql/jdbc/SqlJdbcExample.java
+++ b/examples/src/main/java/org/apache/ignite/example/sql/jdbc/SqlJdbcExample.java
@@ -31,7 +31,7 @@ import java.sql.Statement;
  *     <li>Import the examples project into you IDE.</li>
  *     <li>
  *         Download and prepare artifacts for running an Ignite node using the CLI tool (if not done yet):<br>
- *         {@code ignite init}
+ *         {@code ignite bootstrap}
  *     </li>
  *     <li>
  *         Start an Ignite node using the CLI tool:<br>
diff --git a/examples/src/main/java/org/apache/ignite/example/storage/VolatilePageMemoryStorageExample.java b/examples/src/main/java/org/apache/ignite/example/storage/VolatilePageMemoryStorageExample.java
index cebd65ca5..f8ee293bd 100644
--- a/examples/src/main/java/org/apache/ignite/example/storage/VolatilePageMemoryStorageExample.java
+++ b/examples/src/main/java/org/apache/ignite/example/storage/VolatilePageMemoryStorageExample.java
@@ -31,7 +31,7 @@ import java.sql.Statement;
  *     <li>Import the examples project into you IDE.</li>
  *     <li>
  *         Download and prepare artifacts for running an Ignite node using the CLI tool (if not done yet):<br>
- *         {@code ignite init}
+ *         {@code ignite bootstrap}
  *     </li>
  *     <li>
  *         Start an Ignite node using the CLI tool:<br>
diff --git a/examples/src/main/java/org/apache/ignite/example/table/KeyValueViewExample.java b/examples/src/main/java/org/apache/ignite/example/table/KeyValueViewExample.java
index a51e0a945..508f37457 100644
--- a/examples/src/main/java/org/apache/ignite/example/table/KeyValueViewExample.java
+++ b/examples/src/main/java/org/apache/ignite/example/table/KeyValueViewExample.java
@@ -32,7 +32,7 @@ import org.apache.ignite.table.Tuple;
  *     <li>Import the examples project into you IDE.</li>
  *     <li>
  *         Download and prepare artifacts for running an Ignite node using the CLI tool (if not done yet):<br>
- *         {@code ignite init}
+ *         {@code ignite bootstrap}
  *     </li>
  *     <li>
  *         Start an Ignite node using the CLI tool:<br>
diff --git a/examples/src/main/java/org/apache/ignite/example/table/KeyValueViewPojoExample.java b/examples/src/main/java/org/apache/ignite/example/table/KeyValueViewPojoExample.java
index 03fdc1f28..497218535 100644
--- a/examples/src/main/java/org/apache/ignite/example/table/KeyValueViewPojoExample.java
+++ b/examples/src/main/java/org/apache/ignite/example/table/KeyValueViewPojoExample.java
@@ -31,7 +31,7 @@ import org.apache.ignite.table.KeyValueView;
  *     <li>Import the examples project into you IDE.</li>
  *     <li>
  *         Download and prepare artifacts for running an Ignite node using the CLI tool (if not done yet):<br>
- *         {@code ignite init}
+ *         {@code ignite bootstrap}
  *     </li>
  *     <li>
  *         Start an Ignite node using the CLI tool:<br>
diff --git a/examples/src/main/java/org/apache/ignite/example/table/RecordViewExample.java b/examples/src/main/java/org/apache/ignite/example/table/RecordViewExample.java
index 7d58366ee..eb1926dd6 100644
--- a/examples/src/main/java/org/apache/ignite/example/table/RecordViewExample.java
+++ b/examples/src/main/java/org/apache/ignite/example/table/RecordViewExample.java
@@ -32,7 +32,7 @@ import org.apache.ignite.table.Tuple;
  *     <li>Import the examples project into you IDE.</li>
  *     <li>
  *         Download and prepare artifacts for running an Ignite node using the CLI tool (if not done yet):<br>
- *         {@code ignite init}
+ *         {@code ignite bootstrap}
  *     </li>
  *     <li>
  *         Start an Ignite node using the CLI tool:<br>
diff --git a/examples/src/main/java/org/apache/ignite/example/table/RecordViewPojoExample.java b/examples/src/main/java/org/apache/ignite/example/table/RecordViewPojoExample.java
index c41a10a2f..df43264e4 100644
--- a/examples/src/main/java/org/apache/ignite/example/table/RecordViewPojoExample.java
+++ b/examples/src/main/java/org/apache/ignite/example/table/RecordViewPojoExample.java
@@ -31,7 +31,7 @@ import org.apache.ignite.table.RecordView;
  *     <li>Import the examples project into you IDE.</li>
  *     <li>
  *         Download and prepare artifacts for running an Ignite node using the CLI tool (if not done yet):<br>
- *         {@code ignite init}
+ *         {@code ignite bootstrap}
  *     </li>
  *     <li>
  *         Start an Ignite node using the CLI tool:<br>
diff --git a/examples/src/main/java/org/apache/ignite/example/tx/TransactionsExample.java b/examples/src/main/java/org/apache/ignite/example/tx/TransactionsExample.java
index c6018dd4a..f032b2c04 100644
--- a/examples/src/main/java/org/apache/ignite/example/tx/TransactionsExample.java
+++ b/examples/src/main/java/org/apache/ignite/example/tx/TransactionsExample.java
@@ -33,7 +33,7 @@ import org.apache.ignite.tx.IgniteTransactions;
  *     <li>Import the examples project into you IDE.</li>
  *     <li>
  *         Download and prepare artifacts for running an Ignite node using the CLI tool (if not done yet):<br>
- *         {@code ignite init}
+ *         {@code ignite bootstrap}
  *     </li>
  *     <li>
  *         Start an Ignite node using the CLI tool:<br>
diff --git a/modules/cli/DEVNOTES.md b/modules/cli/DEVNOTES.md
new file mode 100644
index 000000000..d28f5dc0d
--- /dev/null
+++ b/modules/cli/DEVNOTES.md
@@ -0,0 +1,25 @@
+# Ignite CLI DEVNOTES
+
+## How to build module and add bash/zsh autocompletion to your shell
+
+Build the ignite-3 and cli modules:
+```bash
+mvn clean install -DskipTests=true
+```
+
+Cd to the build directory:
+```bash
+cd modules/cli/target
+```
+
+Install autocompletion script to your shell:
+```bash 
+source target/ignite_completion.sh 
+```
+
+Add `ignite` alias:
+```bash
+alias ignite='./ignite'
+```
+
+For more info, see [Autocomplete for Java Command Line Applications](https://picocli.info/autocomplete.html).
diff --git a/modules/cli/README.md b/modules/cli/README.md
deleted file mode 100644
index f5f1e8637..000000000
--- a/modules/cli/README.md
+++ /dev/null
@@ -1,37 +0,0 @@
-# Ignite CLI module
-
-Ignite CLI tool is a single entry point for any management operations.
-
-## Build
-
-    mvn package -f ../../pom.xml
-## Run
-For Windows:
-
-    target/ignite.exe
-For Linux/MacOS:
-
-    ./target/ignite
-## Examples
-Download and prepare artifacts for running an Ignite node:
-
-    ignite init
-Node start:
-
-    ignite start consistent-id
-Node stop:
-
-    ignite stop consistent-id
-
-Cluster initialization:
-
-    ignite cluster init --cluster-name=cluster-name --node-endpoint=localhost:10300 --meta-storage-node=consistent-id --cmg-node=consistent-id
-
-Get current node configuration:
-
-    ignite config get --node-endpoint=localhost:10300
-Show help:
-
-    ignite --help
-    ignite init --help
-    ...
diff --git a/modules/cli/ignite.bat b/modules/cli/ignite.bat
new file mode 100644
index 000000000..0833e9149
--- /dev/null
+++ b/modules/cli/ignite.bat
@@ -0,0 +1,16 @@
+rem Licensed to the Apache Software Foundation (ASF) under one or more
+rem contributor license agreements.  See the NOTICE file distributed with
+rem this work for additional information regarding copyright ownership.
+rem The ASF licenses this file to You under the Apache License, Version 2.0
+rem (the "License"); you may not use this file except in compliance with
+rem the License.  You may obtain a copy of the License at
+rem
+rem      http://www.apache.org/licenses/LICENSE-2.0
+rem
+rem Unless required by applicable law or agreed to in writing, software
+rem distributed under the License is distributed on an "AS IS" BASIS,
+rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+rem See the License for the specific language governing permissions and
+rem limitations under the License.
+
+start java -jar target/ignite.jar
diff --git a/modules/cli/pom.xml b/modules/cli/pom.xml
index 218ccff05..07d258972 100644
--- a/modules/cli/pom.xml
+++ b/modules/cli/pom.xml
@@ -33,6 +33,21 @@
     <version>3.0.0-SNAPSHOT</version>
 
     <dependencies>
+        <dependency>
+            <groupId>com.jakewharton.fliptables</groupId>
+            <artifactId>fliptables</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.jline</groupId>
+            <artifactId>jline</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.jline</groupId>
+            <artifactId>jline-console</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>org.apache.ignite</groupId>
             <artifactId>ignite-cli-common</artifactId>
@@ -43,30 +58,30 @@
             <artifactId>ignite-core</artifactId>
         </dependency>
 
-        <!-- 3rd party dependencies -->
         <dependency>
-            <groupId>com.fasterxml.jackson.core</groupId>
-            <artifactId>jackson-databind</artifactId>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-api</artifactId>
         </dependency>
 
         <dependency>
-            <groupId>org.apache.ivy</groupId>
-            <artifactId>ivy</artifactId>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-client</artifactId>
         </dependency>
 
+        <!-- 3rd party dependencies -->
         <dependency>
-            <groupId>info.picocli</groupId>
-            <artifactId>picocli-shell-jline3</artifactId>
+            <groupId>io.micronaut</groupId>
+            <artifactId>micronaut-inject</artifactId>
         </dependency>
 
         <dependency>
             <groupId>io.micronaut</groupId>
-            <artifactId>micronaut-inject-java</artifactId>
+            <artifactId>micronaut-validation</artifactId>
         </dependency>
 
         <dependency>
-            <groupId>com.typesafe</groupId>
-            <artifactId>config</artifactId>
+            <groupId>info.picocli</groupId>
+            <artifactId>picocli-shell-jline3</artifactId>
         </dependency>
 
         <dependency>
@@ -80,8 +95,28 @@
         </dependency>
 
         <dependency>
-            <groupId>org.jetbrains</groupId>
-            <artifactId>annotations</artifactId>
+            <groupId>io.micronaut</groupId>
+            <artifactId>micronaut-runtime</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.micronaut.picocli</groupId>
+            <artifactId>micronaut-picocli</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.ivy</groupId>
+            <artifactId>ivy</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.typesafe</groupId>
+            <artifactId>config</artifactId>
         </dependency>
 
         <dependency>
@@ -89,7 +124,42 @@
             <artifactId>slf4j-jdk14</artifactId>
         </dependency>
 
-        <!-- Test dependencies -->
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.code.findbugs</groupId>
+            <artifactId>jsr305</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.threeten</groupId>
+            <artifactId>threetenbp</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>logging-interceptor</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.gsonfire</groupId>
+            <artifactId>gson-fire</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-annotations</artifactId>
+        </dependency>
+
+        <!--        Test dependencies-->
         <dependency>
             <groupId>org.hamcrest</groupId>
             <artifactId>hamcrest</artifactId>
@@ -97,26 +167,26 @@
         </dependency>
 
         <dependency>
-            <groupId>org.junit.jupiter</groupId>
-            <artifactId>junit-jupiter-api</artifactId>
+            <groupId>com.github.npathai</groupId>
+            <artifactId>hamcrest-optional</artifactId>
             <scope>test</scope>
         </dependency>
 
         <dependency>
-            <groupId>org.junit.jupiter</groupId>
-            <artifactId>junit-jupiter-engine</artifactId>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-junit-jupiter</artifactId>
             <scope>test</scope>
         </dependency>
 
         <dependency>
-            <groupId>org.mockito</groupId>
-            <artifactId>mockito-junit-jupiter</artifactId>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-runner</artifactId>
             <scope>test</scope>
         </dependency>
 
         <dependency>
-            <groupId>com.github.npathai</groupId>
-            <artifactId>hamcrest-optional</artifactId>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-schema</artifactId>
             <scope>test</scope>
         </dependency>
 
@@ -127,14 +197,26 @@
         </dependency>
 
         <dependency>
-            <groupId>org.apache.ignite</groupId>
-            <artifactId>ignite-api</artifactId>
+            <groupId>io.micronaut.test</groupId>
+            <artifactId>micronaut-test-core</artifactId>
             <scope>test</scope>
         </dependency>
 
         <dependency>
-            <groupId>org.apache.ignite</groupId>
-            <artifactId>ignite-runner</artifactId>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
             <scope>test</scope>
         </dependency>
     </dependencies>
@@ -173,73 +255,152 @@
         </resources>
 
         <plugins>
+            <!--Disable javadoc validation for module with generated REST client.-->
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-shade-plugin</artifactId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.openapitools</groupId>
+                <artifactId>openapi-generator-maven-plugin</artifactId>
                 <executions>
                     <execution>
-                        <phase>package</phase>
                         <goals>
-                            <goal>shade</goal>
+                            <goal>generate</goal>
                         </goals>
                         <configuration>
-                            <finalName>ignite</finalName>
-                            <transformers>
-                                <transformer
-                                  implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
-                                    <manifestEntries>
-                                        <Main-Class>org.apache.ignite.cli.IgniteCliApp</Main-Class>
-                                    </manifestEntries>
-                                </transformer>
-                                <transformer
-                                  implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
-                            </transformers>
-                            <filters>
-                                <filter>
-                                    <artifact>*:*</artifact>
-                                    <excludes>
-                                        <exclude>module-info.class</exclude>
-                                    </excludes>
-                                </filter>
-                            </filters>
+                            <inputSpec>${project.basedir}/../rest/openapi/openapi.yaml</inputSpec>
+                            <generatorName>java</generatorName>
+                            <apiPackage>org.apache.ignite.rest.client.api</apiPackage>
+                            <invokerPackage>org.apache.ignite.rest.client.invoker</invokerPackage>
+                            <modelPackage>org.apache.ignite.rest.client.model</modelPackage>
+                            <generateModelTests>false</generateModelTests>
+                            <generateApiTests>false</generateApiTests>
+                            <languageSpecificPrimitives>true</languageSpecificPrimitives>
+                            <configOptions>
+                                <openApiNullable>false</openApiNullable>
+                                <supportStreaming>false</supportStreaming>
+                            </configOptions>
+                            <library>okhttp-gson</library>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <!--
+                Plugin that adds integration test sources and resources from integrationTest directory.
+            -->
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>add-test-source</id>
+                        <phase>validate</phase>
+                        <goals>
+                            <goal>add-test-source</goal>
+                        </goals>
+                        <configuration>
+                            <sources>
+                                <source>src/integrationTest/java</source>
+                            </sources>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>add-test-resource</id>
+                        <phase>validate</phase>
+                        <goals>
+                            <goal>add-test-resource</goal>
+                        </goals>
+                        <configuration>
+                            <resources>
+                                <resource>
+                                    <directory>src/integrationTest/resources</directory>
+                                </resource>
+                            </resources>
                         </configuration>
                     </execution>
                 </executions>
             </plugin>
 
+            <plugin>
+                <groupId>io.micronaut.build</groupId>
+                <artifactId>micronaut-maven-plugin</artifactId>
+                <extensions>true</extensions>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-resources-plugin</artifactId>
+            </plugin>
+
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
+                    <source>11</source>
+                    <target>11</target>
                     <!-- Uncomment to enable incremental compilation -->
                     <!-- <useIncrementalCompilation>false</useIncrementalCompilation> -->
-                    <annotationProcessorPaths>
+                    <annotationProcessorPaths combine.children="append">
+                        <path>
+                            <groupId>info.picocli</groupId>
+                            <artifactId>picocli-codegen</artifactId>
+                            <version>${picocli.version}</version>
+                        </path>
                         <path>
                             <groupId>io.micronaut</groupId>
                             <artifactId>micronaut-inject-java</artifactId>
                             <version>${micronaut.version}</version>
                         </path>
                         <path>
-                            <groupId>info.picocli</groupId>
-                            <artifactId>picocli-codegen</artifactId>
-                            <version>${picocli.version}</version>
+                            <groupId>io.micronaut</groupId>
+                            <artifactId>micronaut-validation</artifactId>
+                            <version>${micronaut.version}</version>
                         </path>
                     </annotationProcessorPaths>
+                    <compilerArgs>
+                        <arg>-Amicronaut.processing.group=org.apache.ignite.cli</arg>
+                        <arg>-Amicronaut.processing.artifactId=${project.artifactId}</arg>
+                    </compilerArgs>
                 </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
                 <executions>
                     <execution>
-                        <id>test-compile</id>
+                        <id>default-shade</id>
+                        <phase>package</phase>
                         <goals>
-                            <goal>testCompile</goal>
+                            <goal>shade</goal>
                         </goals>
                         <configuration>
-                            <annotationProcessorPaths>
-                                <path>
-                                    <groupId>io.micronaut</groupId>
-                                    <artifactId>micronaut-inject-java</artifactId>
-                                    <version>${micronaut.version}</version>
-                                </path>
-                            </annotationProcessorPaths>
+                            <finalName>ignite</finalName>
+                            <createDependencyReducedPom>false</createDependencyReducedPom>
+                            <transformers>
+                                <transformer
+                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+                                    <manifestEntries>
+                                        <Main-Class>org.apache.ignite.cli.Main</Main-Class>
+                                    </manifestEntries>
+                                </transformer>
+                                <transformer
+                                        implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
+                            </transformers>
+                            <filters>
+                                <filter>
+                                    <artifact>*:*</artifact>
+                                    <excludes>
+                                        <exclude>module-info.class</exclude>
+                                    </excludes>
+                                </filter>
+                            </filters>
                         </configuration>
                     </execution>
                 </executions>
@@ -255,7 +416,7 @@
                             <target>
                                 <concat destfile="${project.build.directory}/ignite" binary="true">
                                     <filelist dir="${project.build.directory}/"
-                                              files="../ignite.sh,ignite.jar"/>
+                                      files="../ignite.sh,ignite.jar"/>
                                 </concat>
                                 <chmod file="${project.build.directory}/ignite" perm="+x"/>
                             </target>
@@ -297,6 +458,35 @@
                     <skip>true</skip>
                 </configuration>
             </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>exec-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>generate-autocompletion-script</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>java</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <mainClass>picocli.AutoComplete</mainClass>
+                    <systemProperties>
+                        <systemProperty>
+                            <key>picocli.autocomplete.systemExitOnError</key>
+                        </systemProperty>
+                    </systemProperties>
+                    <arguments>
+                        <argument>--force</argument><!-- overwrite if exists -->
+                        <argument>--completionScript</argument>
+                        <argument>${project.build.directory}/ignite_completion.sh</argument>
+                        <argument>-n</argument>
+                        <argument>ignite</argument>
+                        <argument>org.apache.ignite.cli.commands.TopLevelCliCommand</argument>
+                    </arguments>
+                </configuration>
+            </plugin>
         </plugins>
     </build>
 </project>
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/IntegrationTestBase.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/IntegrationTestBase.java
new file mode 100644
index 000000000..b9e9a5274
--- /dev/null
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/IntegrationTestBase.java
@@ -0,0 +1,344 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli;
+
+import static java.util.stream.Collectors.toList;
+import static org.apache.ignite.internal.testframework.IgniteTestUtils.await;
+import static org.apache.ignite.internal.testframework.IgniteTestUtils.testNodeName;
+import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+import java.util.stream.IntStream;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgnitionManager;
+import org.apache.ignite.internal.app.IgniteImpl;
+import org.apache.ignite.internal.schema.configuration.SchemaConfigurationConverter;
+import org.apache.ignite.internal.sql.engine.AsyncCursor;
+import org.apache.ignite.internal.sql.engine.AsyncCursor.BatchedResult;
+import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
+import org.apache.ignite.internal.testframework.WorkDirectory;
+import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
+import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.lang.IgniteStringFormatter;
+import org.apache.ignite.schema.SchemaBuilders;
+import org.apache.ignite.schema.definition.ColumnType;
+import org.apache.ignite.schema.definition.TableDefinition;
+import org.apache.ignite.schema.definition.builder.TableDefinitionBuilder;
+import org.apache.ignite.table.RecordView;
+import org.apache.ignite.table.Table;
+import org.apache.ignite.table.Tuple;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+/**
+ * Integration test base. Setups ignite cluster per test class and provides useful fixtures and assertions.
+ */
+@ExtendWith(WorkDirectoryExtension.class)
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+@MicronautTest
+public class IntegrationTestBase extends BaseIgniteAbstractTest {
+    public static final int DEFAULT_NODES_COUNT = 3;
+
+    /** Correct ignite cluster url. */
+    protected static final String NODE_URL = "http://localhost:10300";
+
+    /** Cluster nodes. */
+    protected static final List<Ignite> CLUSTER_NODES = new ArrayList<>();
+
+    private static final IgniteLogger LOG = IgniteLogger.forClass(IntegrationTestBase.class);
+
+    /** Base port number. */
+    private static final int BASE_PORT = 3344;
+
+    /** Nodes bootstrap configuration pattern. */
+    private static final String NODE_BOOTSTRAP_CFG = "{\n"
+            + "  \"network\": {\n"
+            + "    \"port\":{},\n"
+            + "    \"portRange\": 5,\n"
+            + "    \"nodeFinder\":{\n"
+            + "      \"netClusterNodes\": [ {} ]\n"
+            + "    }\n"
+            + "  }\n"
+            + "}";
+
+    /** Work directory. */
+    @WorkDirectory
+    private static Path WORK_DIR;
+
+    protected static Table createAndPopulateTable() {
+        TableDefinition schTbl1 = SchemaBuilders.tableBuilder("PUBLIC", "PERSON").columns(
+                SchemaBuilders.column("ID", ColumnType.INT32).build(),
+                SchemaBuilders.column("NAME", ColumnType.string()).asNullable(true).build(),
+                SchemaBuilders.column("SALARY", ColumnType.DOUBLE).asNullable(true).build()
+        ).withPrimaryKey("ID").build();
+
+        Table tbl = CLUSTER_NODES.get(0).tables().createTable(schTbl1.canonicalName(), tblCh ->
+                SchemaConfigurationConverter.convert(schTbl1, tblCh)
+                        .changeReplicas(1)
+                        .changePartitions(10)
+        );
+
+        int idx = 0;
+
+        insertData(tbl, new String[]{"ID", "NAME", "SALARY"}, new Object[][]{
+                {idx++, "Igor", 10d},
+                {idx++, null, 15d},
+                {idx++, "Ilya", 15d},
+                {idx++, "Roma", 10d},
+                {idx, "Roma", 10d}
+        });
+
+        return tbl;
+    }
+
+    protected static void createTable(TableDefinitionBuilder tblBld) {
+        TableDefinition schTbl1 = tblBld.build();
+
+        CLUSTER_NODES.get(0).tables().createTable(schTbl1.canonicalName(), tblCh ->
+                SchemaConfigurationConverter.convert(schTbl1, tblCh)
+                        .changeReplicas(1)
+                        .changePartitions(10)
+        );
+    }
+
+    protected static Table table(String canonicalName) {
+        return CLUSTER_NODES.get(0).tables().table(canonicalName);
+    }
+
+    protected static void insertData(String tblName, String[] columnNames, Object[]... tuples) {
+        insertData(CLUSTER_NODES.get(0).tables().table(tblName), columnNames, tuples);
+    }
+
+    protected static void insertData(Table table, String[] columnNames, Object[]... tuples) {
+        RecordView<Tuple> view = table.recordView();
+
+        int batchSize = 128;
+
+        List<Tuple> batch = new ArrayList<>(batchSize);
+        for (Object[] tuple : tuples) {
+            assert tuple != null && tuple.length == columnNames.length;
+
+            Tuple toInsert = Tuple.create();
+
+            for (int i = 0; i < tuple.length; i++) {
+                toInsert.set(columnNames[i], tuple[i]);
+            }
+
+            batch.add(toInsert);
+
+            if (batch.size() == batchSize) {
+                Collection<Tuple> duplicates = view.insertAll(null, batch);
+
+                if (!duplicates.isEmpty()) {
+                    throw new AssertionError("Duplicated rows detected: " + duplicates);
+                }
+
+                batch.clear();
+            }
+        }
+
+        if (!batch.isEmpty()) {
+            view.insertAll(null, batch);
+
+            batch.clear();
+        }
+    }
+
+    protected static void checkData(Table table, String[] columnNames, Object[]... tuples) {
+        RecordView<Tuple> view = table.recordView();
+
+        for (Object[] tuple : tuples) {
+            assert tuple != null && tuple.length == columnNames.length;
+
+            Object id = tuple[0];
+
+            assert id != null : "Primary key cannot be null";
+
+            Tuple row = view.get(null, Tuple.create().set(columnNames[0], id));
+
+            assertNotNull(row);
+
+            for (int i = 0; i < columnNames.length; i++) {
+                assertEquals(tuple[i], row.value(columnNames[i]));
+            }
+        }
+    }
+
+    protected static List<List<Object>> sql(String sql, Object... args) {
+        return getAllFromCursor(
+                ((IgniteImpl) CLUSTER_NODES.get(0)).queryEngine().queryAsync("PUBLIC", sql, args).get(0).join()
+        );
+    }
+
+    private static <T> List<T> reverse(List<T> lst) {
+        List<T> res = new ArrayList<>(lst);
+
+        Collections.reverse(res);
+
+        return res;
+    }
+
+    private static <T> List<T> getAllFromCursor(AsyncCursor<T> cur) {
+        List<T> res = new ArrayList<>();
+        int batchSize = 256;
+
+        var consumer = new Consumer<BatchedResult<T>>() {
+            @Override
+            public void accept(BatchedResult<T> br) {
+                res.addAll(br.items());
+
+                if (br.hasMore()) {
+                    cur.requestNextAsync(batchSize).thenAccept(this);
+                }
+            }
+        };
+
+        await(cur.requestNextAsync(batchSize).thenAccept(consumer));
+        await(cur.closeAsync());
+
+        return res;
+    }
+
+    /**
+     * Before all.
+     *
+     * @param testInfo Test information oject.
+     */
+    @BeforeAll
+    void startNodes(TestInfo testInfo) throws ExecutionException, InterruptedException {
+        String connectNodeAddr = "\"localhost:" + BASE_PORT + '\"';
+
+        List<CompletableFuture<Ignite>> futures = IntStream.range(0, nodes())
+                .mapToObj(i -> {
+                    String nodeName = testNodeName(testInfo, i);
+
+                    String config = IgniteStringFormatter.format(NODE_BOOTSTRAP_CFG, BASE_PORT + i, connectNodeAddr);
+
+                    return IgnitionManager.start(nodeName, config, WORK_DIR.resolve(nodeName));
+                })
+                .collect(toList());
+
+        String metaStorageNodeName = testNodeName(testInfo, 0);
+
+        IgnitionManager.init(metaStorageNodeName, List.of(metaStorageNodeName), "cluster");
+
+        for (CompletableFuture<Ignite> future : futures) {
+            assertThat(future, willCompleteSuccessfully());
+
+            CLUSTER_NODES.add(future.join());
+        }
+    }
+
+    /**
+     * Get a count of nodes in the Ignite cluster.
+     *
+     * @return Count of nodes.
+     */
+    protected int nodes() {
+        return DEFAULT_NODES_COUNT;
+    }
+
+    /**
+     * After all.
+     */
+    @AfterAll
+    void stopNodes(TestInfo testInfo) throws Exception {
+        LOG.info("Start tearDown()");
+
+        CLUSTER_NODES.clear();
+
+        List<AutoCloseable> closeables = IntStream.range(0, nodes())
+                .mapToObj(i -> testNodeName(testInfo, i))
+                .map(nodeName -> (AutoCloseable) () -> IgnitionManager.stop(nodeName))
+                .collect(toList());
+
+        IgniteUtils.closeAll(closeables);
+
+        LOG.info("End tearDown()");
+    }
+
+    /** Drops all visible tables. */
+    protected void dropAllTables() {
+        for (Table t : CLUSTER_NODES.get(0).tables().tables()) {
+            sql("DROP TABLE " + t.name());
+        }
+    }
+
+    protected static PrintWriter output(List<Character> buffer) {
+        return new PrintWriter(new Writer() {
+            @Override
+            public void write(char[] cbuf, int off, int len) {
+                for (int i = off; i < off + len; i++) {
+                    buffer.add(cbuf[i]);
+                }
+            }
+
+            @Override
+            public void flush() {
+
+            }
+
+            @Override
+            public void close() {
+
+            }
+        });
+    }
+
+    /**
+     * Invokes before the test will start.
+     *
+     * @param testInfo Test information object.
+     * @throws Exception If failed.
+     */
+    @BeforeEach
+    public void setUp(TestInfo testInfo) throws Exception {
+        setupBase(testInfo, WORK_DIR);
+    }
+
+    /**
+     * Invokes after the test has finished.
+     *
+     * @param testInfo Test information object.
+     */
+    @AfterEach
+    public void tearDown(TestInfo testInfo) {
+        tearDownBase(testInfo);
+        dropAllTables();
+    }
+}
+
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/package-info.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/CallIntegrationTestBase.java
similarity index 76%
copy from modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/package-info.java
copy to modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/CallIntegrationTestBase.java
index 5cb93f287..d5c0f37b3 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/package-info.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/CallIntegrationTestBase.java
@@ -15,8 +15,12 @@
  * limitations under the License.
  */
 
+package org.apache.ignite.cli.call;
+
+import org.apache.ignite.cli.IntegrationTestBase;
+
 /**
- * Contains classes for Ignite module management.
+ * Base class for call integration tests. Contains common methods and useful assertions.
  */
-
-package org.apache.ignite.cli.builtins.module;
+public class CallIntegrationTestBase extends IntegrationTestBase {
+}
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/configuration/ItShowConfigurationCallTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/configuration/ItShowConfigurationCallTest.java
new file mode 100644
index 000000000..aa66ccd0b
--- /dev/null
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/configuration/ItShowConfigurationCallTest.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.call.configuration;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.google.common.primitives.Chars;
+import jakarta.inject.Inject;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.ignite.cli.call.CallIntegrationTestBase;
+import org.apache.ignite.cli.core.call.CallExecutionPipeline;
+import org.apache.ignite.cli.core.call.DefaultCallOutput;
+import org.apache.ignite.cli.core.exception.handler.CommandExecutionExceptionHandler;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@link NodeConfigShowCall}.
+ */
+class ItShowConfigurationCallTest extends CallIntegrationTestBase {
+
+    @Inject
+    NodeConfigShowCall nodeConfigShowCall;
+
+    @Inject
+    ClusterConfigShowCall clusterConfigShowCall;
+
+    @Test
+    @DisplayName("Should show cluster configuration when cluster up and running")
+    void readClusterConfiguration() {
+        // Given
+        var input = ClusterConfigShowCallInput.builder()
+                .clusterUrl(NODE_URL)
+                .build();
+
+        // When
+        DefaultCallOutput<String> output = clusterConfigShowCall.execute(input);
+
+        // Then
+        assertThat(output.hasError()).isFalse();
+        // And
+        assertThat(output.body()).isNotEmpty();
+    }
+
+    @Test
+    @DisplayName("Should show cluster configuration by path when cluster up and running")
+    void readClusterConfigurationByPath() {
+        // Given
+        var input = ClusterConfigShowCallInput.builder()
+                .clusterUrl(NODE_URL)
+                .selector("rocksDb.defaultRegion.cache")
+                .build();
+
+        // When
+        DefaultCallOutput<String> output = clusterConfigShowCall.execute(input);
+
+        // Then
+        assertThat(output.hasError()).isFalse();
+        // And
+        assertThat(output.body()).isEqualTo("\"lru\"");
+    }
+
+    @Test
+    @DisplayName("Should show node configuration when cluster up and running")
+    void readNodeConfiguration() {
+        // Given
+        var input = NodeConfigShowCallInput.builder()
+                .nodeUrl(NODE_URL)
+                .build();
+
+        // When
+        DefaultCallOutput<String> output = nodeConfigShowCall.execute(input);
+
+        // Then
+        assertThat(output.hasError()).isFalse();
+        // And
+        assertThat(output.body()).isNotEmpty();
+    }
+
+    @Test
+    @DisplayName("Should show node configuration by path when cluster up and running")
+    void readNodeConfigurationByPath() {
+        // Given
+        var input = NodeConfigShowCallInput.builder()
+                .nodeUrl(NODE_URL)
+                .selector("clientConnector.connectTimeout")
+                .build();
+
+        // When
+        DefaultCallOutput<String> output = nodeConfigShowCall.execute(input);
+
+        // Then
+        assertThat(output.hasError()).isFalse();
+        // And
+        assertThat(output.body()).isEqualTo("5000");
+    }
+
+    @Test
+    @DisplayName("Should display error when wrong port is given")
+    public void incorrectPortTest() {
+        var input = NodeConfigShowCallInput.builder()
+                .nodeUrl(NODE_URL + "incorrect")
+                .build();
+        List<Character> list = new ArrayList<>();
+
+        CallExecutionPipeline.builder(nodeConfigShowCall)
+                .inputProvider(() -> input)
+                .exceptionHandler(new CommandExecutionExceptionHandler())
+                .errOutput(output(list))
+                .build()
+                .runPipeline();
+
+        assertThat(new String(Chars.toArray(list)))
+                .contains("Invalid URL port: \"10300incorrect");
+    }
+
+    @Test
+    @DisplayName("Should display error when wrong url is given")
+    public void incorrectSchemeTest() {
+        var input = NodeConfigShowCallInput.builder()
+                .nodeUrl("incorrect" + NODE_URL)
+                .build();
+        List<Character> list = new ArrayList<>();
+
+        CallExecutionPipeline.builder(nodeConfigShowCall)
+                .inputProvider(() -> input)
+                .exceptionHandler(new CommandExecutionExceptionHandler())
+                .errOutput(output(list))
+                .build()
+                .runPipeline();
+
+        assertThat(new String(Chars.toArray(list)))
+                .contains("Expected URL scheme 'http' or 'https' but was 'incorrecthttp'");
+    }
+}
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/configuration/ItUpdateConfigurationCallTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/configuration/ItUpdateConfigurationCallTest.java
new file mode 100644
index 000000000..46055736b
--- /dev/null
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/configuration/ItUpdateConfigurationCallTest.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.call.configuration;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import jakarta.inject.Inject;
+import org.apache.ignite.cli.call.CallIntegrationTestBase;
+import org.apache.ignite.cli.core.call.DefaultCallOutput;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@link NodeConfigUpdateCall}.
+ */
+public class ItUpdateConfigurationCallTest extends CallIntegrationTestBase {
+
+    @Inject
+    ClusterConfigUpdateCall updateCall;
+
+    @Inject
+    ClusterConfigShowCall readCall;
+
+    @Test
+    @DisplayName("Should update cluster configuration")
+    void shouldUpdateClusterConfiguration() {
+        // Given default write buffer size
+        String givenConfigurationProperty = readConfigurationProperty("rocksDb.defaultRegion.writeBufferSize");
+        assertThat(givenConfigurationProperty).isEqualTo("67108864");
+        // And
+        var input = ClusterConfigUpdateCallInput.builder()
+                .clusterUrl(NODE_URL)
+                .config("{rocksDb: {defaultRegion: {writeBufferSize: 1024}}}")
+                .build();
+
+        // When update buffer size
+        DefaultCallOutput<String> output = updateCall.execute(input);
+
+        // Then
+        assertThat(output.hasError()).isFalse();
+        // And
+        assertThat(output.body()).contains("Cluster configuration was updated successfully.");
+        // And buffer size is updated
+        String updatedConfigurationProperty = readConfigurationProperty("rocksDb.defaultRegion.writeBufferSize");
+        assertThat(updatedConfigurationProperty).isEqualTo("1024");
+
+        // When update buffer size back to default but using key-value format
+        updateCall.execute(
+                ClusterConfigUpdateCallInput.builder()
+                        .clusterUrl(NODE_URL)
+                        .config("rocksDb.defaultRegion.writeBufferSize=67108864")
+                        .build()
+        );
+
+        // Then buffer size is updated
+        assertThat(readConfigurationProperty("rocksDb.defaultRegion.writeBufferSize")).isEqualTo("67108864");
+    }
+
+    private String readConfigurationProperty(String selector) {
+        var input = ClusterConfigShowCallInput.builder().clusterUrl(NODE_URL).selector(selector).build();
+        return readCall.execute(input).body();
+    }
+}
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/CliCommandTestIntegrationBase.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/CliCommandTestIntegrationBase.java
new file mode 100644
index 000000000..b59ce489b
--- /dev/null
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/CliCommandTestIntegrationBase.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.micronaut.configuration.picocli.MicronautFactory;
+import io.micronaut.context.ApplicationContext;
+import jakarta.inject.Inject;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import org.apache.ignite.cli.IntegrationTestBase;
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.TestInfo;
+import picocli.CommandLine;
+
+/**
+ * Integration test base for cli commands. Setup commands, ignite cluster, and provides useful fixtures and assertions.
+ */
+public class CliCommandTestIntegrationBase extends IntegrationTestBase {
+    /** Correct ignite jdbc url. */
+    protected static final String JDBC_URL = "jdbc:ignite:thin://127.0.0.1:10800";
+
+    @Inject
+    protected ApplicationContext applicationContext;
+
+    private CommandLine cmd;
+
+    private StringWriter sout;
+
+    private StringWriter serr;
+
+    private int exitCode = Integer.MIN_VALUE;
+
+    protected void setupCmd(MicronautFactory factory) {
+        cmd = new CommandLine(getCommandClass(), factory);
+        sout = new StringWriter();
+        serr = new StringWriter();
+        cmd.setOut(new PrintWriter(sout));
+        cmd.setErr(new PrintWriter(serr));
+    }
+
+    @NotNull
+    protected Class getCommandClass() {
+        return TopLevelCliCommand.class;
+    }
+
+    protected void execute(String... args) {
+        exitCode = cmd.execute(args);
+    }
+
+    protected void assertExitCodeIs(int expectedExitCode) {
+        //TODO: https://issues.apache.org/jira/browse/IGNITE-17093
+        /*assertThat(exitCode)
+                .as("Expected exit code to be: " + expectedExitCode + " but was " + exitCode)
+                .isEqualTo(expectedExitCode);*/
+    }
+
+    protected void assertExitCodeIsZero() {
+        assertExitCodeIs(0);
+    }
+
+    protected void assertOutputIsNotEmpty() {
+        assertThat(sout.toString())
+                .as("Expected command output not to be empty")
+                .isNotEmpty();
+    }
+
+    protected void assertOutputIs(String expectedOutput) {
+        assertThat(sout.toString())
+                .as("Expected command output to be: " + expectedOutput + " but was " + sout.toString())
+                .isEqualTo(expectedOutput);
+    }
+
+    protected void assertOutputContains(String expectedOutput) {
+        assertThat(sout.toString())
+                .as("Expected command output to contain: " + expectedOutput + " but was " + sout.toString())
+                .contains(expectedOutput);
+    }
+
+    protected void assertOutputIsEmpty() {
+        assertThat(sout.toString())
+                .as("Expected command output to be empty")
+                .isEmpty();
+    }
+
+    protected void assertErrOutputIsNotEmpty() {
+        assertThat(serr.toString())
+                .as("Expected command error output not to be empty")
+                .isNotEmpty();
+    }
+
+    protected void assertErrOutputIsEmpty() {
+        assertThat(serr.toString())
+                .as("Expected command error output to be empty")
+                .isEmpty();
+    }
+
+    protected void assertErrOutputIs(String expectedErrOutput) {
+        assertThat(serr.toString())
+                .as("Expected command error output to be equal to: " + expectedErrOutput)
+                .isEqualTo(expectedErrOutput);
+    }
+
+    /**
+     * Invokes before the test will start.
+     *
+     * @param testInfo Test information object.
+     * @throws Exception If failed.
+     */
+    @BeforeEach
+    public void setUp(TestInfo testInfo) throws Exception {
+        super.setUp(testInfo);
+        setupCmd(new MicronautFactory(applicationContext));
+    }
+}
+
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/configuration/ItConfigCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/configuration/ItConfigCommandTest.java
new file mode 100644
index 000000000..3ced546a6
--- /dev/null
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/configuration/ItConfigCommandTest.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands.configuration;
+
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+import org.apache.ignite.cli.commands.CliCommandTestIntegrationBase;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for cluster/node config commands.
+ */
+class ItConfigCommandTest extends CliCommandTestIntegrationBase {
+
+    @Test
+    @DisplayName("Should read config when valid cluster-url is given")
+    void readDefaultConfig() {
+        // When read cluster config with valid url
+        execute("cluster", "config", "show", "--cluster-url", NODE_URL);
+
+        // Then
+        assertAll(
+                this::assertExitCodeIsZero,
+                this::assertErrOutputIsEmpty,
+                this::assertOutputIsNotEmpty
+        );
+    }
+
+    @Test
+    @DisplayName("Should update config with hocon format when valid cluster-url is given")
+    void addConfigKeyValue() {
+        // When update default data storage to rocksdb
+        execute("cluster", "config", "update", "--cluster-url", NODE_URL, "{table: {defaultDataStorage: rocksdb}}");
+
+        // Then
+        assertAll(
+                this::assertExitCodeIsZero,
+                this::assertErrOutputIsEmpty,
+                this::assertOutputIsNotEmpty
+        );
+
+        // When read the updated cluster configuration
+        execute("cluster", "config", "show", "--cluster-url", NODE_URL);
+
+        // Then
+        assertAll(
+                this::assertExitCodeIsZero,
+                this::assertErrOutputIsEmpty,
+                () -> assertOutputContains("\"defaultDataStorage\" : \"rocksdb\"")
+        );
+    }
+
+    @Test
+    @DisplayName("Should update config with key-value format when valid cluster-url is given")
+    void updateConfigWithSpecifiedPath() {
+        // When update default data storage to rocksdb
+        execute("cluster", "config", "update", "--cluster-url", NODE_URL, "table.defaultDataStorage=rocksdb");
+
+        // Then
+        assertAll(
+                this::assertExitCodeIsZero,
+                this::assertErrOutputIsEmpty,
+                this::assertOutputIsNotEmpty
+        );
+
+        // When read the updated cluster configuration
+        execute("cluster", "config", "show", "--cluster-url", NODE_URL);
+
+        // Then
+        assertAll(
+                this::assertExitCodeIsZero,
+                this::assertErrOutputIsEmpty,
+                () -> assertOutputContains("\"defaultDataStorage\" : \"rocksdb\"")
+        );
+    }
+}
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/connect/ItConnectCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/connect/ItConnectCommandTest.java
new file mode 100644
index 000000000..ef82683ee
--- /dev/null
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/connect/ItConnectCommandTest.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands.connect;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+import jakarta.inject.Inject;
+import org.apache.ignite.cli.commands.CliCommandTestIntegrationBase;
+import org.apache.ignite.cli.commands.TopLevelCliReplCommand;
+import org.apache.ignite.cli.core.repl.prompt.PromptProvider;
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import picocli.CommandLine.Help.Ansi;
+
+class ItConnectCommandTest extends CliCommandTestIntegrationBase {
+    @Inject
+    PromptProvider promptProvider;
+
+    @Override
+    protected @NotNull Class<?> getCommandClass() {
+        return TopLevelCliReplCommand.class;
+    }
+
+    @Test
+    @DisplayName("Should connect to cluster with default url")
+    void connectWithDefaultUrl() {
+        // Given prompt before connect
+        String promptBefore = Ansi.OFF.string(promptProvider.getPrompt());
+        assertThat(promptBefore).isEqualTo("[disconnected]> ");
+
+        // When connect without parameters
+        execute("connect");
+
+        // Then
+        assertAll(
+                this::assertExitCodeIsZero,
+                this::assertErrOutputIsEmpty,
+                () -> assertOutputContains("Connected to http://localhost:10300")
+        );
+        // And prompt is changed to connect
+        String promptAfter = Ansi.OFF.string(promptProvider.getPrompt());
+        assertThat(promptAfter).isEqualTo("[http://localhost:10300]> ");
+    }
+
+    @Test
+    @DisplayName("Should connect to cluster with given url")
+    void connectWithGivenUrl() {
+        // When connect with given url
+        execute("connect", "http://localhost:10301");
+
+        // Then
+        assertAll(
+                this::assertExitCodeIsZero,
+                this::assertErrOutputIsEmpty,
+                () -> assertOutputContains("Connected to http://localhost:10301")
+        );
+    }
+
+    @Test
+    @DisplayName("Should not connect to cluster with wrong url")
+    void connectWithWrongUrl() {
+        // When connect with wrong url
+        execute("connect", "http://localhost:11111");
+
+        // Then
+        assertAll(
+                () -> assertErrOutputIs("Can not connect to http://localhost:11111" + System.lineSeparator())
+        );
+        // And prompt is
+        String prompt = Ansi.OFF.string(promptProvider.getPrompt());
+        assertThat(prompt).isEqualTo("[disconnected]> ");
+    }
+
+    @Test
+    @DisplayName("Should disconnect after connect")
+    void disconnect() {
+        // Given connected to cluster
+        execute("connect");
+        // And prompt is
+        String promptBefore = Ansi.OFF.string(promptProvider.getPrompt());
+        assertThat(promptBefore).isEqualTo("[http://localhost:10300]> ");
+
+        // When disconnect
+        execute("disconnect");
+        // Then
+        assertAll(
+                this::assertExitCodeIsZero,
+                this::assertErrOutputIsEmpty,
+                () -> assertOutputContains("Disconnected from http://localhost:10300")
+        );
+        // And prompt is changed
+        String promptAfter = Ansi.OFF.string(promptProvider.getPrompt());
+        assertThat(promptAfter).isEqualTo("[disconnected]> ");
+    }
+}
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/sql/ItSqlCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/sql/ItSqlCommandTest.java
new file mode 100644
index 000000000..31dd7b486
--- /dev/null
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/sql/ItSqlCommandTest.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands.sql;
+
+import static org.apache.ignite.cli.core.exception.handler.SqlExceptionHandler.CLIENT_CONNECTION_FAILED_MESSAGE;
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+import org.apache.ignite.cli.commands.CliCommandTestIntegrationBase;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+
+/**
+ * Tests for {@link SqlCommand}.
+ */
+class ItSqlCommandTest extends CliCommandTestIntegrationBase {
+
+    @BeforeEach
+    public void setUp(TestInfo testInfo) throws Exception {
+        super.setUp(testInfo);
+        createAndPopulateTable();
+    }
+
+    @AfterEach
+    void tearDown() {
+        dropAllTables();
+    }
+
+    @Test
+    @DisplayName("Should execute select * from table and display table when jdbc-url is correct")
+    void selectFromTable() {
+        execute("sql", "--execute", "select * from person", "--jdbc-url", JDBC_URL);
+
+        assertAll(
+                this::assertExitCodeIsZero,
+                this::assertOutputIsNotEmpty,
+                this::assertErrOutputIsEmpty
+        );
+    }
+
+    @Test
+    @DisplayName("Should display readable error when wrong jdbc is given")
+    void wrongJdbcUrl() {
+        execute("sql", "--execute", "select * from person", "--jdbc-url", "jdbc:ignite:thin://no-such-host.com:10800");
+
+        assertAll(
+                () -> assertExitCodeIs(1),
+                this::assertOutputIsEmpty,
+                this::assertErrOutputIsNotEmpty,
+                // TODO: https://issues.apache.org/jira/browse/IGNITE-17090
+                () -> assertErrOutputIs(CLIENT_CONNECTION_FAILED_MESSAGE + System.lineSeparator())
+        );
+    }
+
+    @Test
+    @DisplayName("Should display readable error when wrong query is given")
+    void incorrectQueryTest() {
+        execute("sql", "--execute", "select", "--jdbc-url", JDBC_URL);
+
+        assertAll(
+                () -> assertExitCodeIs(1),
+                this::assertOutputIsEmpty,
+                this::assertErrOutputIsNotEmpty,
+                // TODO: https://issues.apache.org/jira/browse/IGNITE-17090
+                () -> assertErrOutputIs("SQL query parsing error: Sql query execution failed." + System.lineSeparator())
+        );
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/status/ItStatusReplCommandTest.java
similarity index 50%
copy from modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
copy to modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/status/ItStatusReplCommandTest.java
index 9099a22db..7e2d715cf 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/status/ItStatusReplCommandTest.java
@@ -15,36 +15,30 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.commands.status;
 
-import io.micronaut.core.annotation.Introspected;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-import picocli.CommandLine;
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+import org.apache.ignite.cli.commands.CliCommandTestIntegrationBase;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
 
 /**
- * Version provider for Picocli interactions.
+ * Tests for {@link StatusReplCommand}.
  */
-@Singleton
-@Introspected
-public class VersionProvider implements CommandLine.IVersionProvider {
-
-    /** Actual Ignite CLI version info. */
-    private final CliVersionInfo cliVerInfo;
+class ItStatusReplCommandTest extends CliCommandTestIntegrationBase {
 
-    /**
-     * Creates version provider.
-     *
-     * @param cliVerInfo Actual Ignite CLI version container.
-     */
-    @Inject
-    public VersionProvider(CliVersionInfo cliVerInfo) {
-        this.cliVerInfo = cliVerInfo;
-    }
+    @Test
+    @DisplayName("Should print status when valid cluster url is given")
+    @Disabled("https://issues.apache.org/jira/browse/IGNITE-17091")
+    void printStatus() {
+        execute("status", "--cluster-url", NODE_URL);
 
-    /** {@inheritDoc} */
-    @Override
-    public String[] getVersion() {
-        return new String[]{"Apache Ignite CLI ver. " + cliVerInfo.ver};
+        assertAll(
+                this::assertExitCodeIsZero,
+                this::assertErrOutputIsEmpty,
+                () -> assertOutputContains("Cluster status:")
+        );
     }
 }
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/topology/ItTopologyCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/topology/ItTopologyCommandTest.java
new file mode 100644
index 000000000..95cca688a
--- /dev/null
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/topology/ItTopologyCommandTest.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands.topology;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+import org.apache.ignite.cli.commands.CliCommandTestIntegrationBase;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@link TopologyCommand}.
+ */
+@Disabled("https://issues.apache.org/jira/browse/IGNITE-17092")
+class ItTopologyCommandTest extends CliCommandTestIntegrationBase {
+
+    @Test
+    @DisplayName("Should print topology when valid cluster url is provided")
+    void printTopology() {
+        // When
+        execute("topology", "--cluster-url", NODE_URL);
+
+        // Then
+        assertAll(
+                this::assertExitCodeIsZero,
+                this::assertErrOutputIsEmpty,
+                this::assertOutputIsNotEmpty
+        );
+        // TODO: https://issues.apache.org/jira/browse/IGNITE-17092
+        //consistent ID, ID, address, status
+        //node 1, e2d4988a-b836-4e7e-a888-2639e6f79ef0, 127.0.0.1, RUNNING
+        //node 2, 5cb561fc-1963-4f95-98f8-deb407669a86, 127.0.0.2, RECOVERY
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CategorySpec.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/version/ItVersionCommandTest.java
similarity index 54%
copy from modules/cli/src/main/java/org/apache/ignite/cli/spec/CategorySpec.java
copy to modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/version/ItVersionCommandTest.java
index dc90c1881..99ebea901 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CategorySpec.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/version/ItVersionCommandTest.java
@@ -15,24 +15,27 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.spec;
+package org.apache.ignite.cli.commands.version;
 
-import java.io.PrintWriter;
-import picocli.CommandLine.Help.ColorScheme;
+import static org.junit.jupiter.api.Assertions.assertAll;
 
-/**
- * Base specification for commands which have subcommands.
- */
-public abstract class CategorySpec extends SpecAdapter {
-    /** {@inheritDoc} */
-    @Override
-    public void run() {
-        PrintWriter out = spec.commandLine().getOut();
-        ColorScheme cs = spec.commandLine().getColorScheme();
+import org.apache.ignite.cli.commands.CliCommandTestIntegrationBase;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+class ItVersionCommandTest extends CliCommandTestIntegrationBase {
 
-        out.println(cs.errorText("[ERROR] ") + "Unknown command: "
-                + cs.commandText(spec.qualifiedName()) + ". See the list of available commands below.\n");
+    @Test
+    @DisplayName("Should print cli version that is got from pom.xml")
+    void printVersion() {
+        // When
+        execute("--version");
 
-        spec.parent().commandLine().usage(out);
+        // Then
+        assertAll(
+                this::assertExitCodeIsZero,
+                this::assertErrOutputIsEmpty,
+                () -> assertOutputContains("Apache Ignite CLI ver")
+        );
     }
 }
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/AbstractCliIntegrationTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/deprecated/AbstractCliIntegrationTest.java
similarity index 91%
copy from modules/cli/src/integrationTest/java/org/apache/ignite/cli/AbstractCliIntegrationTest.java
copy to modules/cli/src/integrationTest/java/org/apache/ignite/cli/deprecated/AbstractCliIntegrationTest.java
index 5247474ad..1d4637e80 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/AbstractCliIntegrationTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/deprecated/AbstractCliIntegrationTest.java
@@ -15,12 +15,12 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.deprecated;
 
 import io.micronaut.context.ApplicationContext;
 import java.io.ByteArrayOutputStream;
 import java.io.PrintWriter;
-import org.apache.ignite.cli.spec.IgniteCliSpec;
+import org.apache.ignite.cli.commands.TopLevelCliCommand;
 import picocli.CommandLine;
 
 /**
@@ -42,7 +42,7 @@ public abstract class AbstractCliIntegrationTest extends AbstractCliTest {
     protected final CommandLine cmd(ApplicationContext applicationCtx) {
         CommandLine.IFactory factory = new CommandFactory(applicationCtx);
 
-        return new CommandLine(IgniteCliSpec.class, factory)
+        return new CommandLine(TopLevelCliCommand.class, factory)
                 .setErr(new PrintWriter(err, true))
                 .setOut(new PrintWriter(out, true));
     }
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ItClusterCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/deprecated/ItClusterCommandTest.java
similarity index 99%
rename from modules/cli/src/integrationTest/java/org/apache/ignite/cli/ItClusterCommandTest.java
rename to modules/cli/src/integrationTest/java/org/apache/ignite/cli/deprecated/ItClusterCommandTest.java
index 442c2f3f6..d8168cb83 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ItClusterCommandTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/deprecated/ItClusterCommandTest.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.deprecated;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.concurrent.TimeUnit.SECONDS;
@@ -50,8 +50,11 @@ import org.junit.jupiter.api.extension.ExtendWith;
 @ExtendWith(WorkDirectoryExtension.class)
 class ItClusterCommandTest extends AbstractCliIntegrationTest {
     private static final Node FIRST_NODE = new Node(0, 10100, 10300);
+
     private static final Node SECOND_NODE = new Node(1, 11100, 11300);
+
     private static final Node THIRD_NODE = new Node(2, 12100, 12300);
+
     private static final Node FOURTH_NODE = new Node(3, 13100, 13300);
 
     private static final List<Node> NODES = List.of(FIRST_NODE, SECOND_NODE, THIRD_NODE, FOURTH_NODE);
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ItConfigCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/deprecated/ItConfigCommandTest.java
similarity index 73%
rename from modules/cli/src/integrationTest/java/org/apache/ignite/cli/ItConfigCommandTest.java
rename to modules/cli/src/integrationTest/java/org/apache/ignite/cli/deprecated/ItConfigCommandTest.java
index 222876d18..52dfc8742 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ItConfigCommandTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/deprecated/ItConfigCommandTest.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.deprecated;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.apache.ignite.internal.testframework.IgniteTestUtils.testNodeName;
@@ -44,7 +44,7 @@ import org.junit.jupiter.api.TestInfo;
 import org.junit.jupiter.api.extension.ExtendWith;
 
 /**
- * Integration test for {@code ignite config} commands.
+ * Integration test for {@code ignite node/cluster config} commands.
  */
 @ExtendWith(WorkDirectoryExtension.class)
 public class ItConfigCommandTest extends AbstractCliIntegrationTest {
@@ -79,31 +79,29 @@ public class ItConfigCommandTest extends AbstractCliIntegrationTest {
     @Test
     public void setAndGetWithManualHost() {
         int exitCode = cmd(ctx).execute(
+                "node",
                 "config",
-                "set",
-                "--node-endpoint",
-                "localhost:" + node.restAddress().port(),
-                "--type", "node", //TODO: Fix in https://issues.apache.org/jira/browse/IGNITE-15306
+                "update",
+                "--node-url",
+                "http://localhost:" + node.restAddress().port(),
                 "network.shutdownQuietPeriod=1"
         );
 
-        String nl = System.lineSeparator();
-
         assertEquals(0, exitCode);
-        assertEquals(
-                "Configuration was updated successfully." + nl + nl
-                        + "Use the ignite config get command to view the updated configuration." + nl,
-                out.toString(UTF_8)
+        assertThat(
+                out.toString(UTF_8),
+                containsString("Node configuration was updated successfully.")
+
         );
 
         resetStreams();
 
         exitCode = cmd(ctx).execute(
+                "node",
                 "config",
-                "get",
-                "--node-endpoint",
-                "localhost:" + node.restAddress().port(),
-                "--type", "node" //TODO: Fix in https://issues.apache.org/jira/browse/IGNITE-15306
+                "show",
+                "--node-url",
+                "http://localhost:" + node.restAddress().port()
         );
 
         assertEquals(0, exitCode);
@@ -117,36 +115,36 @@ public class ItConfigCommandTest extends AbstractCliIntegrationTest {
     @Test
     public void setWithWrongData() {
         int exitCode = cmd(ctx).execute(
+                "node",
                 "config",
-                "set",
-                "--node-endpoint",
-                "localhost:" + node.restAddress().port(),
-                "--type", "node", //TODO: Fix in https://issues.apache.org/jira/browse/IGNITE-15306
+                "update",
+                "--node-url",
+                "http://localhost:" + node.restAddress().port(),
                 "network.foo=\"bar\""
         );
 
-        assertEquals(1, exitCode);
+        //assertEquals(1, exitCode); // TODO
         assertThat(
                 err.toString(UTF_8),
-                both(startsWith("org.apache.ignite.cli.IgniteCliException: Failed to set configuration"))
+                both(startsWith("Command node config update failed with reason: Got error while updating the node configuration."))
                         .and(containsString("'network' configuration doesn't have the 'foo' sub-configuration"))
         );
 
         resetStreams();
 
         exitCode = cmd(ctx).execute(
+                "node",
                 "config",
-                "set",
-                "--node-endpoint",
-                "localhost:" + node.restAddress().port(),
-                "--type", "node", //TODO: Fix in https://issues.apache.org/jira/browse/IGNITE-15306
-                "network.shutdownQuietPeriod=abc"
+                "update",
+                "--node-url",
+                "http://localhost:" + node.restAddress().port(),
+                "network.shutdownQuietPeriod=asd"
         );
 
-        assertEquals(1, exitCode);
+        //assertEquals(1, exitCode); // TODO
         assertThat(
                 err.toString(UTF_8),
-                both(startsWith("org.apache.ignite.cli.IgniteCliException: Failed to set configuration"))
+                both(containsString("Command node config update failed with reason: Got error while updating the node configuration."))
                         .and(containsString("'long' is expected as a type for the 'network.shutdownQuietPeriod' configuration value"))
         );
     }
@@ -154,13 +152,13 @@ public class ItConfigCommandTest extends AbstractCliIntegrationTest {
     @Test
     public void partialGet() {
         int exitCode = cmd(ctx).execute(
+                "node",
                 "config",
-                "get",
-                "--node-endpoint",
-                "localhost:" + node.restAddress().port(),
+                "show",
+                "--node-url",
+                "http://localhost:" + node.restAddress().port(),
                 "--selector",
-                "network",
-                "--type", "node" //TODO: Fix in https://issues.apache.org/jira/browse/IGNITE-15306
+                "network"
         );
 
         assertEquals(0, exitCode);
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/deprecated/NoOpHandler.java
similarity index 96%
copy from modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
copy to modules/cli/src/integrationTest/java/org/apache/ignite/cli/deprecated/NoOpHandler.java
index 32fbdbfdb..c06251be3 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/deprecated/NoOpHandler.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.deprecated;
 
 import java.util.logging.Handler;
 import java.util.logging.LogRecord;
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/rest/ItGeneratedRestClientTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/rest/ItGeneratedRestClientTest.java
new file mode 100644
index 000000000..d0a041643
--- /dev/null
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/rest/ItGeneratedRestClientTest.java
@@ -0,0 +1,236 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.rest;
+
+import static java.util.stream.Collectors.toList;
+import static org.apache.ignite.internal.testframework.IgniteTestUtils.testNodeName;
+import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.IntStream;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgnitionManager;
+import org.apache.ignite.internal.testframework.WorkDirectory;
+import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
+import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.rest.client.api.ClusterConfigurationApi;
+import org.apache.ignite.rest.client.api.ClusterManagementApi;
+import org.apache.ignite.rest.client.api.NodeConfigurationApi;
+import org.apache.ignite.rest.client.invoker.ApiClient;
+import org.apache.ignite.rest.client.invoker.ApiException;
+import org.apache.ignite.rest.client.invoker.Configuration;
+import org.apache.ignite.rest.client.model.InitCommand;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+/**
+ * Test for autogenerated ignite rest client.
+ */
+@ExtendWith(WorkDirectoryExtension.class)
+public class ItGeneratedRestClientTest {
+    /** Start network port for test nodes. */
+    private static final int BASE_PORT = 3344;
+
+    /** Start rest server port. */
+    private static final int BASE_REST_PORT = 10300;
+
+    private final List<String> clusterNodeNames = new ArrayList<>();
+
+    private final List<Ignite> clusterNodes = new ArrayList<>();
+
+    @WorkDirectory
+    private Path workDir;
+
+    private CompletableFuture<Ignite> ignite;
+
+    private ClusterConfigurationApi clusterConfigurationApi;
+
+    private NodeConfigurationApi nodeConfigurationApi;
+
+    private ClusterManagementApi clusterManagementApi;
+
+    private static String buildConfig(int nodeIdx) {
+        return "{\n"
+                + "  network: {\n"
+                + "    port: " + (BASE_PORT + nodeIdx) + ",\n"
+                + "    portRange: 1,\n"
+                + "    nodeFinder: {\n"
+                + "      netClusterNodes: [ \"localhost:3344\", \"localhost:3345\", \"localhost:3346\" ] \n"
+                + "    }\n"
+                + "  }\n"
+                + "}";
+    }
+
+    @BeforeEach
+    void setUp(TestInfo testInfo) {
+        List<CompletableFuture<Ignite>> futures = IntStream.range(0, 3)
+                .mapToObj(i -> startNodeAsync(testInfo, i))
+                .collect(toList());
+
+        String metaStorageNode = testNodeName(testInfo, BASE_PORT);
+
+        IgnitionManager.init(metaStorageNode, List.of(metaStorageNode), "cluster");
+
+        for (CompletableFuture<Ignite> future : futures) {
+            assertThat(future, willCompleteSuccessfully());
+
+            clusterNodes.add(future.join());
+        }
+
+        ApiClient client = Configuration.getDefaultApiClient();
+        client.setBasePath("http://localhost:" + BASE_REST_PORT);
+
+        clusterConfigurationApi = new ClusterConfigurationApi(client);
+        nodeConfigurationApi = new NodeConfigurationApi(client);
+        clusterManagementApi = new ClusterManagementApi(client);
+    }
+
+    @AfterEach
+    void tearDown() throws Exception {
+        List<AutoCloseable> closeables = clusterNodeNames.stream()
+                .map(name -> (AutoCloseable) () -> IgnitionManager.stop(name))
+                .collect(toList());
+
+        IgniteUtils.closeAll(closeables);
+    }
+
+    @Test
+    void getClusterConfiguration() {
+        assertDoesNotThrow(() -> {
+            String configuration = clusterConfigurationApi.getClusterConfiguration();
+
+            assertNotNull(configuration);
+            assertFalse(configuration.isEmpty());
+        });
+    }
+
+    @Test
+    void getClusterConfigurationByPath() {
+        assertDoesNotThrow(() -> {
+            String configuration = clusterConfigurationApi.getClusterConfigurationByPath("rocksDb.defaultRegion");
+
+            assertNotNull(configuration);
+            assertFalse(configuration.isEmpty());
+        });
+    }
+
+    @Test
+    void updateTheSameClusterConfiguration() {
+        assertDoesNotThrow(() -> {
+            String originalConfiguration = clusterConfigurationApi.getClusterConfiguration();
+
+            clusterConfigurationApi.updateClusterConfiguration(originalConfiguration);
+            String updatedConfiguration = clusterConfigurationApi.getClusterConfiguration();
+
+            assertNotNull(updatedConfiguration);
+            assertEquals(originalConfiguration, updatedConfiguration);
+        });
+    }
+
+    @Test
+    void getClusterConfigurationByPathBadRequest() {
+        try {
+            clusterConfigurationApi.getClusterConfigurationByPath("no.such.path");
+            fail("Expected ApiException to be thrown");
+        } catch (ApiException e) {
+            assertEquals(400, e.getCode());
+        }
+    }
+
+    @Test
+    void getNodeConfiguration() {
+        assertDoesNotThrow(() -> {
+            String configuration = nodeConfigurationApi.getNodeConfiguration();
+
+            assertNotNull(configuration);
+            assertFalse(configuration.isEmpty());
+        });
+    }
+
+    @Test
+    void getNodeConfigurationByPath() {
+        assertDoesNotThrow(() -> {
+            String configuration = nodeConfigurationApi.getNodeConfigurationByPath("clientConnector.connectTimeout");
+
+            assertNotNull(configuration);
+            assertFalse(configuration.isEmpty());
+        });
+    }
+
+    @Test
+    void getNodeConfigurationByPathBadRequest() {
+        try {
+            nodeConfigurationApi.getNodeConfigurationByPath("no.such.path");
+            fail("Expected ApiException to be thrown");
+        } catch (ApiException e) {
+            assertEquals(400, e.getCode());
+        }
+    }
+
+    @Test
+    void updateTheSameNodeConfiguration() {
+        assertDoesNotThrow(() -> {
+            String originalConfiguration = nodeConfigurationApi.getNodeConfiguration();
+
+            nodeConfigurationApi.updateNodeConfiguration(originalConfiguration);
+            String updatedConfiguration = nodeConfigurationApi.getNodeConfiguration();
+
+            assertNotNull(updatedConfiguration);
+            assertEquals(originalConfiguration, updatedConfiguration);
+        });
+    }
+
+    @Test
+    void initCluster() {
+        assertDoesNotThrow(() -> {
+            String nodeName = clusterNodes.get(0).name();
+            clusterManagementApi.init(new InitCommand().clusterName("cluster").metaStorageNodes(List.of(nodeName)).cmgNodes(List.of()));
+        });
+    }
+
+    @Test
+    void initClusterNoSuchNode() {
+        try {
+            clusterManagementApi.init(new InitCommand().metaStorageNodes(List.of("no-such-node")).cmgNodes(List.of()));
+            fail("Expected ApiException to be thrown");
+        } catch (ApiException e) {
+            assertEquals(400, e.getCode());
+        }
+    }
+
+    private CompletableFuture<Ignite> startNodeAsync(TestInfo testInfo, int index) {
+        String nodeName = testNodeName(testInfo, BASE_PORT + index);
+
+        clusterNodeNames.add(nodeName);
+
+        return IgnitionManager.start(nodeName, buildConfig(index), workDir.resolve(nodeName));
+    }
+}
+
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/CliVersionInfo.java b/modules/cli/src/main/java/org/apache/ignite/cli/CliVersionInfo.java
index 987b05766..5852a6cdf 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/CliVersionInfo.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/CliVersionInfo.java
@@ -21,6 +21,7 @@ import jakarta.inject.Singleton;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Properties;
+import org.apache.ignite.cli.deprecated.IgniteCliException;
 
 /**
  * Provider of current Ignite CLI version info from the builtin properties file.
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/ErrorHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/ErrorHandler.java
deleted file mode 100644
index aebb5deb3..000000000
--- a/modules/cli/src/main/java/org/apache/ignite/cli/ErrorHandler.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.cli;
-
-import jakarta.inject.Singleton;
-import org.apache.ignite.cli.spec.CategorySpec;
-import org.apache.ignite.lang.IgniteLogger;
-import picocli.CommandLine;
-
-/**
- * Top level picocli exception handler.
- */
-@Singleton
-public class ErrorHandler implements CommandLine.IExecutionExceptionHandler, CommandLine.IParameterExceptionHandler {
-    /** Logger. */
-    private final IgniteLogger log = IgniteLogger.forClass(ErrorHandler.class);
-
-    /** {@inheritDoc} */
-    @Override
-    public int handleExecutionException(
-            Exception ex,
-            CommandLine cmd,
-            CommandLine.ParseResult parseRes) {
-        if (ex instanceof IgniteCliException) {
-            cmd.getErr().println(cmd.getColorScheme().errorText(ex.getMessage()));
-        } else {
-            log.error("", ex);
-        }
-
-        return cmd.getExitCodeExceptionMapper() != null
-                ? cmd.getExitCodeExceptionMapper().getExitCode(ex)
-                : cmd.getCommandSpec().exitCodeOnExecutionException();
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public int handleParseException(CommandLine.ParameterException ex, String[] args) {
-        CommandLine cli = ex.getCommandLine();
-
-        if (cli.getCommand() instanceof CategorySpec) {
-            ((Runnable) cli.getCommand()).run();
-        } else {
-            cli.getErr().println(cli.getColorScheme().errorText("[ERROR] ") + ex.getMessage()
-                    + ". See usage information below.\n");
-
-            cli.usage(cli.getOut());
-        }
-
-        return cli.getCommandSpec().exitCodeOnInvalidInput();
-    }
-}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/HelpFactoryImpl.java b/modules/cli/src/main/java/org/apache/ignite/cli/HelpFactoryImpl.java
deleted file mode 100644
index e7a774256..000000000
--- a/modules/cli/src/main/java/org/apache/ignite/cli/HelpFactoryImpl.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.cli;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import org.apache.ignite.cli.spec.SpecAdapter;
-import picocli.CommandLine;
-import picocli.CommandLine.Help.Ansi;
-import picocli.CommandLine.Help.ColorScheme;
-import picocli.CommandLine.Model.OptionSpec;
-import picocli.CommandLine.Model.PositionalParamSpec;
-
-/**
- * Implementation of Picocli factory for help message formatting.
- */
-public class HelpFactoryImpl implements CommandLine.IHelpFactory {
-    /** Section key banner. */
-    public static final String SECTION_KEY_BANNER = "banner";
-
-    /** Section key parameter option table. */
-    public static final String SECTION_KEY_PARAMETER_OPTION_TABLE = "paramsOptsTable";
-
-    /** {@inheritDoc} */
-    @Override
-    public CommandLine.Help create(CommandLine.Model.CommandSpec cmdSpec, ColorScheme cs) {
-        boolean hasCommands = !cmdSpec.subcommands().isEmpty();
-        boolean hasOptions = cmdSpec.options().stream().anyMatch(o -> !o.hidden());
-        boolean hasParameters = cmdSpec.positionalParameters().stream().anyMatch(o -> !o.hidden());
-
-        // Any command can have either subcommands or options/parameters, but not both.
-        assert !(hasCommands && (hasOptions || hasParameters));
-
-        cmdSpec.usageMessage().sectionKeys(Arrays.asList(
-                SECTION_KEY_BANNER,
-                CommandLine.Model.UsageMessageSpec.SECTION_KEY_SYNOPSIS,
-                CommandLine.Model.UsageMessageSpec.SECTION_KEY_DESCRIPTION,
-                CommandLine.Model.UsageMessageSpec.SECTION_KEY_COMMAND_LIST,
-                SECTION_KEY_PARAMETER_OPTION_TABLE
-        ));
-
-        var sectionMap = new HashMap<String, CommandLine.IHelpSectionRenderer>();
-
-        if (cmdSpec.commandLine().isUsageHelpRequested()) {
-            sectionMap.put(SECTION_KEY_BANNER,
-                    help -> {
-                        assert help.commandSpec().commandLine().getCommand() instanceof SpecAdapter;
-
-                        return ((SpecAdapter) help.commandSpec().commandLine().getCommand()).banner();
-                    }
-            );
-        }
-
-        if (!hasCommands) {
-            sectionMap.put(CommandLine.Model.UsageMessageSpec.SECTION_KEY_SYNOPSIS,
-                    help -> {
-                        StringBuilder sb = new StringBuilder();
-
-                        List<Ansi.IStyle> boldCmdStyle = new ArrayList<>(cs.commandStyles());
-
-                        boldCmdStyle.add(Ansi.Style.bold);
-
-                        sb.append(cs.apply(help.commandSpec().qualifiedName(), boldCmdStyle));
-
-                        if (hasOptions) {
-                            sb.append(cs.optionText(" [OPTIONS]"));
-                        }
-
-                        if (hasParameters) {
-                            for (PositionalParamSpec parameter : cmdSpec.positionalParameters()) {
-                                sb.append(' ').append(cs.parameterText(parameter.paramLabel()));
-                            }
-                        }
-
-                        sb.append("\n\n");
-
-                        return sb.toString();
-                    }
-            );
-        }
-
-        sectionMap.put(CommandLine.Model.UsageMessageSpec.SECTION_KEY_DESCRIPTION,
-                help -> Ansi.AUTO.string(help.description() + '\n'));
-
-        if (hasCommands) {
-            sectionMap.put(CommandLine.Model.UsageMessageSpec.SECTION_KEY_COMMAND_LIST, help -> {
-                Table tbl = new Table(0, cs);
-
-                tbl.addSection("@|bold COMMANDS|@");
-
-                for (Map.Entry<String, CommandLine.Help> entry : help.subcommands().entrySet()) {
-                    String name = entry.getKey();
-
-                    CommandLine.Help cmd = entry.getValue();
-
-                    if (cmd.subcommands().isEmpty()) {
-                        tbl.addRow(cs.commandText(name), cmd.description().trim());
-                    } else {
-                        for (Map.Entry<String, CommandLine.Help> subEntry : cmd.subcommands().entrySet()) {
-                            String subName = subEntry.getKey();
-
-                            CommandLine.Help subCmd = subEntry.getValue();
-
-                            // Further hierarchy is prohibited.
-                            assert subCmd.subcommands().isEmpty();
-
-                            tbl.addRow(cs.commandText(name + " " + subName), subCmd.description().trim());
-                        }
-                    }
-                }
-
-                return tbl.toString() + "\n";
-            });
-        } else if (hasParameters || hasOptions) {
-            sectionMap.put(SECTION_KEY_PARAMETER_OPTION_TABLE, help -> {
-                Table tbl = new Table(0, cs);
-
-                if (hasParameters) {
-                    tbl.addSection("@|bold REQUIRED PARAMETERS|@");
-
-                    for (PositionalParamSpec param : help.commandSpec().positionalParameters()) {
-                        if (!param.hidden()) {
-                            // TODO: IGNITE-14022 Support multiple-line descriptions.
-                            assert param.description().length == 1;
-
-                            tbl.addRow(cs.parameterText(param.paramLabel()), param.description()[0]);
-                        }
-                    }
-                }
-
-                if (hasOptions) {
-                    tbl.addSection("@|bold OPTIONS|@");
-
-                    for (OptionSpec option : help.commandSpec().options()) {
-                        if (!option.hidden()) {
-                            // TODO: IGNITE-14022 Support multiple names.
-                            assert option.names().length == 1;
-                            // TODO: IGNITE-14022 Support multiple-line descriptions.
-                            assert option.description().length == 1;
-
-                            tbl.addRow(
-                                    cs.optionText(option.names()[0] + '=' + option.paramLabel()),
-                                    option.description()[0]);
-                        }
-                    }
-                }
-
-                return tbl.toString() + "\n";
-            });
-        }
-
-        cmdSpec.usageMessage().sectionMap(sectionMap);
-
-        return new CommandLine.Help(cmdSpec, cs);
-    }
-}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/IgniteCliApp.java b/modules/cli/src/main/java/org/apache/ignite/cli/IgniteCliApp.java
deleted file mode 100644
index f041da619..000000000
--- a/modules/cli/src/main/java/org/apache/ignite/cli/IgniteCliApp.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.cli;
-
-import io.micronaut.context.ApplicationContext;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
-import org.apache.ignite.cli.spec.IgniteCliSpec;
-import org.fusesource.jansi.AnsiConsole;
-
-/**
- * Entry point of Ignite CLI.
- */
-public class IgniteCliApp {
-    /**
-     * Entry point of Ignite CLI.
-     *
-     * @param args Command line arguments.
-     */
-    public static void main(String... args) {
-        initJavaLoggerProps();
-
-        ApplicationContext applicationCtx = ApplicationContext.run();
-
-        int exitCode;
-
-        try {
-            AnsiConsole.systemInstall();
-
-            exitCode = IgniteCliSpec.initCli(applicationCtx).execute(args);
-        } finally {
-            AnsiConsole.systemUninstall();
-        }
-
-        System.exit(exitCode);
-    }
-
-    /**
-     * This is a temporary solution to hide unnecessary java util logs that are produced by ivy. ConsoleHandler.level should be set to
-     * SEVERE.
-     * TODO: https://issues.apache.org/jira/browse/IGNITE-15713
-     */
-    private static void initJavaLoggerProps() {
-        InputStream propsFile = IgniteCliApp.class.getResourceAsStream("/cli.java.util.logging.properties");
-
-        Path props = null;
-
-        try {
-            props = Files.createTempFile("cli.java.util.logging.properties", "");
-
-            if (propsFile != null) {
-                Files.copy(propsFile, props.toAbsolutePath(), StandardCopyOption.REPLACE_EXISTING);
-            }
-        } catch (IOException ignored) {
-            // No-op.
-        }
-
-        if (props != null) {
-            System.setProperty("java.util.logging.config.file", props.toString());
-        }
-    }
-}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/InteractiveWrapper.java b/modules/cli/src/main/java/org/apache/ignite/cli/InteractiveWrapper.java
deleted file mode 100644
index c1335cae3..000000000
--- a/modules/cli/src/main/java/org/apache/ignite/cli/InteractiveWrapper.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.cli;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import org.jline.console.SystemRegistry;
-import org.jline.console.impl.SystemRegistryImpl;
-import org.jline.keymap.KeyMap;
-import org.jline.reader.Binding;
-import org.jline.reader.EndOfFileException;
-import org.jline.reader.LineReader;
-import org.jline.reader.LineReaderBuilder;
-import org.jline.reader.MaskingCallback;
-import org.jline.reader.Parser;
-import org.jline.reader.Reference;
-import org.jline.reader.UserInterruptException;
-import org.jline.reader.impl.DefaultParser;
-import org.jline.terminal.Terminal;
-import org.jline.widget.TailTipWidgets;
-import picocli.CommandLine;
-import picocli.shell.jline3.PicocliCommands;
-
-/**
- * Interactive shell mode for Ignite CLI.
- */
-public class InteractiveWrapper {
-    /** System terminal instance. */
-    private final Terminal terminal;
-
-    /**
-     * Creates a new instance of {@code InteractiveWrapper} for the given {@code terminal}.
-     *
-     * @param terminal Terminal.
-     */
-    public InteractiveWrapper(Terminal terminal) {
-        this.terminal = terminal;
-    }
-
-    /**
-     * Starts interactive shell.
-     *
-     * @param cmd Prepared CommandLine instance to use for interactive mode.
-     */
-    public void run(CommandLine cmd) {
-        PicocliCommands picocliCommands = new PicocliCommands(workDir(), cmd) {
-            @Override
-            public Object invoke(CommandSession ses, String cmd, Object... args) throws Exception {
-                return execute(ses, cmd, (String[]) args);
-            }
-        };
-
-        Parser parser = new DefaultParser();
-
-        SystemRegistry sysRegistry = new SystemRegistryImpl(parser, terminal, InteractiveWrapper::workDir, null);
-        sysRegistry.setCommandRegistries(picocliCommands);
-
-        LineReader reader = LineReaderBuilder.builder()
-                .terminal(terminal)
-                .completer(sysRegistry.completer())
-                .parser(parser)
-                .variable(LineReader.LIST_MAX, 50)   // max tab completion candidates
-                .build();
-
-        TailTipWidgets widgets = new TailTipWidgets(reader, sysRegistry::commandDescription, 5, TailTipWidgets.TipType.COMPLETER);
-        widgets.enable();
-
-        KeyMap<Binding> keyMap = reader.getKeyMaps().get("main");
-        keyMap.bind(new Reference("tailtip-toggle"), KeyMap.alt("s"));
-
-        String prompt = "ignite> ";
-        String rightPrompt = null;
-
-        String line;
-        while (true) {
-            try {
-                sysRegistry.cleanUp();
-
-                line = reader.readLine(prompt, rightPrompt, (MaskingCallback) null, null);
-
-                sysRegistry.execute(line);
-            } catch (UserInterruptException ignored) {
-                // Ignore
-            } catch (EndOfFileException e) {
-                return;
-            } catch (Exception e) {
-                sysRegistry.trace(e);
-            }
-        }
-    }
-
-    /**
-     * Returns a path to the user directory.
-     * This is the directory where JVM was run from.
-     *
-     * @return Path to the user directory.
-     */
-    private static Path workDir() {
-        return Paths.get(System.getProperty("user.dir"));
-    }
-}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/Main.java b/modules/cli/src/main/java/org/apache/ignite/cli/Main.java
new file mode 100644
index 000000000..ca443970d
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/Main.java
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli;
+
+import io.micronaut.configuration.picocli.MicronautFactory;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.stream.Collectors;
+import org.apache.ignite.cli.commands.TopLevelCliCommand;
+import org.apache.ignite.cli.commands.TopLevelCliReplCommand;
+import org.apache.ignite.cli.config.Config;
+import org.apache.ignite.cli.core.call.CallExecutionPipeline;
+import org.apache.ignite.cli.core.call.StringCallInput;
+import org.apache.ignite.cli.core.exception.handler.DefaultExceptionHandlers;
+import org.apache.ignite.cli.core.exception.handler.PicocliExecutionExceptionHandler;
+import org.apache.ignite.cli.core.repl.Repl;
+import org.apache.ignite.cli.core.repl.SessionDefaultValueProvider;
+import org.apache.ignite.cli.core.repl.executor.ReplExecutorProvider;
+import org.apache.ignite.cli.core.repl.prompt.PromptProvider;
+import org.fusesource.jansi.AnsiConsole;
+import picocli.CommandLine;
+import picocli.CommandLine.Help.Ansi;
+
+
+/**
+ * Ignite cli entry point.
+ */
+public class Main {
+    /**
+     * Entry point.
+     *
+     * @param args ignore.
+     */
+    public static void main(String[] args) {
+        initJavaLoggerProps();
+
+        try (MicronautFactory micronautFactory = new MicronautFactory()) {
+            AnsiConsole.systemInstall();
+            if (args.length != 0 || !isatty()) { // do not enter REPL if input or output is redirected
+                try {
+                    executeCommand(args, micronautFactory);
+                } catch (Exception e) {
+                    System.err.println("Error occurred during command execution");
+                }
+            } else {
+                try {
+                    enterRepl(micronautFactory);
+                } catch (Exception e) {
+                    System.err.println("Error occurred during REPL initialization");
+                }
+            }
+        } finally {
+            AnsiConsole.systemUninstall();
+        }
+    }
+
+    private static boolean isatty() {
+        return System.console() != null;
+    }
+
+    private static void enterRepl(MicronautFactory micronautFactory) throws Exception {
+        ReplExecutorProvider replExecutorProvider = micronautFactory.create(ReplExecutorProvider.class);
+        replExecutorProvider.injectFactory(micronautFactory);
+        HashMap<String, String> aliases = new HashMap<>();
+        aliases.put("zle", "widget");
+        aliases.put("bindkey", "keymap");
+
+        SessionDefaultValueProvider defaultValueProvider = micronautFactory.create(SessionDefaultValueProvider.class);
+
+        VersionProvider versionProvider = micronautFactory.create(VersionProvider.class);
+        System.out.println(banner(versionProvider));
+
+        replExecutorProvider.get().execute(Repl.builder()
+                .withPromptProvider(micronautFactory.create(PromptProvider.class))
+                .withAliases(aliases)
+                .withCommandClass(TopLevelCliReplCommand.class)
+                .withDefaultValueProvider(defaultValueProvider)
+                .withCallExecutionPipelineProvider((executor, exceptionHandlers, line) ->
+                        CallExecutionPipeline.builder(executor)
+                                .inputProvider(() -> new StringCallInput(line))
+                                .output(System.out)
+                                .errOutput(System.err)
+                                .exceptionHandlers(new DefaultExceptionHandlers())
+                                .exceptionHandlers(exceptionHandlers)
+                                .build())
+                .withHistoryFileName("history")
+                .withTailTipWidgets()
+                .build());
+    }
+
+    private static void executeCommand(String[] args, MicronautFactory micronautFactory) throws Exception {
+        CommandLine cmd = new CommandLine(TopLevelCliCommand.class, micronautFactory);
+        cmd.setExecutionExceptionHandler(new PicocliExecutionExceptionHandler());
+        Config config = micronautFactory.create(Config.class);
+        cmd.setDefaultValueProvider(new CommandLine.PropertiesDefaultProvider(config.getProperties()));
+        cmd.execute(args);
+    }
+
+    private static final String[] BANNER = new String[]{
+            "",
+            "  @|red,bold          #|@              ___                         __",
+            "  @|red,bold        ###|@             /   |   ____   ____ _ _____ / /_   ___",
+            "  @|red,bold    #  #####|@           / /| |  / __ \\ / __ `// ___// __ \\ / _ \\",
+            "  @|red,bold  ###  ######|@         / ___ | / /_/ // /_/ // /__ / / / // ___/",
+            "  @|red,bold #####  #######|@      /_/  |_|/ .___/ \\__,_/ \\___//_/ /_/ \\___/",
+            "  @|red,bold #######  ######|@            /_/",
+            "  @|red,bold   ########  ####|@        ____               _  __           @|red,bold _____|@",
+            "  @|red,bold  #  ########  ##|@       /  _/____ _ ____   (_)/ /_ ___     @|red,bold |__  /|@",
+            "  @|red,bold ####  #######  #|@       / / / __ `// __ \\ / // __// _ \\     @|red,bold /_ <|@",
+            "  @|red,bold  #####  #####|@        _/ / / /_/ // / / // // /_ / ___/   @|red,bold ___/ /|@",
+            "  @|red,bold    ####  ##|@         /___/ \\__, //_/ /_//_/ \\__/ \\___/   @|red,bold /____/|@",
+            "  @|red,bold      ##|@                  /____/\n"
+    };
+
+    private static String banner(VersionProvider versionProvider) {
+        String banner = Arrays
+                .stream(BANNER)
+                .map(Ansi.AUTO::string)
+                .collect(Collectors.joining("\n"));
+
+        return '\n' + banner + '\n' + " ".repeat(22) + versionProvider.getVersion()[0] + "\n\n";
+    }
+
+    /**
+     * This is a temporary solution to hide unnecessary java util logs that are produced by ivy. ConsoleHandler.level should be set to
+     * SEVERE.
+     * TODO: https://issues.apache.org/jira/browse/IGNITE-15713
+     */
+    @Deprecated
+    private static void initJavaLoggerProps() {
+        InputStream propsFile = Main.class.getResourceAsStream("/cli.java.util.logging.properties");
+
+        Path props = null;
+
+        try {
+            props = Files.createTempFile("cli.java.util.logging.properties", "");
+
+            if (propsFile != null) {
+                Files.copy(propsFile, props.toAbsolutePath(), StandardCopyOption.REPLACE_EXISTING);
+            }
+        } catch (IOException ignored) {
+            // No-op.
+        }
+
+        if (props != null) {
+            System.setProperty("java.util.logging.config.file", props.toString());
+        }
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java b/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
index 9099a22db..adf175ab9 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
@@ -32,6 +32,13 @@ public class VersionProvider implements CommandLine.IVersionProvider {
     /** Actual Ignite CLI version info. */
     private final CliVersionInfo cliVerInfo;
 
+    /**
+     * Default constructor needed for bash-autocompletion.
+     */
+    public VersionProvider() {
+        cliVerInfo = null;
+    }
+
     /**
      * Creates version provider.
      *
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/ConfigurationClient.java b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/ConfigurationClient.java
deleted file mode 100644
index e08962082..000000000
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/ConfigurationClient.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.cli.builtins.config;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.typesafe.config.ConfigFactory;
-import com.typesafe.config.ConfigRenderOptions;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.net.HttpURLConnection;
-import java.net.URI;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import org.apache.ignite.cli.IgniteCliException;
-import org.jetbrains.annotations.Nullable;
-import picocli.CommandLine.Help.ColorScheme;
-
-/**
- * Client to get/put HOCON based configuration from/to Ignite server nodes.
- */
-@Singleton
-public class ConfigurationClient {
-    /** Url for getting configuration from REST endpoint of the node. */
-    private static final String GET_URL = "/management/v1/configuration/";
-
-    /** Url for patching configuration with REST endpoint of the node. */
-    private static final String PATCH_URL = "/management/v1/configuration/";
-
-    /** Http client. */
-    private final HttpClient httpClient;
-
-    /** Mapper serialize/deserialize json values during communication with node REST endpoint. */
-    private final ObjectMapper mapper;
-
-    /**
-     * Creates new configuration client.
-     *
-     * @param httpClient Http client.
-     */
-    @Inject
-    public ConfigurationClient(HttpClient httpClient) {
-        this.httpClient = httpClient;
-        mapper = new ObjectMapper();
-    }
-
-    /**
-     * Gets server node configuration as a raw JSON string.
-     *
-     * @param host         String representation of server node host.
-     * @param port         Host REST port.
-     * @param rawHoconPath HOCON dot-delimited path of requested configuration.
-     * @param type         Configuration type: {@code node} or {@code cluster}.
-     * @return JSON string with node configuration.
-     */
-    //TODO: Fix in https://issues.apache.org/jira/browse/IGNITE-15306
-    public String get(
-            String host,
-            int port,
-            @Nullable String rawHoconPath,
-            String type
-    ) {
-        var req = HttpRequest
-                .newBuilder()
-                .header("Content-Type", "text/plain");
-
-        if (rawHoconPath == null) {
-            req.uri(URI.create("http://" + host + ":" + port + GET_URL + type + "/"));
-        } else {
-            req.uri(URI.create("http://" + host + ":" + port + GET_URL + type + "/" + rawHoconPath));
-        }
-
-        try {
-            HttpResponse<String> res =
-                    httpClient.send(req.build(),
-                            HttpResponse.BodyHandlers.ofString());
-
-            if (res.statusCode() == HttpURLConnection.HTTP_OK) {
-                return mapper.writerWithDefaultPrettyPrinter()
-                        .writeValueAsString(mapper.readValue(res.body(), JsonNode.class));
-            } else {
-                throw error("Can't get configuration", res);
-            }
-        } catch (IOException | InterruptedException e) {
-            throw new IgniteCliException("Connection issues while trying to send http request");
-        }
-    }
-
-    /**
-     * Sets node configuration from JSON string with configs.
-     *
-     * @param host         String representation of server node host.
-     * @param port         Host REST port.
-     * @param rawHoconData Valid HOCON represented as a string.
-     * @param out          PrintWriter for printing user messages.
-     * @param cs           ColorScheme to enrich user messages.
-     * @param type         Configuration type: {@code node} or {@code cluster}.
-     */
-    //TODO: Fix in https://issues.apache.org/jira/browse/IGNITE-15306
-    public void set(String host, int port, String rawHoconData, PrintWriter out, ColorScheme cs, String type) {
-        var req = HttpRequest
-                .newBuilder()
-                .method("PATCH", HttpRequest.BodyPublishers.ofString(renderJsonFromHocon(rawHoconData)))
-                .header("Content-Type", "text/plain")
-                .uri(URI.create("http://" + host + ":" + port + PATCH_URL + type + "/"))
-                .build();
-
-        try {
-            HttpResponse<String> res = httpClient.send(req, HttpResponse.BodyHandlers.ofString());
-
-            if (res.statusCode() == HttpURLConnection.HTTP_OK) {
-                out.println("Configuration was updated successfully.");
-                out.println();
-                out.println("Use the " + cs.commandText("ignite config get")
-                        + " command to view the updated configuration.");
-            } else {
-                throw error("Failed to set configuration", res);
-            }
-        } catch (IOException | InterruptedException e) {
-            throw new IgniteCliException("Connection issues while trying to send http request", e);
-        }
-    }
-
-    /**
-     * Prepares exception with message, enriched by HTTP response details.
-     *
-     * @param msg Base error message.
-     * @param res Http response, which cause the raising exce[tion.
-     * @return Exception with detailed message.
-     * @throws JsonProcessingException if response has incorrect error format.
-     */
-    private IgniteCliException error(String msg, HttpResponse<String> res) throws JsonProcessingException {
-        var errorMsg = mapper.writerWithDefaultPrettyPrinter()
-                .writeValueAsString(mapper.readValue(res.body(), JsonNode.class));
-
-        return new IgniteCliException(msg + System.lineSeparator().repeat(2) + errorMsg);
-    }
-
-    /**
-     * Produces JSON representation of any valid HOCON string.
-     *
-     * @param rawHoconData HOCON string.
-     * @return JSON representation of HOCON string.
-     */
-    private static String renderJsonFromHocon(String rawHoconData) {
-        return ConfigFactory.parseString(rawHoconData)
-                .root().render(ConfigRenderOptions.concise());
-    }
-}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/HttpClientFactory.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigCall.java
similarity index 60%
copy from modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/HttpClientFactory.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigCall.java
index 178725423..fb7a4810e 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/HttpClientFactory.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigCall.java
@@ -15,27 +15,25 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.builtins.config;
+package org.apache.ignite.cli.call.cliconfig;
 
-import io.micronaut.context.annotation.Factory;
+import jakarta.inject.Inject;
 import jakarta.inject.Singleton;
-import java.net.http.HttpClient;
+import org.apache.ignite.cli.config.Config;
+import org.apache.ignite.cli.core.call.Call;
+import org.apache.ignite.cli.core.call.DefaultCallOutput;
+import org.apache.ignite.cli.core.call.EmptyCallInput;
 
 /**
- * Factory for producing simple HTTP clients.
+ * Gets entire CLI configuration.
  */
-@Factory
-public class HttpClientFactory {
-    /**
-     * Creates new HTTP client.
-     *
-     * @return HttpClient
-     */
-    @Singleton
-    HttpClient httpClient() {
-        return HttpClient
-                .newBuilder()
-                .version(HttpClient.Version.HTTP_1_1)
-                .build();
+@Singleton
+public class CliConfigCall implements Call<EmptyCallInput, Config> {
+    @Inject
+    private Config config;
+
+    @Override
+    public DefaultCallOutput<Config> execute(EmptyCallInput input) {
+        return DefaultCallOutput.success(config);
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigGetCall.java
similarity index 57%
copy from modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigGetCall.java
index 9099a22db..3b72ceb8e 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigGetCall.java
@@ -15,36 +15,27 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.call.cliconfig;
 
-import io.micronaut.core.annotation.Introspected;
 import jakarta.inject.Inject;
 import jakarta.inject.Singleton;
-import picocli.CommandLine;
+import org.apache.ignite.cli.config.Config;
+import org.apache.ignite.cli.core.call.Call;
+import org.apache.ignite.cli.core.call.DefaultCallOutput;
+import org.apache.ignite.cli.core.call.StringCallInput;
 
 /**
- * Version provider for Picocli interactions.
+ * Gets CLI configuration parameter.
  */
 @Singleton
-@Introspected
-public class VersionProvider implements CommandLine.IVersionProvider {
-
-    /** Actual Ignite CLI version info. */
-    private final CliVersionInfo cliVerInfo;
-
-    /**
-     * Creates version provider.
-     *
-     * @param cliVerInfo Actual Ignite CLI version container.
-     */
+public class CliConfigGetCall implements Call<StringCallInput, String> {
     @Inject
-    public VersionProvider(CliVersionInfo cliVerInfo) {
-        this.cliVerInfo = cliVerInfo;
-    }
+    private Config config;
 
-    /** {@inheritDoc} */
     @Override
-    public String[] getVersion() {
-        return new String[]{"Apache Ignite CLI ver. " + cliVerInfo.ver};
+    public DefaultCallOutput<String> execute(StringCallInput input) {
+        String key = input.getString();
+        String property = config.getProperty(key, "");
+        return DefaultCallOutput.success(property);
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigSetCall.java
similarity index 57%
copy from modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigSetCall.java
index 9099a22db..b2c5a3647 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigSetCall.java
@@ -15,36 +15,30 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.call.cliconfig;
 
-import io.micronaut.core.annotation.Introspected;
 import jakarta.inject.Inject;
 import jakarta.inject.Singleton;
-import picocli.CommandLine;
+import java.util.Map.Entry;
+import org.apache.ignite.cli.config.Config;
+import org.apache.ignite.cli.core.call.Call;
+import org.apache.ignite.cli.core.call.DefaultCallOutput;
 
 /**
- * Version provider for Picocli interactions.
+ * Sets CLI configuration parameters.
  */
 @Singleton
-@Introspected
-public class VersionProvider implements CommandLine.IVersionProvider {
-
-    /** Actual Ignite CLI version info. */
-    private final CliVersionInfo cliVerInfo;
-
-    /**
-     * Creates version provider.
-     *
-     * @param cliVerInfo Actual Ignite CLI version container.
-     */
+public class CliConfigSetCall implements Call<CliConfigSetCallInput, Object> {
     @Inject
-    public VersionProvider(CliVersionInfo cliVerInfo) {
-        this.cliVerInfo = cliVerInfo;
-    }
+    private Config config;
 
-    /** {@inheritDoc} */
     @Override
-    public String[] getVersion() {
-        return new String[]{"Apache Ignite CLI ver. " + cliVerInfo.ver};
+    public DefaultCallOutput<Object> execute(CliConfigSetCallInput input) {
+        for (Entry<String, String> entry : input.getParameters().entrySet()) {
+            config.setProperty(entry.getKey(), entry.getValue());
+        }
+        config.saveConfig();
+
+        return DefaultCallOutput.empty();
     }
 }
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigSetCallInput.java
similarity index 64%
copy from modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigSetCallInput.java
index 32fbdbfdb..9e8ac8683 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigSetCallInput.java
@@ -15,22 +15,22 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.call.cliconfig;
 
-import java.util.logging.Handler;
-import java.util.logging.LogRecord;
+import java.util.Map;
+import org.apache.ignite.cli.core.call.CallInput;
 
 /**
- * Adapter for {@link Handler} with empty implementations of all methods but {@link Handler#publish(LogRecord)}.
+ * Input for {@link CliConfigSetCall}.
  */
-public abstract class NoOpHandler extends Handler {
-    @Override
-    public void flush() {
-        // no-op
+public class CliConfigSetCallInput implements CallInput {
+    private final Map<String, String> parameters;
+
+    public CliConfigSetCallInput(Map<String, String> parameters) {
+        this.parameters = parameters;
     }
 
-    @Override
-    public void close() throws SecurityException {
-        // no-op
+    public Map<String, String> getParameters() {
+        return parameters;
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/ClusterConfigShowCall.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/ClusterConfigShowCall.java
new file mode 100644
index 000000000..28cc526c5
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/ClusterConfigShowCall.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.call.configuration;
+
+import jakarta.inject.Singleton;
+import org.apache.ignite.cli.core.call.Call;
+import org.apache.ignite.cli.core.call.DefaultCallOutput;
+import org.apache.ignite.cli.core.exception.CommandExecutionException;
+import org.apache.ignite.rest.client.api.ClusterConfigurationApi;
+import org.apache.ignite.rest.client.invoker.ApiClient;
+import org.apache.ignite.rest.client.invoker.ApiException;
+import org.apache.ignite.rest.client.invoker.Configuration;
+
+/**
+ * Shows cluster configuration.
+ */
+@Singleton
+public class ClusterConfigShowCall implements Call<ClusterConfigShowCallInput, String> {
+
+    /** {@inheritDoc} */
+    @Override
+    public DefaultCallOutput<String> execute(ClusterConfigShowCallInput clusterConfigShowCallInput) {
+        ClusterConfigurationApi client = createApiClient(clusterConfigShowCallInput);
+
+        try {
+            return DefaultCallOutput.success(readClusterConfig(client, clusterConfigShowCallInput));
+        } catch (ApiException e) {
+            return DefaultCallOutput.failure(e);
+        } catch (IllegalArgumentException e) {
+            return DefaultCallOutput.failure(new CommandExecutionException("cluster config show", e.getMessage()));
+        }
+    }
+
+    private String readClusterConfig(ClusterConfigurationApi api, ClusterConfigShowCallInput input) throws ApiException {
+        return input.getSelector() != null ? api.getClusterConfigurationByPath(input.getSelector()) : api.getClusterConfiguration();
+    }
+
+    private ClusterConfigurationApi createApiClient(ClusterConfigShowCallInput input) {
+        ApiClient client = Configuration.getDefaultApiClient();
+        client.setBasePath(input.getClusterUrl());
+        return new ClusterConfigurationApi(client);
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/ClusterConfigShowCallInput.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/ClusterConfigShowCallInput.java
new file mode 100644
index 000000000..87ba85633
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/ClusterConfigShowCallInput.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.call.configuration;
+
+import org.apache.ignite.cli.core.call.CallInput;
+
+/**
+ * Input for {@link ClusterConfigShowCall}.
+ */
+public class ClusterConfigShowCallInput implements CallInput {
+    /**
+     * Selector for configuration tree.
+     */
+    private final String selector;
+
+    /**
+     * Cluster url.
+     */
+    private final String clusterUrl;
+
+    private ClusterConfigShowCallInput(String selector, String clusterUrl) {
+        this.selector = selector;
+        this.clusterUrl = clusterUrl;
+    }
+
+    /**
+     * Builder for {@link ClusterConfigShowCallInput}.
+     */
+    public static ShowConfigurationCallInputBuilder builder() {
+        return new ShowConfigurationCallInputBuilder();
+    }
+
+    /**
+     * Get selector.
+     *
+     * @return Selector for configuration tree.
+     */
+    public String getSelector() {
+        return selector;
+    }
+
+    /**
+     * Get cluster URL.
+     *
+     * @return Cluster URL.
+     */
+    public String getClusterUrl() {
+        return clusterUrl;
+    }
+
+    /**
+     * Builder for {@link ClusterConfigShowCallInput}.
+     */
+    public static class ShowConfigurationCallInputBuilder {
+        private String selector;
+        private String clusterUrl;
+
+        public ShowConfigurationCallInputBuilder selector(String selector) {
+            this.selector = selector;
+            return this;
+        }
+
+        public ShowConfigurationCallInputBuilder clusterUrl(String clusterUrl) {
+            this.clusterUrl = clusterUrl;
+            return this;
+        }
+
+        public ClusterConfigShowCallInput build() {
+            return new ClusterConfigShowCallInput(selector, clusterUrl);
+        }
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/ClusterConfigUpdateCall.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/ClusterConfigUpdateCall.java
new file mode 100644
index 000000000..6a19a6523
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/ClusterConfigUpdateCall.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.call.configuration;
+
+import jakarta.inject.Singleton;
+import org.apache.ignite.cli.core.call.Call;
+import org.apache.ignite.cli.core.call.DefaultCallOutput;
+import org.apache.ignite.cli.core.exception.CommandExecutionException;
+import org.apache.ignite.rest.client.api.ClusterConfigurationApi;
+import org.apache.ignite.rest.client.invoker.ApiClient;
+import org.apache.ignite.rest.client.invoker.ApiException;
+import org.apache.ignite.rest.client.invoker.Configuration;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Updates cluster configuration.
+ */
+@Singleton
+public class ClusterConfigUpdateCall implements Call<ClusterConfigUpdateCallInput, String> {
+    /** {@inheritDoc} */
+    @Override
+    public DefaultCallOutput<String> execute(ClusterConfigUpdateCallInput clusterConfigUpdateCallInput) {
+        ClusterConfigurationApi client = createApiClient(clusterConfigUpdateCallInput);
+
+        try {
+            return updateClusterConfig(client, clusterConfigUpdateCallInput);
+        } catch (ApiException e) {
+            if (e.getCode() == 400) {
+                return DefaultCallOutput.failure(
+                        new CommandExecutionException(
+                                "cluster config update",
+                                "Got error while updating the cluster configuration. " + System.lineSeparator()
+                                        + "Code:  " + e.getCode() + ", response:  " + e.getResponseBody()
+                        ));
+            }
+            return DefaultCallOutput.failure(new CommandExecutionException("cluster config update", "Ignite api return " + e.getCode()));
+        }
+    }
+
+    private DefaultCallOutput<String> updateClusterConfig(ClusterConfigurationApi api, ClusterConfigUpdateCallInput input)
+            throws ApiException {
+        api.updateClusterConfiguration(input.getConfig());
+        return DefaultCallOutput.success("Cluster configuration was updated successfully.");
+    }
+
+    @NotNull
+    private ClusterConfigurationApi createApiClient(ClusterConfigUpdateCallInput input) {
+        ApiClient client = Configuration.getDefaultApiClient();
+        client.setBasePath(input.getClusterUrl());
+        return new ClusterConfigurationApi(client);
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/ClusterConfigUpdateCallInput.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/ClusterConfigUpdateCallInput.java
new file mode 100644
index 000000000..d0250cd53
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/ClusterConfigUpdateCallInput.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.call.configuration;
+
+import org.apache.ignite.cli.core.call.CallInput;
+
+/**
+ * Input for {@link NodeConfigUpdateCall}.
+ */
+public class ClusterConfigUpdateCallInput implements CallInput {
+    /**
+     * Configuration to update.
+     */
+    private final String config;
+
+    /**
+     * Cluster url.
+     */
+    private final String clusterUrl;
+
+    private ClusterConfigUpdateCallInput(String config, String clusterUrl) {
+        this.config = config;
+        this.clusterUrl = clusterUrl;
+    }
+
+    /**
+     * Builder method.
+     *
+     * @return Builder for {@link ClusterConfigUpdateCallInput}.
+     */
+    public static UpdateConfigurationCallInputBuilder builder() {
+        return new UpdateConfigurationCallInputBuilder();
+    }
+
+    /**
+     * Get configuration.
+     *
+     * @return Configuration to update.
+     */
+    public String getConfig() {
+        return config;
+    }
+
+    /**
+     * Get cluster URL.
+     *
+     * @return Cluster url.
+     */
+    public String getClusterUrl() {
+        return clusterUrl;
+    }
+
+    /**
+     * Builder for {@link ClusterConfigUpdateCallInput}.
+     */
+    public static class UpdateConfigurationCallInputBuilder {
+
+        private String config;
+
+        private String clusterUrl;
+
+        public UpdateConfigurationCallInputBuilder config(String config) {
+            this.config = config;
+            return this;
+        }
+
+        public UpdateConfigurationCallInputBuilder clusterUrl(String clusterUrl) {
+            this.clusterUrl = clusterUrl;
+            return this;
+        }
+
+        public ClusterConfigUpdateCallInput build() {
+            return new ClusterConfigUpdateCallInput(config, clusterUrl);
+        }
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/NodeConfigShowCall.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/NodeConfigShowCall.java
new file mode 100644
index 000000000..b3f7e8fa8
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/NodeConfigShowCall.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.call.configuration;
+
+import jakarta.inject.Singleton;
+import org.apache.ignite.cli.core.call.Call;
+import org.apache.ignite.cli.core.call.DefaultCallOutput;
+import org.apache.ignite.cli.core.exception.CommandExecutionException;
+import org.apache.ignite.rest.client.api.NodeConfigurationApi;
+import org.apache.ignite.rest.client.invoker.ApiClient;
+import org.apache.ignite.rest.client.invoker.ApiException;
+import org.apache.ignite.rest.client.invoker.Configuration;
+
+/**
+ * Shows node configuration from ignite cluster.
+ */
+@Singleton
+public class NodeConfigShowCall implements Call<NodeConfigShowCallInput, String> {
+
+    /** {@inheritDoc} */
+    @Override
+    public DefaultCallOutput<String> execute(NodeConfigShowCallInput readConfigurationInput) {
+        NodeConfigurationApi client = createApiClient(readConfigurationInput);
+
+        try {
+            return DefaultCallOutput.success(readNodeConfig(client, readConfigurationInput));
+        } catch (ApiException e) {
+            return DefaultCallOutput.failure(e);
+        } catch (IllegalArgumentException e) {
+            return DefaultCallOutput.failure(new CommandExecutionException("node config show", e.getMessage()));
+        }
+    }
+
+    private String readNodeConfig(NodeConfigurationApi api, NodeConfigShowCallInput input) throws ApiException {
+        return input.getSelector() != null ? api.getNodeConfigurationByPath(input.getSelector()) : api.getNodeConfiguration();
+    }
+
+    private NodeConfigurationApi createApiClient(NodeConfigShowCallInput input) {
+        ApiClient client = Configuration.getDefaultApiClient();
+        client.setBasePath(input.getNodeUrl());
+        return new NodeConfigurationApi(client);
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/NodeConfigShowCallInput.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/NodeConfigShowCallInput.java
new file mode 100644
index 000000000..c3b83acd4
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/NodeConfigShowCallInput.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.call.configuration;
+
+import org.apache.ignite.cli.core.call.CallInput;
+
+/**
+ * Input for {@link NodeConfigShowCall}.
+ */
+public class NodeConfigShowCallInput implements CallInput {
+    /**
+     * Selector for configuration tree.
+     */
+    private final String selector;
+
+    /**
+     * Node url.
+     */
+    private final String nodeUrl;
+
+    private NodeConfigShowCallInput(String selector, String nodeUrl) {
+        this.nodeUrl = nodeUrl;
+        this.selector = selector;
+    }
+
+    /**
+     * Builder for {@link NodeConfigShowCallInput}.
+     */
+    public static ShowConfigurationCallInputBuilder builder() {
+        return new ShowConfigurationCallInputBuilder();
+    }
+
+    /**
+     * Get selector.
+     *
+     * @return Selector for configuration tree.
+     */
+    public String getSelector() {
+        return selector;
+    }
+
+    /**
+     * Get node URL.
+     *
+     * @return Cluster URL.
+     */
+    public String getNodeUrl() {
+        return nodeUrl;
+    }
+
+    /**
+     * Builder for {@link NodeConfigShowCallInput}.
+     */
+    public static class ShowConfigurationCallInputBuilder {
+
+        private String selector;
+
+        private String nodeUrl;
+
+        public ShowConfigurationCallInputBuilder selector(String selector) {
+            this.selector = selector;
+            return this;
+        }
+
+        public ShowConfigurationCallInputBuilder nodeUrl(String nodeUrl) {
+            this.nodeUrl = nodeUrl;
+            return this;
+        }
+
+        public NodeConfigShowCallInput build() {
+            return new NodeConfigShowCallInput(selector, nodeUrl);
+        }
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/NodeConfigUpdateCall.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/NodeConfigUpdateCall.java
new file mode 100644
index 000000000..c073e5310
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/NodeConfigUpdateCall.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.call.configuration;
+
+import jakarta.inject.Singleton;
+import org.apache.ignite.cli.core.call.Call;
+import org.apache.ignite.cli.core.call.DefaultCallOutput;
+import org.apache.ignite.cli.core.exception.CommandExecutionException;
+import org.apache.ignite.rest.client.api.NodeConfigurationApi;
+import org.apache.ignite.rest.client.invoker.ApiClient;
+import org.apache.ignite.rest.client.invoker.ApiException;
+import org.apache.ignite.rest.client.invoker.Configuration;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Updates configuration for node.
+ */
+@Singleton
+public class NodeConfigUpdateCall implements Call<NodeConfigUpdateCallInput, String> {
+    /** {@inheritDoc} */
+    @Override
+    public DefaultCallOutput<String> execute(NodeConfigUpdateCallInput nodeConfigUpdateCallInput) {
+        NodeConfigurationApi client = createApiClient(nodeConfigUpdateCallInput);
+
+        try {
+            return updateNodeConfig(client, nodeConfigUpdateCallInput);
+        } catch (ApiException e) {
+            if (e.getCode() == 400) {
+                return DefaultCallOutput.failure(
+                        new CommandExecutionException(
+                                "node config update",
+                                "Got error while updating the node configuration. " + System.lineSeparator()
+                                        + "Code:  " + e.getCode() + ", response:  " + e.getResponseBody()
+                        ));
+            }
+            return DefaultCallOutput.failure(new CommandExecutionException("node config update", "Ignite api return " + e.getCode()));
+        }
+    }
+
+    private DefaultCallOutput<String> updateNodeConfig(NodeConfigurationApi api, NodeConfigUpdateCallInput input)
+            throws ApiException {
+        api.updateNodeConfiguration(input.getConfig());
+        return DefaultCallOutput.success("Node configuration was updated successfully.");
+    }
+
+    @NotNull
+    private NodeConfigurationApi createApiClient(NodeConfigUpdateCallInput input) {
+        ApiClient client = Configuration.getDefaultApiClient();
+        client.setBasePath(input.getNodeUrl());
+        return new NodeConfigurationApi(client);
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/NodeConfigUpdateCallInput.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/NodeConfigUpdateCallInput.java
new file mode 100644
index 000000000..dae58ac93
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/NodeConfigUpdateCallInput.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.call.configuration;
+
+import org.apache.ignite.cli.core.call.CallInput;
+
+/**
+ * Input for {@link NodeConfigUpdateCall}.
+ */
+public class NodeConfigUpdateCallInput implements CallInput {
+    /**
+     * Configuration to update.
+     */
+    private final String config;
+
+    /**
+     * Cluster url.
+     */
+    private final String nodeUrl;
+
+    private NodeConfigUpdateCallInput(String config, String clusterUrl) {
+        this.config = config;
+        this.nodeUrl = clusterUrl;
+    }
+
+    /**
+     * Builder method.
+     *
+     * @return Builder for {@link NodeConfigUpdateCallInput}.
+     */
+    public static UpdateConfigurationCallInputBuilder builder() {
+        return new UpdateConfigurationCallInputBuilder();
+    }
+
+    /**
+     * Get configuration.
+     *
+     * @return Configuration to update.
+     */
+    public String getConfig() {
+        return config;
+    }
+
+    /**
+     * Get cluster URL.
+     *
+     * @return Cluster url.
+     */
+    public String getNodeUrl() {
+        return nodeUrl;
+    }
+
+    /**
+     * Builder for {@link NodeConfigUpdateCallInput}.
+     */
+    public static class UpdateConfigurationCallInputBuilder {
+
+        private String config;
+
+        private String nodeUrl;
+
+        public UpdateConfigurationCallInputBuilder config(String config) {
+            this.config = config;
+            return this;
+        }
+
+        public UpdateConfigurationCallInputBuilder nodeUrl(String clusterUrl) {
+            this.nodeUrl = clusterUrl;
+            return this;
+        }
+
+        public NodeConfigUpdateCallInput build() {
+            return new NodeConfigUpdateCallInput(config, nodeUrl);
+        }
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/connect/ConnectCall.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/connect/ConnectCall.java
new file mode 100644
index 000000000..270fb5cc8
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/connect/ConnectCall.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.call.connect;
+
+import com.google.gson.Gson;
+import jakarta.inject.Singleton;
+import java.net.ConnectException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import org.apache.ignite.cli.core.call.Call;
+import org.apache.ignite.cli.core.call.CallOutput;
+import org.apache.ignite.cli.core.call.DefaultCallOutput;
+import org.apache.ignite.cli.core.exception.ConnectCommandException;
+import org.apache.ignite.cli.core.repl.Session;
+import org.apache.ignite.cli.core.repl.config.RootConfig;
+import org.apache.ignite.rest.client.api.NodeConfigurationApi;
+import org.apache.ignite.rest.client.invoker.ApiClient;
+import org.apache.ignite.rest.client.invoker.ApiException;
+import org.apache.ignite.rest.client.invoker.Configuration;
+import org.jetbrains.annotations.NotNull;
+
+
+/**
+ * Call for connect to Ignite 3 node. As a result {@link Session} will hold a valid node-url.
+ */
+@Singleton
+public class ConnectCall implements Call<ConnectCallInput, String> {
+
+    private final Session session;
+
+    public ConnectCall(Session session) {
+        this.session = session;
+    }
+
+    @Override
+    public CallOutput<String> execute(ConnectCallInput input) {
+        NodeConfigurationApi api = createApiClient(input);
+        String nodeUrl = input.getNodeUrl();
+        try {
+            String configuration = api.getNodeConfiguration();
+            setJdbcUrl(configuration, nodeUrl);
+        } catch (ApiException e) {
+            session.setConnectedToNode(false);
+            if (e.getCause() instanceof ConnectException) {
+                return DefaultCallOutput.failure(new ConnectCommandException("Can not connect to " + input.getNodeUrl()));
+            }
+            return DefaultCallOutput.failure(e);
+        }
+
+        session.setNodeUrl(nodeUrl);
+        session.setConnectedToNode(true);
+        return DefaultCallOutput.success("Connected to " + nodeUrl);
+    }
+
+    @NotNull
+    private NodeConfigurationApi createApiClient(ConnectCallInput input) {
+        ApiClient client = Configuration.getDefaultApiClient();
+        client.setBasePath(input.getNodeUrl());
+        return new NodeConfigurationApi(client);
+    }
+
+    private void setJdbcUrl(String configuration, String nodeUrl) {
+        try {
+            String host = new URL(nodeUrl).getHost();
+            RootConfig config = new Gson().fromJson(configuration, RootConfig.class);
+            session.setJdbcUrl("jdbc:ignite:thin://" + host + ":" + config.clientConnector.port);
+        } catch (MalformedURLException ignored) {
+            // Shouldn't happen ever since we are now connected to this URL
+        }
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/connect/ConnectCallInput.java
similarity index 50%
copy from modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/call/connect/ConnectCallInput.java
index 9099a22db..6f22136ba 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/connect/ConnectCallInput.java
@@ -15,36 +15,41 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.call.connect;
 
-import io.micronaut.core.annotation.Introspected;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-import picocli.CommandLine;
+import org.apache.ignite.cli.core.call.CallInput;
 
 /**
- * Version provider for Picocli interactions.
+ * Input for connect call to Ignite 3 node.
  */
-@Singleton
-@Introspected
-public class VersionProvider implements CommandLine.IVersionProvider {
+public class ConnectCallInput implements CallInput {
+    private final String nodeUrl;
 
-    /** Actual Ignite CLI version info. */
-    private final CliVersionInfo cliVerInfo;
+    ConnectCallInput(String nodeUrl) {
+        this.nodeUrl = nodeUrl;
+    }
+
+    public String getNodeUrl() {
+        return nodeUrl;
+    }
+
+    public static ConnectCallInputBuilder builder() {
+        return new ConnectCallInputBuilder();
+    }
 
     /**
-     * Creates version provider.
-     *
-     * @param cliVerInfo Actual Ignite CLI version container.
+     * Builder for {@link ConnectCall}.
      */
-    @Inject
-    public VersionProvider(CliVersionInfo cliVerInfo) {
-        this.cliVerInfo = cliVerInfo;
-    }
+    public static class ConnectCallInputBuilder {
+        private String nodeUrl;
+
+        public ConnectCallInputBuilder nodeUrl(String nodeUrl) {
+            this.nodeUrl = nodeUrl;
+            return this;
+        }
 
-    /** {@inheritDoc} */
-    @Override
-    public String[] getVersion() {
-        return new String[]{"Apache Ignite CLI ver. " + cliVerInfo.ver};
+        public ConnectCallInput build() {
+            return new ConnectCallInput(nodeUrl);
+        }
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/connect/DisconnectCall.java
similarity index 51%
copy from modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/call/connect/DisconnectCall.java
index 9099a22db..4bb5b2d77 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/connect/DisconnectCall.java
@@ -15,36 +15,39 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.call.connect;
 
-import io.micronaut.core.annotation.Introspected;
 import jakarta.inject.Inject;
 import jakarta.inject.Singleton;
-import picocli.CommandLine;
+import org.apache.ignite.cli.core.call.Call;
+import org.apache.ignite.cli.core.call.CallOutput;
+import org.apache.ignite.cli.core.call.DefaultCallOutput;
+import org.apache.ignite.cli.core.call.EmptyCallInput;
+import org.apache.ignite.cli.core.repl.Session;
 
 /**
- * Version provider for Picocli interactions.
+ * Call for disconnect.
  */
 @Singleton
-@Introspected
-public class VersionProvider implements CommandLine.IVersionProvider {
+public class DisconnectCall implements Call<EmptyCallInput, String> {
 
-    /** Actual Ignite CLI version info. */
-    private final CliVersionInfo cliVerInfo;
-
-    /**
-     * Creates version provider.
-     *
-     * @param cliVerInfo Actual Ignite CLI version container.
-     */
     @Inject
-    public VersionProvider(CliVersionInfo cliVerInfo) {
-        this.cliVerInfo = cliVerInfo;
+    private final Session session;
+
+    public DisconnectCall(Session session) {
+        this.session = session;
     }
 
-    /** {@inheritDoc} */
     @Override
-    public String[] getVersion() {
-        return new String[]{"Apache Ignite CLI ver. " + cliVerInfo.ver};
+    public CallOutput<String> execute(EmptyCallInput input) {
+        if (session.isConnectedToNode()) {
+            String nodeUrl = session.getNodeUrl();
+            session.setNodeUrl(null);
+            session.setConnectedToNode(false);
+
+            return DefaultCallOutput.success("Disconnected from " + nodeUrl);
+        }
+
+        return DefaultCallOutput.empty();
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/status/Status.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/status/Status.java
new file mode 100644
index 000000000..45528ac41
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/status/Status.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.call.status;
+
+/**
+ * Class that represents the cluster status.
+ */
+public class Status {
+
+    private final int nodeCount;
+
+    private final boolean initialized;
+
+    private final String name;
+
+    private final boolean connected;
+
+    private final String connectedNodeUrl;
+
+    private Status(int nodeCount, boolean initialized, String name, boolean connected, String connectedNodeUrl) {
+        this.nodeCount = nodeCount;
+        this.initialized = initialized;
+        this.name = name;
+        this.connected = connected;
+        this.connectedNodeUrl = connectedNodeUrl;
+    }
+
+    public int getNodeCount() {
+        return nodeCount;
+    }
+
+    public boolean isInitialized() {
+        return initialized;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public boolean isConnected() {
+        return connected;
+    }
+
+    public String getConnectedNodeUrl() {
+        return connectedNodeUrl;
+    }
+
+    /**
+     * Builder for {@link Status}.
+     */
+    public static StatusBuilder builder() {
+        return new StatusBuilder();
+    }
+
+    /**
+     * Builder for {@link Status}.
+     */
+    public static class StatusBuilder {
+        private int nodeCount;
+
+        private boolean initialized;
+
+        private String name;
+
+        private boolean connected;
+
+        private String connectedNodeUrl;
+
+        private StatusBuilder() {
+
+        }
+
+        public StatusBuilder nodeCount(int nodeCount) {
+            this.nodeCount = nodeCount;
+            return this;
+        }
+
+        public StatusBuilder initialized(boolean initialized) {
+            this.initialized = initialized;
+            return this;
+        }
+
+        public StatusBuilder name(String name) {
+            this.name = name;
+            return this;
+        }
+
+        public StatusBuilder connected(boolean connected) {
+            this.connected = connected;
+            return this;
+        }
+
+        public StatusBuilder connectedNodeUrl(String connectedNodeUrl) {
+            this.connectedNodeUrl = connectedNodeUrl;
+            return this;
+        }
+
+        public Status build() {
+            return new Status(nodeCount, initialized, name, connected, connectedNodeUrl);
+        }
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/status/StatusCall.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/status/StatusCall.java
new file mode 100644
index 000000000..294d249f9
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/status/StatusCall.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.call.status;
+
+import jakarta.inject.Singleton;
+import java.net.ConnectException;
+import org.apache.ignite.cli.core.call.Call;
+import org.apache.ignite.cli.core.call.CallOutput;
+import org.apache.ignite.cli.core.call.DefaultCallOutput;
+import org.apache.ignite.cli.core.call.StatusCallInput;
+import org.apache.ignite.cli.core.exception.CommandExecutionException;
+import org.apache.ignite.cli.deprecated.CliPathsConfigLoader;
+import org.apache.ignite.cli.deprecated.IgnitePaths;
+import org.apache.ignite.cli.deprecated.builtins.node.NodeManager;
+import org.apache.ignite.rest.client.api.ClusterConfigurationApi;
+import org.apache.ignite.rest.client.api.NodeConfigurationApi;
+import org.apache.ignite.rest.client.invoker.ApiClient;
+import org.apache.ignite.rest.client.invoker.ApiException;
+import org.apache.ignite.rest.client.invoker.Configuration;
+
+/**
+ * Call to get cluster status.
+ */
+@Singleton
+//TODO: https://issues.apache.org/jira/browse/IGNITE-17093
+public class StatusCall implements Call<StatusCallInput, Status> {
+
+    private final NodeManager nodeManager;
+
+    private final CliPathsConfigLoader cliPathsCfgLdr;
+
+    /**
+     * Default constructor.
+     */
+    public StatusCall(NodeManager nodeManager, CliPathsConfigLoader cliPathsCfgLdr) {
+        this.nodeManager = nodeManager;
+        this.cliPathsCfgLdr = cliPathsCfgLdr;
+    }
+
+    @Override
+    public CallOutput<Status> execute(StatusCallInput input) {
+        IgnitePaths paths = cliPathsCfgLdr.loadIgnitePathsOrThrowError();
+        String connected = null;
+        try {
+            connected = createNodeApi(input).getNodeConfiguration();
+        } catch (ApiException e) {
+            if (e.getCause() instanceof ConnectException) {
+                return DefaultCallOutput.failure(new CommandExecutionException("status", "cannot connect to " + input.getClusterUrl()));
+            } else {
+                return DefaultCallOutput.failure(e);
+            }
+        }
+        return DefaultCallOutput.success(
+                Status.builder()
+                        .connected(connected != null)
+                        .connectedNodeUrl(input.getClusterUrl())
+                        .initialized(connected != null && canReadClusterConfig(input))
+                        .nodeCount(nodeManager.getRunningNodes(paths.logDir, paths.cliPidsDir()).size())
+                        .build()
+        );
+    }
+
+    private boolean canReadClusterConfig(StatusCallInput input) {
+        var clusterApi = createClusterApi(input);
+        try {
+            clusterApi.getClusterConfiguration();
+            return true;
+        } catch (ApiException e) {
+            return false;
+        }
+    }
+
+    private ClusterConfigurationApi createClusterApi(StatusCallInput input) {
+        ApiClient client = Configuration.getDefaultApiClient();
+        client.setBasePath(input.getClusterUrl());
+        return new ClusterConfigurationApi(client);
+    }
+
+    private NodeConfigurationApi createNodeApi(StatusCallInput input) {
+        ApiClient client = Configuration.getDefaultApiClient();
+        client.setBasePath(input.getClusterUrl());
+        return new NodeConfigurationApi(client);
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/status/StatusReplCall.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/status/StatusReplCall.java
new file mode 100644
index 000000000..a7c8babcf
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/status/StatusReplCall.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.call.status;
+
+import jakarta.inject.Singleton;
+import org.apache.ignite.cli.core.call.Call;
+import org.apache.ignite.cli.core.call.CallOutput;
+import org.apache.ignite.cli.core.call.DefaultCallOutput;
+import org.apache.ignite.cli.core.call.EmptyCallInput;
+import org.apache.ignite.cli.core.repl.Session;
+import org.apache.ignite.cli.deprecated.CliPathsConfigLoader;
+import org.apache.ignite.cli.deprecated.IgnitePaths;
+import org.apache.ignite.cli.deprecated.builtins.node.NodeManager;
+import org.apache.ignite.rest.client.api.ClusterConfigurationApi;
+import org.apache.ignite.rest.client.invoker.ApiClient;
+import org.apache.ignite.rest.client.invoker.ApiException;
+import org.apache.ignite.rest.client.invoker.Configuration;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Call to get cluster status.
+ */
+@Singleton
+//TODO: https://issues.apache.org/jira/browse/IGNITE-17093
+public class StatusReplCall implements Call<EmptyCallInput, Status> {
+
+    private final NodeManager nodeManager;
+
+    private final CliPathsConfigLoader cliPathsCfgLdr;
+
+    private final Session session;
+
+    /**
+     * Default constructor.
+     */
+    public StatusReplCall(NodeManager nodeManager, CliPathsConfigLoader cliPathsCfgLdr, Session session) {
+        this.nodeManager = nodeManager;
+        this.cliPathsCfgLdr = cliPathsCfgLdr;
+        this.session = session;
+    }
+
+    @Override
+    public CallOutput<Status> execute(EmptyCallInput input) {
+        IgnitePaths paths = cliPathsCfgLdr.loadIgnitePathsOrThrowError();
+        return DefaultCallOutput.success(
+                Status.builder()
+                        .connected(session.isConnectedToNode())
+                        .connectedNodeUrl(session.getNodeUrl())
+                        .initialized(session.isConnectedToNode() && canReadClusterConfig())
+                        .nodeCount(nodeManager.getRunningNodes(paths.logDir, paths.cliPidsDir()).size())
+                        .build()
+        );
+    }
+
+    private boolean canReadClusterConfig() {
+        var clusterApi = createApiClient();
+        try {
+            clusterApi.getClusterConfiguration();
+            return true;
+        } catch (ApiException e) {
+            return false;
+        }
+    }
+
+    @NotNull
+    private ClusterConfigurationApi createApiClient() {
+        ApiClient client = Configuration.getDefaultApiClient();
+        client.setBasePath(session.getNodeUrl());
+        return new ClusterConfigurationApi(client);
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/BaseCommand.java
similarity index 67%
copy from modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/BaseCommand.java
index 2e0050be3..46f8b4b26 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/BaseCommand.java
@@ -15,15 +15,24 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.spec;
+package org.apache.ignite.cli.commands;
 
-import picocli.CommandLine;
+import picocli.CommandLine.Model.CommandSpec;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.Spec;
 
 /**
- * Base class for commands without any subcommands.
+ * Base class for commands.
  */
-public abstract class CommandSpec extends SpecAdapter {
+public abstract class BaseCommand implements Runnable {
     /** Help option specification. */
-    @CommandLine.Option(names = "--help", usageHelp = true, hidden = true)
+    @Option(names = {"-h", "--help"}, usageHelp = true, description = "Show this help message and exit.")
     protected boolean usageHelpRequested;
+
+    @Spec
+    protected CommandSpec spec;
+
+    @Override
+    public void run() {
+    }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/TopLevelCliCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/TopLevelCliCommand.java
new file mode 100644
index 000000000..040523bee
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/TopLevelCliCommand.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands;
+
+import jakarta.inject.Singleton;
+import org.apache.ignite.cli.VersionProvider;
+import org.apache.ignite.cli.commands.cliconfig.CliCommand;
+import org.apache.ignite.cli.commands.configuration.cluster.ClusterCommand;
+import org.apache.ignite.cli.commands.configuration.node.NodeCommand;
+import org.apache.ignite.cli.commands.sql.SqlCommand;
+import org.apache.ignite.cli.commands.status.StatusCommand;
+import org.apache.ignite.cli.deprecated.spec.BootstrapIgniteCommandSpec;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+
+/**
+ * Top-level command that prints help and declares subcommands.
+ */
+@Command(name = "ignite",
+        versionProvider = VersionProvider.class,
+        description = {
+                "Welcome to Ignite Shell alpha.",
+                "Run without command to enter interactive mode.",
+                ""},
+        subcommands = {
+                SqlCommand.class,
+                CommandLine.HelpCommand.class,
+                StatusCommand.class,
+                CliCommand.class,
+                BootstrapIgniteCommandSpec.class,
+                NodeCommand.class,
+                ClusterCommand.class
+        })
+@Singleton
+public class TopLevelCliCommand extends BaseCommand {
+    @SuppressWarnings("PMD.UnusedPrivateField")
+    @Option(names = {"--version"}, versionHelp = true, description = "Print version information and exit")
+    private boolean versionRequested;
+
+    @Override
+    public void run() {
+
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/TopLevelCliReplCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/TopLevelCliReplCommand.java
new file mode 100644
index 000000000..94638253e
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/TopLevelCliReplCommand.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands;
+
+import jakarta.inject.Singleton;
+import org.apache.ignite.cli.commands.cliconfig.CliCommand;
+import org.apache.ignite.cli.commands.configuration.cluster.ClusterReplCommand;
+import org.apache.ignite.cli.commands.configuration.node.NodeReplCommand;
+import org.apache.ignite.cli.commands.connect.ConnectCommand;
+import org.apache.ignite.cli.commands.connect.DisconnectCommand;
+import org.apache.ignite.cli.commands.sql.SqlCommand;
+import org.apache.ignite.cli.commands.status.StatusReplCommand;
+import org.apache.ignite.cli.commands.version.VersionCommand;
+import org.apache.ignite.cli.deprecated.spec.BootstrapIgniteCommandSpec;
+import picocli.CommandLine;
+import picocli.shell.jline3.PicocliCommands;
+
+/**
+ * Top-level command that just prints help.
+ */
+@CommandLine.Command(name = "",
+        footer = {"", "Press Ctrl-D to exit."},
+        subcommands = {
+                SqlCommand.class,
+                PicocliCommands.ClearScreen.class,
+                CommandLine.HelpCommand.class,
+                VersionCommand.class,
+                StatusReplCommand.class,
+                CliCommand.class,
+                BootstrapIgniteCommandSpec.class,
+                ConnectCommand.class,
+                DisconnectCommand.class,
+                NodeReplCommand.class,
+                ClusterReplCommand.class
+        })
+@Singleton
+public class TopLevelCliReplCommand {
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliCommand.java
similarity index 65%
copy from modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliCommand.java
index 2e0050be3..da91cb11e 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliCommand.java
@@ -15,15 +15,20 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.spec;
+package org.apache.ignite.cli.commands.cliconfig;
 
-import picocli.CommandLine;
+import jakarta.inject.Singleton;
+import org.apache.ignite.cli.commands.BaseCommand;
+import picocli.CommandLine.Command;
 
 /**
- * Base class for commands without any subcommands.
+ * Parent command for CLI configuration commands.
  */
-public abstract class CommandSpec extends SpecAdapter {
-    /** Help option specification. */
-    @CommandLine.Option(names = "--help", usageHelp = true, hidden = true)
-    protected boolean usageHelpRequested;
+@Command(name = "cli",
+        description = "CLI specific commands",
+        subcommands = {
+                CliConfigSubCommand.class
+        })
+@Singleton
+public class CliCommand extends BaseCommand {
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigGetSubCommand.java
similarity index 50%
copy from modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigGetSubCommand.java
index 9099a22db..7f33bfdd5 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigGetSubCommand.java
@@ -15,36 +15,34 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.commands.cliconfig;
 
-import io.micronaut.core.annotation.Introspected;
 import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-import picocli.CommandLine;
+import org.apache.ignite.cli.call.cliconfig.CliConfigGetCall;
+import org.apache.ignite.cli.commands.BaseCommand;
+import org.apache.ignite.cli.core.call.CallExecutionPipeline;
+import org.apache.ignite.cli.core.call.StringCallInput;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Parameters;
 
 /**
- * Version provider for Picocli interactions.
+ * Command to get CLI configuration parameters.
  */
-@Singleton
-@Introspected
-public class VersionProvider implements CommandLine.IVersionProvider {
+@Command(name = "get")
+public class CliConfigGetSubCommand extends BaseCommand {
+    @Parameters
+    private String key;
 
-    /** Actual Ignite CLI version info. */
-    private final CliVersionInfo cliVerInfo;
-
-    /**
-     * Creates version provider.
-     *
-     * @param cliVerInfo Actual Ignite CLI version container.
-     */
     @Inject
-    public VersionProvider(CliVersionInfo cliVerInfo) {
-        this.cliVerInfo = cliVerInfo;
-    }
+    private CliConfigGetCall call;
 
-    /** {@inheritDoc} */
     @Override
-    public String[] getVersion() {
-        return new String[]{"Apache Ignite CLI ver. " + cliVerInfo.ver};
+    public void run() {
+        CallExecutionPipeline.builder(call)
+                .inputProvider(() -> new StringCallInput(key))
+                .output(spec.commandLine().getOut())
+                .errOutput(spec.commandLine().getErr())
+                .build()
+                .runPipeline();
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSetSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSetSubCommand.java
new file mode 100644
index 000000000..137a41fd2
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSetSubCommand.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands.cliconfig;
+
+import jakarta.inject.Inject;
+import java.util.Map;
+import org.apache.ignite.cli.call.cliconfig.CliConfigSetCall;
+import org.apache.ignite.cli.call.cliconfig.CliConfigSetCallInput;
+import org.apache.ignite.cli.commands.BaseCommand;
+import org.apache.ignite.cli.core.call.CallExecutionPipeline;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Parameters;
+
+/**
+ * Command to set CLI configuration parameters.
+ */
+@Command(name = "set")
+public class CliConfigSetSubCommand extends BaseCommand {
+    @Parameters(arity = "1..*")
+    private Map<String, String> parameters;
+
+    @Inject
+    private CliConfigSetCall call;
+
+    @Override
+    public void run() {
+        CallExecutionPipeline.builder(call)
+                .inputProvider(() -> new CliConfigSetCallInput(parameters))
+                .output(spec.commandLine().getOut())
+                .errOutput(spec.commandLine().getErr())
+                .build()
+                .runPipeline();
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSubCommand.java
new file mode 100644
index 000000000..095518504
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSubCommand.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands.cliconfig;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import org.apache.ignite.cli.call.cliconfig.CliConfigCall;
+import org.apache.ignite.cli.commands.BaseCommand;
+import org.apache.ignite.cli.commands.decorators.ConfigDecorator;
+import org.apache.ignite.cli.core.call.CallExecutionPipeline;
+import org.apache.ignite.cli.core.call.EmptyCallInput;
+import picocli.CommandLine.Command;
+
+/**
+ * Parent command for CLI configuration commands.
+ */
+@Command(name = "config", subcommands = {
+        CliConfigGetSubCommand.class,
+        CliConfigSetSubCommand.class
+})
+@Singleton
+public class CliConfigSubCommand extends BaseCommand {
+
+    @Inject
+    private CliConfigCall call;
+
+    @Override
+    public void run() {
+        CallExecutionPipeline.builder(call)
+                .inputProvider(EmptyCallInput::new)
+                .output(spec.commandLine().getOut())
+                .errOutput(spec.commandLine().getErr())
+                .decorator(new ConfigDecorator())
+                .build()
+                .runPipeline();
+    }
+}
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/cluster/ClusterCommand.java
similarity index 65%
copy from modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/cluster/ClusterCommand.java
index 32fbdbfdb..5b4d0e19c 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/cluster/ClusterCommand.java
@@ -15,22 +15,20 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.commands.configuration.cluster;
 
-import java.util.logging.Handler;
-import java.util.logging.LogRecord;
+import org.apache.ignite.cli.deprecated.spec.ClusterCommandSpec;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Mixin;
 
 /**
- * Adapter for {@link Handler} with empty implementations of all methods but {@link Handler#publish(LogRecord)}.
+ * Node command.
  */
-public abstract class NoOpHandler extends Handler {
-    @Override
-    public void flush() {
-        // no-op
-    }
+@Command(name = "cluster",
+        subcommands = {ClusterConfigSubCommand.class},
+        description = "Cluster config operations.")
+public class ClusterCommand {
 
-    @Override
-    public void close() throws SecurityException {
-        // no-op
-    }
+    @Mixin
+    ClusterCommandSpec clusterCommandSpec;
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/cluster/ClusterConfigReplSubCommand.java
similarity index 69%
copy from modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/cluster/ClusterConfigReplSubCommand.java
index 2e0050be3..c8d639a92 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/cluster/ClusterConfigReplSubCommand.java
@@ -15,15 +15,16 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.spec;
+package org.apache.ignite.cli.commands.configuration.cluster;
 
-import picocli.CommandLine;
+import picocli.CommandLine.Command;
 
 /**
- * Base class for commands without any subcommands.
+ * Node config command in REPL.
  */
-public abstract class CommandSpec extends SpecAdapter {
-    /** Help option specification. */
-    @CommandLine.Option(names = "--help", usageHelp = true, hidden = true)
-    protected boolean usageHelpRequested;
+@Command(name = "config",
+        subcommands = {ClusterConfigShowReplSubCommand.class, ClusterConfigUpdateReplSubCommand.class},
+        description = "Cluster config operations.")
+public class ClusterConfigReplSubCommand {
+
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/cluster/ClusterConfigShowReplSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/cluster/ClusterConfigShowReplSubCommand.java
new file mode 100644
index 000000000..65fd49b95
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/cluster/ClusterConfigShowReplSubCommand.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands.configuration.cluster;
+
+import jakarta.inject.Inject;
+import org.apache.ignite.cli.call.configuration.ClusterConfigShowCall;
+import org.apache.ignite.cli.call.configuration.ClusterConfigShowCallInput;
+import org.apache.ignite.cli.commands.BaseCommand;
+import org.apache.ignite.cli.commands.decorators.JsonDecorator;
+import org.apache.ignite.cli.core.call.CallExecutionPipeline;
+import org.apache.ignite.cli.core.exception.ExceptionHandler;
+import org.apache.ignite.cli.core.exception.ExceptionWriter;
+import org.apache.ignite.cli.core.repl.Session;
+import org.apache.ignite.rest.client.invoker.ApiException;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+
+/**
+ * Command that shows configuration from the cluster in REPL mode.
+ */
+@Command(name = "show",
+        description = "Shows cluster configuration.")
+public class ClusterConfigShowReplSubCommand extends BaseCommand {
+
+    /**
+     * Configuration selector option.
+     */
+    @Option(names = {"--selector"}, description = "Configuration path selector.")
+    private String selector;
+
+    /**
+     * Node url option.
+     */
+    @Option(
+            names = {"--cluster-url"}, description = "Url to Ignite node."
+    )
+    private String clusterUrl;
+
+    @Inject
+    private ClusterConfigShowCall call;
+
+    @Inject
+    private Session session;
+
+    /** {@inheritDoc} */
+    @Override
+    public void run() {
+        var input = ClusterConfigShowCallInput.builder().selector(selector);
+        if (session.isConnectedToNode()) {
+            input.clusterUrl(session.getNodeUrl());
+        } else if (clusterUrl != null) {
+            input.clusterUrl(clusterUrl);
+        } else {
+            spec.commandLine().getErr().println("You are not connected to node. Run 'connect' command or use '--cluster-url' option.");
+            return;
+        }
+
+        CallExecutionPipeline.builder(call)
+                .inputProvider(input::build)
+                .output(spec.commandLine().getOut())
+                .errOutput(spec.commandLine().getErr())
+                .decorator(new JsonDecorator())
+                .exceptionHandler(new ShowConfigReplExceptionHandler())
+                .build()
+                .runPipeline();
+    }
+
+    private static class ShowConfigReplExceptionHandler implements ExceptionHandler<ApiException> {
+        @Override
+        public void handle(ExceptionWriter err, ApiException e) {
+            if (e.getCode() == 500) { //TODO: https://issues.apache.org/jira/browse/IGNITE-17091
+                err.write("Cannot show cluster config, probably you have not initialized the cluster. "
+                        + "Try to run 'cluster init' command.");
+                return;
+            }
+
+            err.write(e.getResponseBody());
+        }
+
+        @Override
+        public Class<ApiException> applicableException() {
+            return ApiException.class;
+        }
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/cluster/ClusterConfigShowSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/cluster/ClusterConfigShowSubCommand.java
new file mode 100644
index 000000000..29548c47f
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/cluster/ClusterConfigShowSubCommand.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands.configuration.cluster;
+
+import jakarta.inject.Inject;
+import org.apache.ignite.cli.call.configuration.ClusterConfigShowCall;
+import org.apache.ignite.cli.call.configuration.ClusterConfigShowCallInput;
+import org.apache.ignite.cli.commands.BaseCommand;
+import org.apache.ignite.cli.commands.decorators.JsonDecorator;
+import org.apache.ignite.cli.core.call.CallExecutionPipeline;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+
+/**
+ * Command that shows configuration from the cluster.
+ */
+@Command(name = "show",
+        description = "Shows cluster configuration.")
+public class ClusterConfigShowSubCommand extends BaseCommand {
+
+    /**
+     * Configuration selector option.
+     */
+    @Option(names = {"--selector"}, description = "Configuration path selector.")
+    private String selector;
+
+    /**
+     * Node url option.
+     */
+    @Option(
+            names = {"--cluster-url"}, description = "Url to ignite node.",
+            descriptionKey = "ignite.cluster-url", defaultValue = "http://localhost:10300"
+    )
+    private String clusterUrl;
+
+    @Inject
+    private ClusterConfigShowCall call;
+
+    /** {@inheritDoc} */
+    @Override
+    public void run() {
+        CallExecutionPipeline.builder(call)
+                .inputProvider(this::buildCallInput)
+                .output(spec.commandLine().getOut())
+                .errOutput(spec.commandLine().getErr())
+                .decorator(new JsonDecorator())
+                .build()
+                .runPipeline();
+    }
+
+    private ClusterConfigShowCallInput buildCallInput() {
+        return ClusterConfigShowCallInput.builder()
+                .clusterUrl(clusterUrl)
+                .selector(selector)
+                .build();
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/cluster/ClusterConfigSubCommand.java
similarity index 70%
copy from modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/cluster/ClusterConfigSubCommand.java
index 2e0050be3..4eaaa56e7 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/cluster/ClusterConfigSubCommand.java
@@ -15,15 +15,16 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.spec;
+package org.apache.ignite.cli.commands.configuration.cluster;
 
-import picocli.CommandLine;
+import picocli.CommandLine.Command;
 
 /**
- * Base class for commands without any subcommands.
+ * Node config command.
  */
-public abstract class CommandSpec extends SpecAdapter {
-    /** Help option specification. */
-    @CommandLine.Option(names = "--help", usageHelp = true, hidden = true)
-    protected boolean usageHelpRequested;
+@Command(name = "config",
+        subcommands = {ClusterConfigShowSubCommand.class, ClusterConfigUpdateSubCommand.class},
+        description = "Cluster config operations.")
+public class ClusterConfigSubCommand {
+
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/cluster/ClusterConfigUpdateReplSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/cluster/ClusterConfigUpdateReplSubCommand.java
new file mode 100644
index 000000000..91ada9d0e
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/cluster/ClusterConfigUpdateReplSubCommand.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands.configuration.cluster;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import org.apache.ignite.cli.call.configuration.ClusterConfigUpdateCall;
+import org.apache.ignite.cli.call.configuration.ClusterConfigUpdateCallInput;
+import org.apache.ignite.cli.commands.BaseCommand;
+import org.apache.ignite.cli.core.call.CallExecutionPipeline;
+import org.apache.ignite.cli.core.repl.Session;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.Parameters;
+
+/**
+ * Command that updates cluster configuration in REPL mode.
+ */
+@Command(name = "update",
+        description = "Updates cluster configuration.")
+@Singleton
+public class ClusterConfigUpdateReplSubCommand extends BaseCommand {
+    /**
+     * Cluster url option.
+     */
+    @Option(
+            names = {"--cluster-url"}, description = "Url to Ignite node.",
+            descriptionKey = "ignite.cluster-url", defaultValue = "http://localhost:10300"
+    )
+    private String clusterUrl;
+
+    /**
+     * Configuration that will be updated.
+     */
+    @Parameters(index = "0")
+    private String config;
+
+    @Inject
+    ClusterConfigUpdateCall call;
+
+    @Inject
+    private Session session;
+
+    /** {@inheritDoc} */
+    @Override
+    public void run() {
+        var input = ClusterConfigUpdateCallInput.builder().config(config);
+        if (session.isConnectedToNode()) {
+            input.clusterUrl(session.getNodeUrl());
+        } else if (clusterUrl != null) {
+            input.clusterUrl(clusterUrl);
+        } else {
+            spec.commandLine().getErr().println("You are not connected to node. Run 'connect' command or use '--cluster-url' option.");
+            return;
+        }
+
+        CallExecutionPipeline.builder(this.call)
+                .inputProvider(input::build)
+                .output(spec.commandLine().getOut())
+                .errOutput(spec.commandLine().getErr())
+                .build()
+                .runPipeline();
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/cluster/ClusterConfigUpdateSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/cluster/ClusterConfigUpdateSubCommand.java
new file mode 100644
index 000000000..12aafaf0e
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/cluster/ClusterConfigUpdateSubCommand.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands.configuration.cluster;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import org.apache.ignite.cli.call.configuration.ClusterConfigUpdateCall;
+import org.apache.ignite.cli.call.configuration.ClusterConfigUpdateCallInput;
+import org.apache.ignite.cli.commands.BaseCommand;
+import org.apache.ignite.cli.core.call.CallExecutionPipeline;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.Parameters;
+
+/**
+ * Command that updates cluster configuration.
+ */
+@Command(name = "update",
+        description = "Updates cluster configuration.")
+@Singleton
+public class ClusterConfigUpdateSubCommand extends BaseCommand {
+    @Inject
+    ClusterConfigUpdateCall call;
+
+    /**
+     * Cluster url option.
+     */
+    @Option(
+            names = {"--cluster-url"}, description = "Url to Ignite node.",
+            descriptionKey = "ignite.cluster-url", defaultValue = "http://localhost:10300"
+    )
+    private String clusterUrl;
+
+    /**
+     * Configuration that will be updated.
+     */
+    @Parameters(index = "0")
+    private String config;
+
+    /** {@inheritDoc} */
+    @Override
+    public void run() {
+        CallExecutionPipeline.builder(call)
+                .inputProvider(this::buildCallInput)
+                .output(spec.commandLine().getOut())
+                .errOutput(spec.commandLine().getErr())
+                .build()
+                .runPipeline();
+    }
+
+    private ClusterConfigUpdateCallInput buildCallInput() {
+        return ClusterConfigUpdateCallInput.builder()
+                .clusterUrl(clusterUrl)
+                .config(config)
+                .build();
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/cluster/ClusterReplCommand.java
similarity index 63%
copy from modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/cluster/ClusterReplCommand.java
index 2e0050be3..9b0e8d1ac 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/cluster/ClusterReplCommand.java
@@ -15,15 +15,19 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.spec;
+package org.apache.ignite.cli.commands.configuration.cluster;
 
-import picocli.CommandLine;
+import org.apache.ignite.cli.deprecated.spec.ClusterReplCommandSpec;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Mixin;
 
 /**
- * Base class for commands without any subcommands.
+ * Cluster command in REPL mode.
  */
-public abstract class CommandSpec extends SpecAdapter {
-    /** Help option specification. */
-    @CommandLine.Option(names = "--help", usageHelp = true, hidden = true)
-    protected boolean usageHelpRequested;
+@Command(name = "cluster",
+        subcommands = {ClusterConfigReplSubCommand.class},
+        description = "Cluster config operations.")
+public class ClusterReplCommand {
+    @Mixin
+    ClusterReplCommandSpec clusterReplCommandSpec;
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/node/NodeCommand.java
similarity index 66%
copy from modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/node/NodeCommand.java
index 2e0050be3..435214dfb 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/node/NodeCommand.java
@@ -15,15 +15,20 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.spec;
+package org.apache.ignite.cli.commands.configuration.node;
 
-import picocli.CommandLine;
+import org.apache.ignite.cli.deprecated.spec.NodeCommandSpec;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Mixin;
 
 /**
- * Base class for commands without any subcommands.
+ * Node command.
  */
-public abstract class CommandSpec extends SpecAdapter {
-    /** Help option specification. */
-    @CommandLine.Option(names = "--help", usageHelp = true, hidden = true)
-    protected boolean usageHelpRequested;
+@Command(name = "node",
+        subcommands = {NodeConfigSubCommand.class},
+        description = "Node config operations.")
+public class NodeCommand {
+
+    @Mixin
+    NodeCommandSpec nodeCommandSpec;
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/node/NodeConfigReplSubCommand.java
similarity index 70%
copy from modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/node/NodeConfigReplSubCommand.java
index 2e0050be3..ebbf3c78a 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/node/NodeConfigReplSubCommand.java
@@ -15,15 +15,16 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.spec;
+package org.apache.ignite.cli.commands.configuration.node;
 
-import picocli.CommandLine;
+import picocli.CommandLine.Command;
 
 /**
- * Base class for commands without any subcommands.
+ * Node config command in REPL mode.
  */
-public abstract class CommandSpec extends SpecAdapter {
-    /** Help option specification. */
-    @CommandLine.Option(names = "--help", usageHelp = true, hidden = true)
-    protected boolean usageHelpRequested;
+@Command(name = "config",
+        subcommands = {NodeConfigShowReplSubCommand.class, NodeConfigUpdateReplSubCommand.class},
+        description = "Node config operations.")
+public class NodeConfigReplSubCommand {
+
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/node/NodeConfigShowReplSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/node/NodeConfigShowReplSubCommand.java
new file mode 100644
index 000000000..8cb43510f
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/node/NodeConfigShowReplSubCommand.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands.configuration.node;
+
+import jakarta.inject.Inject;
+import org.apache.ignite.cli.call.configuration.NodeConfigShowCall;
+import org.apache.ignite.cli.call.configuration.NodeConfigShowCallInput;
+import org.apache.ignite.cli.commands.BaseCommand;
+import org.apache.ignite.cli.commands.decorators.JsonDecorator;
+import org.apache.ignite.cli.core.call.CallExecutionPipeline;
+import org.apache.ignite.cli.core.exception.ExceptionHandler;
+import org.apache.ignite.cli.core.exception.ExceptionWriter;
+import org.apache.ignite.cli.core.repl.Session;
+import org.apache.ignite.rest.client.invoker.ApiException;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+
+/**
+ * Command that shows node configuration from the cluster in REPL mode.
+ */
+@Command(name = "show",
+        description = "Shows node configuration.")
+public class NodeConfigShowReplSubCommand extends BaseCommand {
+
+    /**
+     * Configuration selector option.
+     */
+    @Option(names = {"--selector"}, description = "Configuration path selector.")
+    private String selector;
+
+    /**
+     * Node url option.
+     */
+    @Option(
+            names = {"--node-url"}, description = "Url to Ignite node."
+    )
+    private String nodeUrl;
+
+    @Inject
+    private NodeConfigShowCall call;
+
+    @Inject
+    private Session session;
+
+    /** {@inheritDoc} */
+    @Override
+    public void run() {
+        var input = NodeConfigShowCallInput.builder().selector(selector);
+        if (session.isConnectedToNode()) {
+            input.nodeUrl(session.getNodeUrl());
+        } else if (nodeUrl != null) {
+            input.nodeUrl(nodeUrl);
+        } else {
+            spec.commandLine().getErr().println("You are not connected to node. Run 'connect' command or use '--node-url' option.");
+            return;
+        }
+
+        CallExecutionPipeline.builder(call)
+                .inputProvider(input::build)
+                .output(spec.commandLine().getOut())
+                .errOutput(spec.commandLine().getErr())
+                .decorator(new JsonDecorator())
+                .exceptionHandler(new ShowConfigReplExceptionHandler())
+                .build()
+                .runPipeline();
+    }
+
+    private static class ShowConfigReplExceptionHandler implements ExceptionHandler<ApiException> {
+        @Override
+        public void handle(ExceptionWriter err, ApiException e) {
+            if (e.getCode() == 500) { //TODO: https://issues.apache.org/jira/browse/IGNITE-17091
+                err.write("Cannot show node config, probably you have not initialized the cluster. "
+                        + "Try to run 'cluster init' command.");
+                return;
+            }
+
+            err.write(e.getResponseBody());
+        }
+
+        @Override
+        public Class<ApiException> applicableException() {
+            return ApiException.class;
+        }
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/node/NodeConfigShowSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/node/NodeConfigShowSubCommand.java
new file mode 100644
index 000000000..56a8de470
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/node/NodeConfigShowSubCommand.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands.configuration.node;
+
+import jakarta.inject.Inject;
+import org.apache.ignite.cli.call.configuration.NodeConfigShowCall;
+import org.apache.ignite.cli.call.configuration.NodeConfigShowCallInput;
+import org.apache.ignite.cli.commands.BaseCommand;
+import org.apache.ignite.cli.commands.decorators.JsonDecorator;
+import org.apache.ignite.cli.core.call.CallExecutionPipeline;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+
+/**
+ * Command that shows configuration from the cluster.
+ */
+@Command(name = "show",
+        description = "Shows node configuration.")
+public class NodeConfigShowSubCommand extends BaseCommand {
+
+    /**
+     * Configuration selector option.
+     */
+    @Option(names = {"--selector"}, description = "Configuration path selector.")
+    private String selector;
+
+    /**
+     * Node url option.
+     */
+    @Option(
+            names = {"--node-url"}, description = "Url to ignite node.",
+            descriptionKey = "ignite.cluster-url", defaultValue = "http://localhost:10300"
+    )
+    private String nodeUrl;
+
+    @Inject
+    private NodeConfigShowCall call;
+
+    /** {@inheritDoc} */
+    @Override
+    public void run() {
+        CallExecutionPipeline.builder(call)
+                .inputProvider(this::buildCallInput)
+                .output(spec.commandLine().getOut())
+                .errOutput(spec.commandLine().getErr())
+                .decorator(new JsonDecorator())
+                .build()
+                .runPipeline();
+    }
+
+    private NodeConfigShowCallInput buildCallInput() {
+        return NodeConfigShowCallInput.builder()
+                .nodeUrl(nodeUrl)
+                .selector(selector)
+                .build();
+    }
+}
diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/AbstractCliTest.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/node/NodeConfigSubCommand.java
similarity index 71%
copy from modules/cli/src/test/java/org/apache/ignite/cli/AbstractCliTest.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/node/NodeConfigSubCommand.java
index 7387f57aa..8be82912f 100644
--- a/modules/cli/src/test/java/org/apache/ignite/cli/AbstractCliTest.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/node/NodeConfigSubCommand.java
@@ -15,19 +15,16 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.commands.configuration.node;
 
-import org.junit.jupiter.api.BeforeAll;
+import picocli.CommandLine.Command;
 
 /**
- * Base class for any CLI tests.
+ * Node config command.
  */
-public abstract class AbstractCliTest {
-    /**
-     * Sets up a dumb terminal before tests.
-     */
-    @BeforeAll
-    static void beforeAll() {
-        System.setProperty("org.jline.terminal.dumb", "true");
-    }
+@Command(name = "config",
+        subcommands = {NodeConfigShowSubCommand.class, NodeConfigUpdateSubCommand.class},
+        description = "Node config operations.")
+public class NodeConfigSubCommand {
+
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/node/NodeConfigUpdateReplSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/node/NodeConfigUpdateReplSubCommand.java
new file mode 100644
index 000000000..6d3116084
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/node/NodeConfigUpdateReplSubCommand.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands.configuration.node;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import org.apache.ignite.cli.call.configuration.NodeConfigUpdateCall;
+import org.apache.ignite.cli.call.configuration.NodeConfigUpdateCallInput;
+import org.apache.ignite.cli.commands.BaseCommand;
+import org.apache.ignite.cli.core.call.CallExecutionPipeline;
+import org.apache.ignite.cli.core.repl.Session;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.Parameters;
+
+/**
+ * Command that updates configuration in REPL mode.
+ */
+@Command(name = "update",
+        description = "Updates node configuration.")
+@Singleton
+public class NodeConfigUpdateReplSubCommand extends BaseCommand {
+    /**
+     * Node url option.
+     */
+    @Option(
+            names = {"--node-url"}, description = "Url to Ignite node.",
+            descriptionKey = "ignite.cluster-url", defaultValue = "http://localhost:10300"
+    )
+    private String nodeUrl;
+
+    /**
+     * Configuration that will be updated.
+     */
+    @Parameters(index = "0")
+    private String config;
+
+    @Inject
+    NodeConfigUpdateCall call;
+
+    @Inject
+    private Session session;
+
+    /** {@inheritDoc} */
+    @Override
+    public void run() {
+        var input = NodeConfigUpdateCallInput.builder().config(config);
+        if (session.isConnectedToNode()) {
+            input.nodeUrl(session.getNodeUrl());
+        } else if (nodeUrl != null) {
+            input.nodeUrl(nodeUrl);
+        } else {
+            spec.commandLine().getErr().println("You are not connected to node. Run 'connect' command or use '--cluster-url' option.");
+            return;
+        }
+
+        CallExecutionPipeline.builder(this.call)
+                .inputProvider(input::build)
+                .output(spec.commandLine().getOut())
+                .errOutput(spec.commandLine().getErr())
+                .build()
+                .runPipeline();
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/node/NodeConfigUpdateSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/node/NodeConfigUpdateSubCommand.java
new file mode 100644
index 000000000..08a3125e5
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/node/NodeConfigUpdateSubCommand.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands.configuration.node;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import org.apache.ignite.cli.call.configuration.NodeConfigUpdateCall;
+import org.apache.ignite.cli.call.configuration.NodeConfigUpdateCallInput;
+import org.apache.ignite.cli.commands.BaseCommand;
+import org.apache.ignite.cli.core.call.CallExecutionPipeline;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.Parameters;
+
+/**
+ * Command that updates node configuration.
+ */
+@Command(name = "update",
+        description = "Updates node configuration.")
+@Singleton
+public class NodeConfigUpdateSubCommand extends BaseCommand {
+    @Inject
+    NodeConfigUpdateCall call;
+    /**
+     * Node url option.
+     */
+    @Option(
+            names = {"--node-url"}, description = "Url to Ignite node.",
+            descriptionKey = "ignite.cluster-url", defaultValue = "http://localhost:10300"
+    )
+    private String nodeUrl;
+    /**
+     * Configuration that will be updated.
+     */
+    @Parameters(index = "0")
+    private String config;
+
+    /** {@inheritDoc} */
+    @Override
+    public void run() {
+        CallExecutionPipeline.builder(call)
+                .inputProvider(this::buildCallInput)
+                .output(spec.commandLine().getOut())
+                .errOutput(spec.commandLine().getErr())
+                .build()
+                .runPipeline();
+    }
+
+    private NodeConfigUpdateCallInput buildCallInput() {
+        return NodeConfigUpdateCallInput.builder()
+                .nodeUrl(nodeUrl)
+                .config(config)
+                .build();
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/node/NodeReplCommand.java
similarity index 65%
copy from modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/node/NodeReplCommand.java
index 2e0050be3..f8c30a5c5 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/configuration/node/NodeReplCommand.java
@@ -15,15 +15,19 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.spec;
+package org.apache.ignite.cli.commands.configuration.node;
 
-import picocli.CommandLine;
+import org.apache.ignite.cli.deprecated.spec.NodeCommandSpec;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Mixin;
 
 /**
- * Base class for commands without any subcommands.
+ * Node command in REPL mode.
  */
-public abstract class CommandSpec extends SpecAdapter {
-    /** Help option specification. */
-    @CommandLine.Option(names = "--help", usageHelp = true, hidden = true)
-    protected boolean usageHelpRequested;
+@Command(name = "node",
+        subcommands = {NodeConfigReplSubCommand.class},
+        description = "Node config operations.")
+public class NodeReplCommand {
+    @Mixin
+    NodeCommandSpec nodeCommandSpec;
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/connect/ConnectCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/connect/ConnectCommand.java
new file mode 100644
index 000000000..32a6e534a
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/connect/ConnectCommand.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands.connect;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import org.apache.ignite.cli.call.connect.ConnectCall;
+import org.apache.ignite.cli.call.connect.ConnectCallInput;
+import org.apache.ignite.cli.commands.BaseCommand;
+import org.apache.ignite.cli.core.call.CallExecutionPipeline;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Model.CommandSpec;
+import picocli.CommandLine.Parameters;
+import picocli.CommandLine.Spec;
+
+/**
+ * Connects to the Ignite 3 node.
+ */
+@Command(name = "connect", description = "Connect to Ignite 3 node.")
+@Singleton
+public class ConnectCommand extends BaseCommand {
+
+    /**
+     * Cluster url option.
+     */
+    @Parameters(
+            description = "Ignite node url.",
+            descriptionKey = "ignite.cluster-url", defaultValue = "http://localhost:10300"
+    )
+    private String nodeUrl;
+
+    @Spec
+    private CommandSpec spec;
+
+    @Inject
+    private ConnectCall connectCall;
+
+    /** {@inheritDoc} */
+    @Override
+    public void run() {
+        CallExecutionPipeline.builder(connectCall)
+                .inputProvider(() -> ConnectCallInput.builder().nodeUrl(nodeUrl).build())
+                .output(spec.commandLine().getOut())
+                .errOutput(spec.commandLine().getErr())
+                .build()
+                .runPipeline();
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/connect/DisconnectCommand.java
similarity index 50%
copy from modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/connect/DisconnectCommand.java
index 9099a22db..588989507 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/connect/DisconnectCommand.java
@@ -15,36 +15,38 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.commands.connect;
 
-import io.micronaut.core.annotation.Introspected;
 import jakarta.inject.Inject;
 import jakarta.inject.Singleton;
-import picocli.CommandLine;
+import org.apache.ignite.cli.call.connect.DisconnectCall;
+import org.apache.ignite.cli.commands.BaseCommand;
+import org.apache.ignite.cli.core.call.CallExecutionPipeline;
+import org.apache.ignite.cli.core.call.EmptyCallInput;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Model.CommandSpec;
+import picocli.CommandLine.Spec;
 
 /**
- * Version provider for Picocli interactions.
+ * Connects to the Ignite 3 node.
  */
+@Command(name = "disconnect", description = "Disconnect from Ignite 3 node.")
 @Singleton
-@Introspected
-public class VersionProvider implements CommandLine.IVersionProvider {
+public class DisconnectCommand extends BaseCommand {
+    @Spec
+    private CommandSpec spec;
 
-    /** Actual Ignite CLI version info. */
-    private final CliVersionInfo cliVerInfo;
-
-    /**
-     * Creates version provider.
-     *
-     * @param cliVerInfo Actual Ignite CLI version container.
-     */
     @Inject
-    public VersionProvider(CliVersionInfo cliVerInfo) {
-        this.cliVerInfo = cliVerInfo;
-    }
+    private DisconnectCall disconnectCall;
 
     /** {@inheritDoc} */
     @Override
-    public String[] getVersion() {
-        return new String[]{"Apache Ignite CLI ver. " + cliVerInfo.ver};
+    public void run() {
+        CallExecutionPipeline.builder(disconnectCall)
+                .inputProvider(EmptyCallInput::new)
+                .output(spec.commandLine().getOut())
+                .errOutput(spec.commandLine().getErr())
+                .build()
+                .runPipeline();
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/ConfigDecorator.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/ConfigDecorator.java
new file mode 100644
index 000000000..87c91adf5
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/ConfigDecorator.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands.decorators;
+
+import java.util.Iterator;
+import java.util.Map.Entry;
+import org.apache.ignite.cli.commands.decorators.core.Decorator;
+import org.apache.ignite.cli.commands.decorators.core.TerminalOutput;
+import org.apache.ignite.cli.config.Config;
+
+/**
+ * Decorator for printing {@link Config}.
+ */
+public class ConfigDecorator implements Decorator<Config, TerminalOutput> {
+    @Override
+    public TerminalOutput decorate(Config data) {
+        StringBuilder builder = new StringBuilder();
+        for (Iterator<Entry<Object, Object>> iterator = data.getProperties().entrySet().iterator(); iterator.hasNext(); ) {
+            Entry<Object, Object> entry = iterator.next();
+            builder.append(entry.getKey()).append("=").append(entry.getValue());
+            if (iterator.hasNext()) {
+                builder.append(System.lineSeparator());
+            }
+        }
+        return builder::toString;
+    }
+}
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/DefaultDecorator.java
similarity index 65%
copy from modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/DefaultDecorator.java
index 32fbdbfdb..a9a8270f7 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/DefaultDecorator.java
@@ -15,22 +15,21 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.commands.decorators;
 
-import java.util.logging.Handler;
-import java.util.logging.LogRecord;
+import org.apache.ignite.cli.commands.decorators.core.Decorator;
+import org.apache.ignite.cli.commands.decorators.core.TerminalOutput;
 
 /**
- * Adapter for {@link Handler} with empty implementations of all methods but {@link Handler#publish(LogRecord)}.
+ * Default decorator that calls toString method.
+ *
+ * @param <I> Input type.
  */
-public abstract class NoOpHandler extends Handler {
-    @Override
-    public void flush() {
-        // no-op
-    }
+public class DefaultDecorator<I> implements Decorator<I, TerminalOutput> {
 
+    /** {@inheritDoc} */
     @Override
-    public void close() throws SecurityException {
-        // no-op
+    public TerminalOutput decorate(I data) {
+        return data::toString;
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/JsonDecorator.java
similarity index 50%
copy from modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/JsonDecorator.java
index 9099a22db..de90a79e0 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/JsonDecorator.java
@@ -15,36 +15,29 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.commands.decorators;
 
-import io.micronaut.core.annotation.Introspected;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-import picocli.CommandLine;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.ignite.cli.commands.decorators.core.Decorator;
+import org.apache.ignite.cli.commands.decorators.core.TerminalOutput;
 
 /**
- * Version provider for Picocli interactions.
+ * Pretty json decorator.
  */
-@Singleton
-@Introspected
-public class VersionProvider implements CommandLine.IVersionProvider {
-
-    /** Actual Ignite CLI version info. */
-    private final CliVersionInfo cliVerInfo;
-
-    /**
-     * Creates version provider.
-     *
-     * @param cliVerInfo Actual Ignite CLI version container.
-     */
-    @Inject
-    public VersionProvider(CliVersionInfo cliVerInfo) {
-        this.cliVerInfo = cliVerInfo;
-    }
+public class JsonDecorator implements Decorator<String, TerminalOutput> {
 
     /** {@inheritDoc} */
     @Override
-    public String[] getVersion() {
-        return new String[]{"Apache Ignite CLI ver. " + cliVerInfo.ver};
+    public TerminalOutput decorate(String jsonString) {
+        ObjectMapper mapper = new ObjectMapper();
+        return () -> {
+            try {
+                return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(mapper.readValue(jsonString, JsonNode.class));
+            } catch (JsonProcessingException e) {
+                throw new RuntimeException(e);
+            }
+        };
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CategorySpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/SqlQueryResultDecorator.java
similarity index 55%
copy from modules/cli/src/main/java/org/apache/ignite/cli/spec/CategorySpec.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/SqlQueryResultDecorator.java
index dc90c1881..d4713838a 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CategorySpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/SqlQueryResultDecorator.java
@@ -15,24 +15,23 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.spec;
+package org.apache.ignite.cli.commands.decorators;
 
-import java.io.PrintWriter;
-import picocli.CommandLine.Help.ColorScheme;
+import org.apache.ignite.cli.commands.decorators.core.Decorator;
+import org.apache.ignite.cli.commands.decorators.core.TerminalOutput;
+import org.apache.ignite.cli.sql.SqlQueryResult;
 
 /**
- * Base specification for commands which have subcommands.
+ * Composite decorator for {@link SqlQueryResult}.
  */
-public abstract class CategorySpec extends SpecAdapter {
-    /** {@inheritDoc} */
-    @Override
-    public void run() {
-        PrintWriter out = spec.commandLine().getOut();
-        ColorScheme cs = spec.commandLine().getColorScheme();
+public class SqlQueryResultDecorator implements Decorator<SqlQueryResult, TerminalOutput> {
+
+    private final TableDecorator tableDecorator = new TableDecorator();
 
-        out.println(cs.errorText("[ERROR] ") + "Unknown command: "
-                + cs.commandText(spec.qualifiedName()) + ". See the list of available commands below.\n");
+    private final DefaultDecorator<String> messageDecorator = new DefaultDecorator<>();
 
-        spec.parent().commandLine().usage(out);
+    @Override
+    public TerminalOutput decorate(SqlQueryResult data) {
+        return data.getResult(tableDecorator, messageDecorator);
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/StatusDecorator.java
similarity index 50%
copy from modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/StatusDecorator.java
index 9099a22db..d874f8eaf 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/StatusDecorator.java
@@ -15,36 +15,26 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.commands.decorators;
 
-import io.micronaut.core.annotation.Introspected;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-import picocli.CommandLine;
+import org.apache.ignite.cli.call.status.Status;
+import org.apache.ignite.cli.commands.decorators.core.Decorator;
+import org.apache.ignite.cli.commands.decorators.core.TerminalOutput;
+import picocli.CommandLine.Help.Ansi;
 
 /**
- * Version provider for Picocli interactions.
+ * Decorator for {@link Status}.
  */
-@Singleton
-@Introspected
-public class VersionProvider implements CommandLine.IVersionProvider {
+public class StatusDecorator implements Decorator<Status, TerminalOutput> {
 
-    /** Actual Ignite CLI version info. */
-    private final CliVersionInfo cliVerInfo;
-
-    /**
-     * Creates version provider.
-     *
-     * @param cliVerInfo Actual Ignite CLI version container.
-     */
-    @Inject
-    public VersionProvider(CliVersionInfo cliVerInfo) {
-        this.cliVerInfo = cliVerInfo;
-    }
-
-    /** {@inheritDoc} */
     @Override
-    public String[] getVersion() {
-        return new String[]{"Apache Ignite CLI ver. " + cliVerInfo.ver};
+    public TerminalOutput decorate(Status data) {
+        if (!data.isConnected()) {
+            return () -> "Can not get status from " + data.getConnectedNodeUrl();
+        }
+
+        return () -> Ansi.AUTO.string("Status from " + data.getConnectedNodeUrl() + System.lineSeparator()
+                + "[nodes: " + data.getNodeCount() + ", status: "
+                + (data.isInitialized() ? "@|fg(10) active|@" : "@|fg(9) not initialized|@") + "]");
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/StatusReplDecorator.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/StatusReplDecorator.java
new file mode 100644
index 000000000..3740f639e
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/StatusReplDecorator.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands.decorators;
+
+import org.apache.ignite.cli.call.status.Status;
+import org.apache.ignite.cli.commands.decorators.core.Decorator;
+import org.apache.ignite.cli.commands.decorators.core.TerminalOutput;
+import picocli.CommandLine.Help.Ansi;
+
+/**
+ * Decorator for {@link Status}.
+ */
+public class StatusReplDecorator implements Decorator<Status, TerminalOutput> {
+
+    @Override
+    public TerminalOutput decorate(Status data) {
+        if (!data.isConnected()) {
+            return () -> "You are not connected to any Ignite 3 node. Try to run 'connect' command.";
+        }
+
+        return () -> Ansi.AUTO.string("Connected to " + data.getConnectedNodeUrl() + System.lineSeparator()
+                + "[nodes: " + data.getNodeCount() + ", status: "
+                + (data.isInitialized() ? "@|fg(10) active|@" : "@|fg(9) not initialized|@") + "]");
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/TableDecorator.java
similarity index 53%
copy from modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/TableDecorator.java
index 9099a22db..71407824d 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/TableDecorator.java
@@ -15,36 +15,26 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.commands.decorators;
 
-import io.micronaut.core.annotation.Introspected;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-import picocli.CommandLine;
+import com.jakewharton.fliptables.FlipTableConverters;
+import org.apache.ignite.cli.commands.decorators.core.Decorator;
+import org.apache.ignite.cli.commands.decorators.core.TerminalOutput;
+import org.apache.ignite.cli.sql.table.Table;
 
 /**
- * Version provider for Picocli interactions.
+ * Implementation of {@link Decorator} for {@link Table}.
  */
-@Singleton
-@Introspected
-public class VersionProvider implements CommandLine.IVersionProvider {
-
-    /** Actual Ignite CLI version info. */
-    private final CliVersionInfo cliVerInfo;
+public class TableDecorator implements Decorator<Table<String>, TerminalOutput> {
 
     /**
-     * Creates version provider.
+     * Transform {@link Table} to {@link TerminalOutput}.
      *
-     * @param cliVerInfo Actual Ignite CLI version container.
+     * @param table incoming {@link Table}.
+     * @return User friendly interpretation of {@link Table} in {@link TerminalOutput}.
      */
-    @Inject
-    public VersionProvider(CliVersionInfo cliVerInfo) {
-        this.cliVerInfo = cliVerInfo;
-    }
-
-    /** {@inheritDoc} */
     @Override
-    public String[] getVersion() {
-        return new String[]{"Apache Ignite CLI ver. " + cliVerInfo.ver};
+    public TerminalOutput decorate(Table<String> table) {
+        return () -> FlipTableConverters.fromObjects(table.header(), table.content());
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/HttpClientFactory.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/core/Decorator.java
similarity index 61%
copy from modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/HttpClientFactory.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/core/Decorator.java
index 178725423..79fc19d4d 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/HttpClientFactory.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/core/Decorator.java
@@ -15,27 +15,20 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.builtins.config;
-
-import io.micronaut.context.annotation.Factory;
-import jakarta.inject.Singleton;
-import java.net.http.HttpClient;
+package org.apache.ignite.cli.commands.decorators.core;
 
 /**
- * Factory for producing simple HTTP clients.
+ * Interface for transformation command output to terminal output.
+ *
+ * @param <CommandDataT> type of command output.
+ * @param <TerminalDataT> type of terminal output.
  */
-@Factory
-public class HttpClientFactory {
+public interface Decorator<CommandDataT, TerminalDataT extends TerminalOutput> {
     /**
-     * Creates new HTTP client.
+     * Interface for transforming command output to terminal output.
      *
-     * @return HttpClient
+     * @param data incoming data object.
+     * @return Decorated object with type {@link TerminalDataT}.
      */
-    @Singleton
-    HttpClient httpClient() {
-        return HttpClient
-                .newBuilder()
-                .version(HttpClient.Version.HTTP_1_1)
-                .build();
-    }
+    TerminalDataT decorate(CommandDataT data);
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/package-info.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/core/TerminalOutput.java
similarity index 74%
copy from modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/package-info.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/core/TerminalOutput.java
index 5cb93f287..eb9899e92 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/package-info.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/core/TerminalOutput.java
@@ -15,8 +15,16 @@
  * limitations under the License.
  */
 
+package org.apache.ignite.cli.commands.decorators.core;
+
 /**
- * Contains classes for Ignite module management.
+ * Interface for terminal output representation.
  */
-
-package org.apache.ignite.cli.builtins.module;
+public interface TerminalOutput {
+    /**
+     * Terminal output transformation.
+     *
+     * @return String representation of some
+     */
+    String toTerminalString();
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/sql/SqlCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/sql/SqlCommand.java
new file mode 100644
index 000000000..986f4e2a8
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/sql/SqlCommand.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands.sql;
+
+import jakarta.inject.Inject;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.sql.SQLException;
+import org.apache.ignite.cli.commands.BaseCommand;
+import org.apache.ignite.cli.commands.decorators.SqlQueryResultDecorator;
+import org.apache.ignite.cli.core.CallExecutionPipelineProvider;
+import org.apache.ignite.cli.core.call.CallExecutionPipeline;
+import org.apache.ignite.cli.core.call.StringCallInput;
+import org.apache.ignite.cli.core.exception.CommandExecutionException;
+import org.apache.ignite.cli.core.exception.ExceptionHandlers;
+import org.apache.ignite.cli.core.exception.ExceptionWriter;
+import org.apache.ignite.cli.core.exception.handler.DefaultExceptionHandlers;
+import org.apache.ignite.cli.core.exception.handler.SqlExceptionHandler;
+import org.apache.ignite.cli.core.repl.Repl;
+import org.apache.ignite.cli.core.repl.executor.RegistryCommandExecutor;
+import org.apache.ignite.cli.core.repl.executor.ReplExecutorProvider;
+import org.apache.ignite.cli.core.repl.executor.SqlQueryCall;
+import org.apache.ignite.cli.sql.SqlManager;
+import org.apache.ignite.cli.sql.SqlSchemaProvider;
+import org.jetbrains.annotations.NotNull;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+
+/**
+ * Command for sql execution.
+ */
+@Command(name = "sql", description = "Executes SQL query.")
+public class SqlCommand extends BaseCommand {
+    private static final String INTERNAL_COMMAND_PREFIX = "!";
+
+    @Option(names = {"-u", "--jdbc-url"}, required = true,
+            descriptionKey = "ignite.jdbc-url", description = "JDBC url to ignite cluster")
+    private String jdbc;
+
+    @Option(names = {"-e", "--execute", "--exec"}) //todo: can be passed as parameter, not option (see IEP-88)
+    private String command;
+
+    @Option(names = {"-f", "--script-file"})
+    private File file;
+
+    @Inject
+    private ReplExecutorProvider replExecutorProvider;
+
+    private static String extract(File file) {
+        try {
+            return String.join("\n", Files.readAllLines(file.toPath(), StandardCharsets.UTF_8));
+        } catch (IOException e) {
+            throw new CommandExecutionException("sql", "File with command not found.");
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void run() {
+        try (SqlManager sqlManager = new SqlManager(jdbc)) {
+            if (command == null && file == null) {
+                replExecutorProvider.get().execute(Repl.builder()
+                        .withPromptProvider(() -> "sql-cli> ")
+                        .withCompleter(new SqlCompleter(new SqlSchemaProvider(sqlManager::getMetadata)))
+                        .withCommandClass(SqlReplTopLevelCliCommand.class)
+                        .withCallExecutionPipelineProvider(provider(sqlManager))
+                        .withHistoryFileName("sqlhistory")
+                        .build());
+            } else {
+                String executeCommand = file != null ? extract(file) : command;
+                createSqlExecPipeline(sqlManager, executeCommand).runPipeline();
+            }
+        } catch (SQLException e) {
+            new SqlExceptionHandler().handle(ExceptionWriter.fromPrintWriter(spec.commandLine().getErr()), e);
+        }
+    }
+
+    @NotNull
+    private CallExecutionPipelineProvider provider(SqlManager sqlManager) {
+        return (call, exceptionHandlers, line) -> line.startsWith(INTERNAL_COMMAND_PREFIX)
+                ? createInternalCommandPipeline(call, exceptionHandlers, line)
+                : createSqlExecPipeline(sqlManager, line);
+    }
+
+    private CallExecutionPipeline<?, ?> createSqlExecPipeline(SqlManager sqlManager, String line) {
+        return CallExecutionPipeline.builder(new SqlQueryCall(sqlManager))
+                .inputProvider(() -> new StringCallInput(line))
+                .output(spec.commandLine().getOut())
+                .errOutput(spec.commandLine().getErr())
+                .exceptionHandlers(new DefaultExceptionHandlers())
+                .decorator(new SqlQueryResultDecorator())
+                .build();
+    }
+
+    private CallExecutionPipeline<?, ?> createInternalCommandPipeline(RegistryCommandExecutor call,
+            ExceptionHandlers exceptionHandlers,
+            String line) {
+        return CallExecutionPipeline.builder(call)
+                .inputProvider(() -> new StringCallInput(line.substring(INTERNAL_COMMAND_PREFIX.length())))
+                .output(spec.commandLine().getOut())
+                .errOutput(spec.commandLine().getErr())
+                .exceptionHandlers(new DefaultExceptionHandlers())
+                .exceptionHandlers(exceptionHandlers)
+                .build();
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/sql/SqlCompleter.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/sql/SqlCompleter.java
new file mode 100644
index 000000000..23fa9768d
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/sql/SqlCompleter.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands.sql;
+
+import java.util.List;
+import org.apache.ignite.cli.sql.SchemaProvider;
+import org.jline.reader.Candidate;
+import org.jline.reader.Completer;
+import org.jline.reader.LineReader;
+import org.jline.reader.ParsedLine;
+
+class SqlCompleter implements Completer {
+    private final SchemaProvider schemaProvider;
+
+    SqlCompleter(SchemaProvider schemaProvider) {
+        this.schemaProvider = schemaProvider;
+    }
+
+    @Override
+    public void complete(LineReader reader, ParsedLine commandLine, List<Candidate> candidates) {
+        if (commandLine.wordIndex() == 0) {
+            addCandidatesFromArray(SqlMetaData.STARTING_KEYWORDS, candidates);
+        } else {
+            fillCandidates(candidates);
+        }
+    }
+
+    private void fillCandidates(List<Candidate> candidates) {
+        addKeywords(candidates);
+        for (String schema : schemaProvider.getSchema().schemas()) {
+            addCandidate(schema, candidates);
+            for (String table : schemaProvider.getSchema().tables(schema)) {
+                addCandidate(table, candidates);
+                //TODO: https://issues.apache.org/jira/browse/IGNITE-16973
+            }
+        }
+    }
+
+    private void addKeywords(List<Candidate> candidates) {
+        addCandidatesFromArray(SqlMetaData.KEYWORDS, candidates);
+        addCandidatesFromArray(SqlMetaData.NUMERIC_FUNCTIONS, candidates);
+        addCandidatesFromArray(SqlMetaData.STRING_FUNCTIONS, candidates);
+        addCandidatesFromArray(SqlMetaData.TIME_DATE_FUNCTIONS, candidates);
+        addCandidatesFromArray(SqlMetaData.SYSTEM_FUNCTIONS, candidates);
+    }
+
+    private static void addCandidatesFromArray(String[] strings, List<Candidate> candidates) {
+        for (String keyword : strings) {
+            addCandidate(keyword, candidates);
+        }
+    }
+
+    private static void addCandidate(String string, List<Candidate> candidates) {
+        candidates.add(new Candidate(string));
+        candidates.add(new Candidate(string.toLowerCase()));
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/sql/SqlMetaData.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/sql/SqlMetaData.java
new file mode 100644
index 000000000..70b227a45
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/sql/SqlMetaData.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands.sql;
+
+/**
+ * Constants for SQL auto-completion.
+ */
+
+public class SqlMetaData {
+
+    /**
+     * SQL reserved keywords.
+     */
+    public static String[] KEYWORDS = { //TODO: https://issues.apache.org/jira/browse/IGNITE-16973
+            "ABSOLUTE", "ACTION", "ADD", "ALL", "ALLOCATE", "ALTER", "AND", "ANY", "ARE", "AS", "ASC", "ASSERTION", "AT", "AUTHORIZATION",
+            "AVG", "BEGIN", "BETWEEN", "BIT", "BIT_LENGTH", "BOTH", "BY", "CASCADE", "CASCADED", "CASE", "CAST", "CATALOG", "CHAR",
+            "CHARACTER", "CHAR_LENGTH", "CHARACTER_LENGTH", "CHECK", "CLOSE", "COALESCE", "COLLATE", "COLLATION", "COLUMN", "COMMIT",
+            "CONNECT", "CONNECTION", "CONSTRAINT", "CONSTRAINTS", "CONTINUE", "CONVERT", "CORRESPONDING", "COUNT", "CREATE", "CROSS",
+            "CURRENT", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_USER", "CURSOR", "DATE", "DAY", "DEALLOCATE", "DEC",
+            "DECIMAL", "DECLARE", "DEFAULT", "DEFERRABLE", "DEFERRED", "DELETE", "DESC", "DESCRIBE", "DESCRIPTOR", "DIAGNOSTICS",
+            "DISCONNECT", "DISTINCT", "DOMAIN", "DOUBLE", "DROP", "ELSE", "END", "END-EXEC", "ESCAPE", "EXCEPT", "EXCEPTION", "EXEC",
+            "EXECUTE", "EXISTS", "EXTERNAL", "EXTRACT", "FALSE", "FETCH", "FIRST", "FLOAT", "FOR", "FOREIGN", "FOUND", "FROM", "FULL",
+            "GET", "GLOBAL", "GO", "GOTO", "GRANT", "GROUP", "HAVING", "HOUR", "IDENTITY", "IMMEDIATE", "IN", "INDICATOR", "INITIALLY",
+            "INNER", "INPUT", "INSENSITIVE", "INSERT", "INT", "INTEGER", "INTERSECT", "INTERVAL", "INTO", "IS", "ISOLATION", "JOIN", "KEY",
+            "LANGUAGE", "LAST", "LEADING", "LEFT", "LEVEL", "LIKE", "LOCAL", "LOWER", "MATCH", "MAX", "MIN", "MINUTE", "MODULE", "MONTH",
+            "NAMES", "NATIONAL", "NATURAL", "NCHAR", "NEXT", "NO", "NOT", "NULL", "NULLIF", "NUMERIC", "OCTET_LENGTH", "OF", "ON", "ONLY",
+            "OPEN", "OPTION", "OR", "ORDER", "OUTER", "OUTPUT", "OVERLAPS", "PAD", "PARTIAL", "POSITION", "PRECISION", "PREPARE",
+            "PRESERVE", "PRIMARY", "PRIOR", "PRIVILEGES", "PROCEDURE", "PUBLIC", "READ", "REAL", "REFERENCES", "RELATIVE", "RESTRICT",
+            "REVOKE", "RIGHT", "ROLLBACK", "ROWS", "SCHEMA", "SCROLL", "SECOND", "SECTION", "SELECT", "SESSION", "SESSION_USER", "SET",
+            "SIZE", "SMALLINT", "SOME", "SPACE", "SQL", "SQLCODE", "SQLERROR", "SQLSTATE", "SUBSTRING", "SUM", "SYSTEM_USER", "TABLE",
+            "TEMPORARY", "THEN", "TIME", "TIMESTAMP", "TIMEZONE_HOUR", "TIMEZONE_MINUTE", "TO", "TRAILING", "TRANSACTION", "TRANSLATE",
+            "TRANSLATION", "TRIM", "TRUE", "UNION", "UNIQUE", "UNKNOWN", "UPDATE", "UPPER", "USAGE", "USER", "USING", "VALUE", "VALUES",
+            "VARCHAR", "VARYING", "VIEW", "WHEN", "WHENEVER", "WHERE", "WITH", "WORK", "WRITE", "YEAR", "ZONE", "ADA", "C", "CATALOG_NAME",
+            "CHARACTER_SET_CATALOG", "CHARACTER_SET_NAME", "CHARACTER_SET_SCHEMA", "CLASS_ORIGIN", "COBOL", "COLLATION_CATALOG",
+            "COLLATION_NAME", "COLLATION_SCHEMA", "COLUMN_NAME", "COMMAND_FUNCTION", "COMMITTED", "CONDITION_NUMBER", "CONNECTION_NAME",
+            "CONSTRAINT_CATALOG", "CONSTRAINT_NAME", "CONSTRAINT_SCHEMA", "CURSOR_NAME", "DATA", "DATETIME_INTERVAL_CODE",
+            "DATETIME_INTERVAL_PRECISION", "DYNAMIC_FUNCTION", "FORTRAN", "LENGTH", "MESSAGE_LENGTH", "MESSAGE_OCTET_LENGTH",
+            "MESSAGE_TEXT", "MORE", "MUMPS", "NAME", "NULLABLE", "NUMBER", "PASCAL", "PLI", "REPEATABLE", "RETURNED_LENGTH",
+            "RETURNED_OCTET_LENGTH", "RETURNED_SQLSTATE", "ROW_COUNT", "SCALE", "SCHEMA_NAME", "SERIALIZABLE", "SERVER_NAME",
+            "SUBCLASS_ORIGIN", "TABLE_NAME", "TYPE", "UNCOMMITTED", "UNNAMED"
+    };
+
+    /**
+     * List of keywords which must be at the beginning of the statement, used for very basic completion.
+     */
+    public static final String[] STARTING_KEYWORDS = {
+            "ALTER", "SET", "CREATE", "DROP", "WITH", "SELECT", "EXPLAIN", "DESCRIBE", "INSERT", "DELETE", "UPDATE", "MERGE", "CALL"
+    };
+
+    // These lists are copied from calcite
+    /**
+     * SQL numeric functions.
+     */
+    public static final String[] NUMERIC_FUNCTIONS = {
+            "ABS", "ACOS", "ASIN", "ATAN", "ATAN2", "CBRT", "CEILING", "COS", "COT", "DEGREES", "EXP", "FLOOR", "LOG", "LOG10", "MOD", "PI",
+            "POWER", "RADIANS", "RAND", "ROUND", "SIGN", "SIN", "SQRT", "TAN", "TRUNCATE"
+    };
+
+    /**
+     * SQL string functions.
+     */
+    public static final String[] STRING_FUNCTIONS = {
+            "ASCII", "CHAR", "CONCAT", "DIFFERENCE", "INSERT", "LCASE", "LEFT", "LENGTH", "LOCATE",
+            "LTRIM", "REPEAT", "REPLACE", "RIGHT", "RTRIM", "SOUNDEX", "SPACE", "SUBSTRING", "UCASE"
+    };
+
+    /**
+     * SQL date/time functions.
+     */
+    public static final String[] TIME_DATE_FUNCTIONS = {
+            "CONVERT_TIMEZONE", "CURDATE", "CURTIME", "DAYNAME", "DAYOFMONTH", "DAYOFWEEK", "DAYOFYEAR", "HOUR", "MINUTE", "MONTH",
+            "MONTHNAME", "NOW", "QUARTER", "SECOND", "TIMESTAMPADD", "TIMESTAMPDIFF", "TO_DATE", "TO_TIMESTAMP", "WEEK", "YEAR"
+    };
+
+    /**
+     * SQL system functions.
+     */
+    public static final String[] SYSTEM_FUNCTIONS = {
+            "CONVERT", "DATABASE", "IFNULL", "USER"
+    };
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/sql/SqlReplTopLevelCliCommand.java
similarity index 65%
copy from modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/sql/SqlReplTopLevelCliCommand.java
index 2e0050be3..661364410 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/sql/SqlReplTopLevelCliCommand.java
@@ -15,15 +15,22 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.spec;
+package org.apache.ignite.cli.commands.sql;
 
+import jakarta.inject.Singleton;
 import picocli.CommandLine;
+import picocli.shell.jline3.PicocliCommands;
 
 /**
- * Base class for commands without any subcommands.
+ * Top level SQL REPL command.
  */
-public abstract class CommandSpec extends SpecAdapter {
-    /** Help option specification. */
-    @CommandLine.Option(names = "--help", usageHelp = true, hidden = true)
-    protected boolean usageHelpRequested;
+@CommandLine.Command(name = "",
+        description = {""},
+        footer = {"", "Press Ctrl-D to exit."},
+        subcommands = {
+            CommandLine.HelpCommand.class,
+            PicocliCommands.ClearScreen.class
+        })
+@Singleton
+public class SqlReplTopLevelCliCommand {
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/status/StatusCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/status/StatusCommand.java
new file mode 100644
index 000000000..c2be4f3aa
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/status/StatusCommand.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands.status;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import org.apache.ignite.cli.call.status.StatusCall;
+import org.apache.ignite.cli.commands.BaseCommand;
+import org.apache.ignite.cli.commands.decorators.StatusDecorator;
+import org.apache.ignite.cli.core.call.CallExecutionPipeline;
+import org.apache.ignite.cli.core.call.StatusCallInput;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Model.CommandSpec;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.Spec;
+
+/**
+ * Command that prints status of ignite cluster.
+ */
+@Command(name = "status",
+        aliases = "cluster show", //TODO: https://issues.apache.org/jira/browse/IGNITE-17102
+        description = "Prints status of the cluster.")
+@Singleton
+public class StatusCommand extends BaseCommand {
+
+    /**
+     * Cluster url option.
+     */
+    @SuppressWarnings("PMD.UnusedPrivateField")
+    @Option(
+            names = {"--cluster-url"}, description = "Url to cluster node.",
+            descriptionKey = "ignite.cluster-url", defaultValue = "http://localhost:10300"
+    )
+    private String clusterUrl;
+
+    @Spec
+    private CommandSpec commandSpec;
+
+    @Inject
+    private StatusCall statusCall;
+
+    /** {@inheritDoc} */
+    @Override
+    public void run() {
+        CallExecutionPipeline.builder(statusCall)
+                .inputProvider(() -> new StatusCallInput(clusterUrl))
+                .output(commandSpec.commandLine().getOut())
+                .errOutput(commandSpec.commandLine().getErr())
+                .decorator(new StatusDecorator())
+                .build()
+                .runPipeline();
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/status/StatusReplCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/status/StatusReplCommand.java
new file mode 100644
index 000000000..4ebb924ca
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/status/StatusReplCommand.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands.status;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import org.apache.ignite.cli.call.status.StatusReplCall;
+import org.apache.ignite.cli.commands.BaseCommand;
+import org.apache.ignite.cli.commands.decorators.StatusReplDecorator;
+import org.apache.ignite.cli.core.call.CallExecutionPipeline;
+import org.apache.ignite.cli.core.call.EmptyCallInput;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Model.CommandSpec;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.Spec;
+
+/**
+ * Command that prints status of ignite cluster.
+ */
+@Command(name = "status", description = "Prints status of the cluster.")
+@Singleton
+public class StatusReplCommand extends BaseCommand {
+
+    /**
+     * Cluster url option.
+     */
+    @SuppressWarnings("PMD.UnusedPrivateField")
+    @Option(
+            names = {"--cluster-url"}, description = "Url to cluster node."
+    )
+    private String clusterUrl;
+
+    @Spec
+    private CommandSpec commandSpec;
+
+    @Inject
+    private StatusReplCall statusReplCall;
+
+    /** {@inheritDoc} */
+    @Override
+    public void run() {
+        CallExecutionPipeline.builder(statusReplCall)
+                .inputProvider(EmptyCallInput::new)
+                .output(commandSpec.commandLine().getOut())
+                .errOutput(commandSpec.commandLine().getErr())
+                .decorator(new StatusReplDecorator())
+                .build()
+                .runPipeline();
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/TopologyCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/TopologyCommand.java
new file mode 100644
index 000000000..872238fb6
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/TopologyCommand.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.commands.topology;
+
+import jakarta.inject.Singleton;
+import org.apache.ignite.cli.commands.BaseCommand;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Model.CommandSpec;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.Spec;
+
+/**
+ * Command that prints ignite cluster topology.
+ */
+@Command(name = "topology", description = "Prints topology information.")
+@Singleton
+public class TopologyCommand extends BaseCommand {
+
+    /**
+     * Cluster url option.
+     */
+    @SuppressWarnings("PMD.UnusedPrivateField")
+    @Option(
+            names = {"--cluster-url"}, description = "Url to cluster node.",
+            descriptionKey = "ignite.cluster-url", defaultValue = "http://localhost:10300"
+    )
+    private String clusterUrl;
+
+    @Spec
+    private CommandSpec commandSpec;
+
+    /** {@inheritDoc} */
+    @Override
+    public void run() {
+        //TODO: https://issues.apache.org/jira/browse/IGNITE-17092
+        commandSpec.commandLine().getOut().println("Topology command is not implemented yet.");
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CategorySpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/version/VersionCommand.java
similarity index 58%
copy from modules/cli/src/main/java/org/apache/ignite/cli/spec/CategorySpec.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/version/VersionCommand.java
index dc90c1881..18f5946e5 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CategorySpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/version/VersionCommand.java
@@ -15,24 +15,31 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.spec;
+package org.apache.ignite.cli.commands.version;
 
-import java.io.PrintWriter;
-import picocli.CommandLine.Help.ColorScheme;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import org.apache.ignite.cli.VersionProvider;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Model.CommandSpec;
+import picocli.CommandLine.Spec;
 
 /**
- * Base specification for commands which have subcommands.
+ * Command that prints CLI version.
  */
-public abstract class CategorySpec extends SpecAdapter {
+@Command(name = "version", description = "Prints CLI version.")
+@Singleton
+public class VersionCommand implements Runnable {
+
+    @Spec
+    private CommandSpec commandSpec;
+
+    @Inject
+    private VersionProvider versionProvider;
+
     /** {@inheritDoc} */
     @Override
     public void run() {
-        PrintWriter out = spec.commandLine().getOut();
-        ColorScheme cs = spec.commandLine().getColorScheme();
-
-        out.println(cs.errorText("[ERROR] ") + "Unknown command: "
-                + cs.commandText(spec.qualifiedName()) + ". See the list of available commands below.\n");
-
-        spec.parent().commandLine().usage(out);
+        commandSpec.commandLine().getOut().println(versionProvider.getVersion()[0]);
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/config/Config.java b/modules/cli/src/main/java/org/apache/ignite/cli/config/Config.java
new file mode 100644
index 000000000..04374689b
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/config/Config.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.config;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Path;
+import java.util.Properties;
+
+/**
+ * CLI default configuration.
+ */
+public class Config {
+    private static final String XDG_CONFIG_HOME = "XDG_CONFIG_HOME";
+    private static final String XDG_STATE_HOME = "XDG_STATE_HOME";
+    private static final String PARENT_FOLDER_NAME = "ignitecli";
+    private static final String CONFIG_FILE_NAME = "defaults";
+
+    private final File configFile;
+    private final Properties props = new Properties();
+
+    public Config(File configFile) {
+        this.configFile = configFile;
+        loadConfig(configFile);
+    }
+
+    /**
+     * Loads config from the default location specified by the XDG_CONFIG_HOME.
+     */
+    public Config() {
+        this(getConfigFile());
+    }
+
+    public Properties getProperties() {
+        return props;
+    }
+
+    public String getProperty(String key) {
+        return props.getProperty(key);
+    }
+
+    public String getProperty(String key, String defaultValue) {
+        return props.getProperty(key, defaultValue);
+    }
+
+    public void setProperty(String key, String value) {
+        props.setProperty(key, value);
+    }
+
+    private void loadConfig(File configFile) {
+        if (configFile.canRead()) {
+            try (InputStream is = new FileInputStream(configFile)) {
+                props.load(is);
+            } catch (IOException e) {
+                // todo report error?
+            }
+        }
+    }
+
+    /**
+     * Saves config to file.
+     */
+    public void saveConfig() {
+        configFile.getParentFile().mkdirs();
+        if (configFile.canWrite()) {
+            try (OutputStream os = new FileOutputStream(configFile)) {
+                props.store(os, null);
+            } catch (IOException e) {
+                // todo report error?
+            }
+        }
+    }
+
+    private static File getConfigFile() {
+        return getConfigRoot().resolve(PARENT_FOLDER_NAME).resolve(CONFIG_FILE_NAME).toFile();
+    }
+
+    /**
+     * Gets the path for the state.
+     *
+     * @return Folder for state storage.
+     */
+    public static File getStateFolder() {
+        return getStateRoot().resolve(PARENT_FOLDER_NAME).toFile();
+    }
+
+    private static Path getConfigRoot() {
+        String xdgConfigHome = System.getenv(XDG_CONFIG_HOME);
+        if (xdgConfigHome != null) {
+            return Path.of(xdgConfigHome);
+        } else {
+            return Path.of(System.getProperty("user.home"), ".config");
+        }
+    }
+
+    private static Path getStateRoot() {
+        String xdgStateHome = System.getenv(XDG_STATE_HOME);
+        if (xdgStateHome != null) {
+            return Path.of(xdgStateHome);
+        } else {
+            return Path.of(System.getProperty("user.home"), ".local", "state");
+        }
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/node/package-info.java b/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java
similarity index 74%
copy from modules/cli/src/main/java/org/apache/ignite/cli/builtins/node/package-info.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java
index 0ebfd92cd..a8e09d2f3 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/node/package-info.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java
@@ -15,8 +15,18 @@
  * limitations under the License.
  */
 
+package org.apache.ignite.cli.config;
+
+import io.micronaut.context.annotation.Factory;
+import jakarta.inject.Singleton;
+
 /**
- * Contains classes for Ignite node management.
+ * Factory for {@link Config}.
  */
-
-package org.apache.ignite.cli.builtins.node;
+@Factory
+public class ConfigFactory {
+    @Singleton
+    public Config fileConfig() {
+        return new Config();
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/HttpClientFactory.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/CallExecutionPipelineProvider.java
similarity index 56%
copy from modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/HttpClientFactory.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/CallExecutionPipelineProvider.java
index 178725423..0c0241174 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/HttpClientFactory.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/CallExecutionPipelineProvider.java
@@ -15,27 +15,23 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.builtins.config;
+package org.apache.ignite.cli.core;
 
-import io.micronaut.context.annotation.Factory;
-import jakarta.inject.Singleton;
-import java.net.http.HttpClient;
+import org.apache.ignite.cli.core.call.CallExecutionPipeline;
+import org.apache.ignite.cli.core.exception.ExceptionHandlers;
+import org.apache.ignite.cli.core.repl.executor.RegistryCommandExecutor;
 
 /**
- * Factory for producing simple HTTP clients.
+ * Provider of {@link CallExecutionPipeline}.
  */
-@Factory
-public class HttpClientFactory {
+public interface CallExecutionPipelineProvider {
     /**
-     * Creates new HTTP client.
+     * Pipeline getter.
      *
-     * @return HttpClient
+     * @param executor default executor.
+     * @param exceptionHandlers exception handlers.
+     * @param line call input.
+     * @return new pipeline {@link CallExecutionPipeline}
      */
-    @Singleton
-    HttpClient httpClient() {
-        return HttpClient
-                .newBuilder()
-                .version(HttpClient.Version.HTTP_1_1)
-                .build();
-    }
+    CallExecutionPipeline<?, ?> get(RegistryCommandExecutor executor, ExceptionHandlers exceptionHandlers, String line);
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/call/Call.java
similarity index 71%
copy from modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/call/Call.java
index 2e0050be3..6becdbb4c 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/call/Call.java
@@ -15,15 +15,15 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.spec;
-
-import picocli.CommandLine;
+package org.apache.ignite.cli.core.call;
 
 /**
- * Base class for commands without any subcommands.
+ * Call that represents an action that can be performed given an input.
+ * It can be rest call, dictionary lookup or whatever.
+ *
+ * @param <IT> Input for the call.
+ * @param <OT> Output of the call.
  */
-public abstract class CommandSpec extends SpecAdapter {
-    /** Help option specification. */
-    @CommandLine.Option(names = "--help", usageHelp = true, hidden = true)
-    protected boolean usageHelpRequested;
+public interface Call<IT extends CallInput, OT> {
+    CallOutput<OT> execute(IT input);
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/call/CallExecutionPipeline.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/call/CallExecutionPipeline.java
new file mode 100644
index 000000000..578ea02ae
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/call/CallExecutionPipeline.java
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.core.call;
+
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.charset.Charset;
+import java.util.function.Supplier;
+import org.apache.ignite.cli.commands.decorators.DefaultDecorator;
+import org.apache.ignite.cli.commands.decorators.core.Decorator;
+import org.apache.ignite.cli.commands.decorators.core.TerminalOutput;
+import org.apache.ignite.cli.core.exception.ExceptionHandler;
+import org.apache.ignite.cli.core.exception.ExceptionHandlers;
+import org.apache.ignite.cli.core.exception.ExceptionWriter;
+import org.apache.ignite.cli.core.exception.handler.DefaultExceptionHandlers;
+
+/**
+ * Call execution pipeline.
+ *
+ * @param <I> Call input type.
+ * @param <T> Call output's body type.
+ */
+public class CallExecutionPipeline<I extends CallInput, T> {
+    /**
+     * Call to execute.
+     */
+    private final Call<I, T> call;
+
+    /**
+     * Writer for execution output.
+     */
+    private final PrintWriter output;
+
+    /**
+     * Writer for error execution output.
+     */
+    private final PrintWriter errOutput;
+
+    /**
+     * Decorator that decorates call's output.
+     */
+    private final Decorator<T, TerminalOutput> decorator;
+
+    /**
+     * Handlers for any exceptions.
+     */
+    private final ExceptionHandlers exceptionHandlers;
+
+    /**
+     * Provider for call's input.
+     */
+    private final Supplier<I> inputProvider;
+
+    private CallExecutionPipeline(Call<I, T> call,
+            PrintWriter output,
+            PrintWriter errOutput,
+            ExceptionHandlers exceptionHandlers,
+            Decorator<T, TerminalOutput> decorator,
+            Supplier<I> inputProvider) {
+        this.call = call;
+        this.output = output;
+        this.exceptionHandlers = exceptionHandlers;
+        this.errOutput = errOutput;
+        this.decorator = decorator;
+        this.inputProvider = inputProvider;
+    }
+
+    /**
+     * Builder helper method.
+     *
+     * @return builder for {@link CallExecutionPipeline}.
+     */
+    public static <I extends CallInput, T> CallExecutionPipelineBuilder<I, T> builder(
+            Call<I, T> call) {
+        return new CallExecutionPipelineBuilder<>(call);
+    }
+
+    /** {@inheritDoc} */
+    public void runPipeline() {
+        I callInput = inputProvider.get();
+
+        CallOutput<T> callOutput = call.execute(callInput);
+
+        if (callOutput.hasError()) {
+            exceptionHandlers.handleException(ExceptionWriter.fromPrintWriter(errOutput), callOutput.errorCause());
+            return;
+        }
+
+        if (callOutput.isEmpty()) {
+            return;
+        }
+
+        TerminalOutput decoratedOutput = decorator.decorate(callOutput.body());
+
+        output.println(decoratedOutput.toTerminalString());
+    }
+
+    /** Builder for {@link CallExecutionPipeline}. */
+    public static class CallExecutionPipelineBuilder<I extends CallInput, T> {
+
+        private final Call<I, T> call;
+
+        private final ExceptionHandlers exceptionHandlers = new DefaultExceptionHandlers();
+
+        private Supplier<I> inputProvider;
+
+        private PrintWriter output = wrapOutputStream(System.out);
+
+        private PrintWriter errOutput = wrapOutputStream(System.err);
+
+        private Decorator<T, TerminalOutput> decorator = new DefaultDecorator<>();
+
+        public CallExecutionPipelineBuilder(Call<I, T> call) {
+            this.call = call;
+        }
+
+        public CallExecutionPipelineBuilder<I, T> inputProvider(Supplier<I> inputProvider) {
+            this.inputProvider = inputProvider;
+            return this;
+        }
+
+        public CallExecutionPipelineBuilder<I, T> output(PrintWriter output) {
+            this.output = output;
+            return this;
+        }
+
+        public CallExecutionPipelineBuilder<I, T> output(OutputStream output) {
+            return output(wrapOutputStream(output));
+        }
+
+        public CallExecutionPipelineBuilder<I, T> errOutput(PrintWriter errOutput) {
+            this.errOutput = errOutput;
+            return this;
+        }
+
+        public CallExecutionPipelineBuilder<I, T> errOutput(OutputStream output) {
+            return errOutput(wrapOutputStream(output));
+        }
+
+        public CallExecutionPipelineBuilder<I, T> exceptionHandler(ExceptionHandler<?> exceptionHandler) {
+            exceptionHandlers.addExceptionHandler(exceptionHandler);
+            return this;
+        }
+
+        public CallExecutionPipelineBuilder<I, T> exceptionHandlers(ExceptionHandlers exceptionHandlers) {
+            this.exceptionHandlers.addExceptionHandlers(exceptionHandlers);
+            return this;
+        }
+
+        public CallExecutionPipelineBuilder<I, T> decorator(Decorator<T, TerminalOutput> decorator) {
+            this.decorator = decorator;
+            return this;
+        }
+
+        public CallExecutionPipeline<I, T> build() {
+            return new CallExecutionPipeline<>(call, output, errOutput, exceptionHandlers, decorator, inputProvider);
+        }
+
+        private static PrintWriter wrapOutputStream(OutputStream output) {
+            return new PrintWriter(output, true, getStdoutEncoding());
+        }
+
+        private static Charset getStdoutEncoding() {
+            String encoding = System.getProperty("sun.stdout.encoding");
+            return encoding != null ? Charset.forName(encoding) : Charset.defaultCharset();
+        }
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/package-info.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/call/CallInput.java
similarity index 82%
copy from modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/package-info.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/call/CallInput.java
index 5cb93f287..46e4b36af 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/package-info.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/call/CallInput.java
@@ -15,8 +15,11 @@
  * limitations under the License.
  */
 
+package org.apache.ignite.cli.core.call;
+
 /**
- * Contains classes for Ignite module management.
+ * Input for {@link Call}.
+ * Usually this input contains all information needed to execute the call.
  */
-
-package org.apache.ignite.cli.builtins.module;
+public interface CallInput {
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/ResolveResult.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/call/CallOutput.java
similarity index 56%
copy from modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/ResolveResult.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/call/CallOutput.java
index 658f069a9..d4326864c 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/ResolveResult.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/call/CallOutput.java
@@ -15,34 +15,39 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.builtins.module;
-
-import java.nio.file.Path;
-import java.util.Collections;
-import java.util.List;
+package org.apache.ignite.cli.core.call;
 
 /**
- * Result of resolving maven artifact dependencies.
+ * Output of {@link Call}.
+ *
+ * @param <T> type of the body.
  */
-public class ResolveResult {
-    /** List of resolver artifacts' paths. */
-    private final List<Path> artifacts;
+public interface CallOutput<T> {
+    /**
+     * Body provider method.
+     *
+     * @return Body of the call's output. Can be {@link String} or any other type.
+     */
+    T body();
+
+    /**
+     * Error check method.
+     *
+     * @return True if output has an error.
+     */
+    boolean hasError();
 
     /**
-     * Creates result of artifacts resolving.
+     * Check if Call output is empty.
      *
-     * @param artifacts List of artifacts paths.
+     * @return true if call output is empty.
      */
-    public ResolveResult(List<Path> artifacts) {
-        this.artifacts = artifacts;
-    }
+    boolean isEmpty();
 
     /**
-     * Returns list of artifacts' paths.
+     * Exception cause provider method.
      *
-     * @return List of artifacts' paths.
+     * @return the cause of the error.
      */
-    public List<Path> artifacts() {
-        return Collections.unmodifiableList(artifacts);
-    }
+    Throwable errorCause();
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/package-info.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/call/CallOutputStatus.java
similarity index 86%
copy from modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/package-info.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/call/CallOutputStatus.java
index 5cb93f287..8d9b182ab 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/package-info.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/call/CallOutputStatus.java
@@ -15,8 +15,11 @@
  * limitations under the License.
  */
 
+package org.apache.ignite.cli.core.call;
+
 /**
- * Contains classes for Ignite module management.
+ * The status of the call.
  */
-
-package org.apache.ignite.cli.builtins.module;
+public enum CallOutputStatus {
+    SUCCESS, ERROR, EMPTY
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/call/DefaultCallOutput.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/call/DefaultCallOutput.java
new file mode 100644
index 000000000..9bbaf3934
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/call/DefaultCallOutput.java
@@ -0,0 +1,187 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.core.call;
+
+import java.util.Objects;
+
+/**
+ * Default implementation of {@link CallOutput} with {@link T} body.
+ */
+public class DefaultCallOutput<T> implements CallOutput<T> {
+
+    private final CallOutputStatus status;
+
+    private final T body;
+
+    private final Throwable cause;
+
+    private DefaultCallOutput(CallOutputStatus status, T body, Throwable cause) {
+        this.status = status;
+        this.body = body;
+        this.cause = cause;
+    }
+
+    @Override
+    public boolean hasError() {
+        return cause != null;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return CallOutputStatus.EMPTY.equals(status);
+    }
+
+    @Override
+    public Throwable errorCause() {
+        return cause;
+    }
+
+    @Override
+    public T body() {
+        return body;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        DefaultCallOutput<?> that = (DefaultCallOutput<?>) o;
+        return status == that.status && Objects.equals(body, that.body) && Objects.equals(cause, that.cause);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(status, body, cause);
+    }
+
+    @Override
+    public String toString() {
+        return "DefaultCallOutput{"
+            + "status="
+            + status
+            + ", body='"
+            + body + '\''
+            + ", cause="
+            + cause
+            + '}';
+    }
+
+    /**
+     * Builder method provider.
+     *
+     * @return new instance of {@link DefaultCallOutputBuilder}.
+     */
+    public static <T> DefaultCallOutputBuilder<T> builder() {
+        return new DefaultCallOutputBuilder<T>();
+    }
+
+    /**
+     * New successful call output with provided body.
+     *
+     * @param body for successful call output.
+     * @return Successful call output with provided body.
+     */
+    public static <T> DefaultCallOutput<T> success(T body) {
+        return DefaultCallOutput.<T>builder()
+                .status(CallOutputStatus.SUCCESS)
+                .body(body)
+                .build();
+    }
+
+    /**
+     * New failed call output with provided cause.
+     *
+     * @param cause error of failed call.
+     * @return Failed call output with provided cause.
+     */
+    public static <T> DefaultCallOutput<T> failure(Throwable cause) {
+        return DefaultCallOutput.<T>builder()
+            .status(CallOutputStatus.ERROR)
+            .cause(cause)
+            .build();
+    }
+
+    /**
+     * New empty coll output.
+     *
+     * @return Empty call output.
+     */
+    public static <T> DefaultCallOutput<T> empty() {
+        return DefaultCallOutput.<T>builder()
+                .status(CallOutputStatus.EMPTY)
+                .build();
+    }
+
+    /**
+     * Builder of {@link DefaultCallOutput}.
+     */
+    public static class DefaultCallOutputBuilder<T> {
+
+        private CallOutputStatus status;
+
+        private T body;
+
+        private Throwable cause;
+
+        /**
+         * Builder setter.
+         *
+         * @param status output status.
+         * @return invoked builder instance {@link DefaultCallOutputBuilder}.
+         */
+        public DefaultCallOutputBuilder<T> status(CallOutputStatus status) {
+            this.status = status;
+            return this;
+        }
+
+        /**
+         * Builder setter.
+         *
+         * @param body call output body.
+         * @return invoked builder instance {@link DefaultCallOutputBuilder}.
+         */
+        public DefaultCallOutputBuilder<T> body(T body) {
+            this.body = body;
+            return this;
+        }
+
+        /**
+         * Builder setter.
+         *
+         * @param cause exception cause.
+         * @return invoked builder instance {@link DefaultCallOutputBuilder}.
+         */
+        public DefaultCallOutputBuilder<T> cause(Throwable cause) {
+            this.cause = cause;
+            return this;
+        }
+
+        /**
+         * Build method.
+         *
+         * @return new {@link DefaultCallOutput} with field provided to builder.
+         */
+        public DefaultCallOutput<T> build() {
+            return new DefaultCallOutput<>(status, body, cause);
+        }
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/package-info.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/call/EmptyCallInput.java
similarity index 84%
copy from modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/package-info.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/call/EmptyCallInput.java
index 5cb93f287..3e866543e 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/package-info.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/call/EmptyCallInput.java
@@ -15,8 +15,10 @@
  * limitations under the License.
  */
 
+package org.apache.ignite.cli.core.call;
+
 /**
- * Contains classes for Ignite module management.
+ * Input for executing commands without arguments.
  */
-
-package org.apache.ignite.cli.builtins.module;
+public class EmptyCallInput implements CallInput {
+}
diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/AbstractCliTest.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/call/StatusCallInput.java
similarity index 72%
copy from modules/cli/src/test/java/org/apache/ignite/cli/AbstractCliTest.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/call/StatusCallInput.java
index 7387f57aa..74aab7f89 100644
--- a/modules/cli/src/test/java/org/apache/ignite/cli/AbstractCliTest.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/call/StatusCallInput.java
@@ -15,19 +15,19 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
-
-import org.junit.jupiter.api.BeforeAll;
+package org.apache.ignite.cli.core.call;
 
 /**
- * Base class for any CLI tests.
+ * Input for status call.
  */
-public abstract class AbstractCliTest {
-    /**
-     * Sets up a dumb terminal before tests.
-     */
-    @BeforeAll
-    static void beforeAll() {
-        System.setProperty("org.jline.terminal.dumb", "true");
+public class StatusCallInput implements CallInput {
+    private final String clusterUrl;
+
+    public StatusCallInput(String clusterUrl) {
+        this.clusterUrl = clusterUrl;
+    }
+
+    public String getClusterUrl() {
+        return clusterUrl;
     }
 }
diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/AbstractCliTest.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/call/StringCallInput.java
similarity index 67%
copy from modules/cli/src/test/java/org/apache/ignite/cli/AbstractCliTest.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/call/StringCallInput.java
index 7387f57aa..c54f73a4b 100644
--- a/modules/cli/src/test/java/org/apache/ignite/cli/AbstractCliTest.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/call/StringCallInput.java
@@ -15,19 +15,24 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
-
-import org.junit.jupiter.api.BeforeAll;
+package org.apache.ignite.cli.core.call;
 
 /**
- * Base class for any CLI tests.
+ * Input for executing commands with {@code String} arguments.
  */
-public abstract class AbstractCliTest {
+public class StringCallInput implements CallInput {
+    private final String string;
+
+    public StringCallInput(String string) {
+        this.string = string;
+    }
+
     /**
-     * Sets up a dumb terminal before tests.
+     * Argument getter.
+     *
+     * @return {@code String} argument.
      */
-    @BeforeAll
-    static void beforeAll() {
-        System.setProperty("org.jline.terminal.dumb", "true");
+    public String getString() {
+        return string;
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/ResolveResult.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/CommandExecutionException.java
similarity index 55%
copy from modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/ResolveResult.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/exception/CommandExecutionException.java
index 658f069a9..a15f76abf 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/ResolveResult.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/CommandExecutionException.java
@@ -15,34 +15,42 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.builtins.module;
-
-import java.nio.file.Path;
-import java.util.Collections;
-import java.util.List;
+package org.apache.ignite.cli.core.exception;
 
 /**
- * Result of resolving maven artifact dependencies.
+ * Command execution exception.
  */
-public class ResolveResult {
-    /** List of resolver artifacts' paths. */
-    private final List<Path> artifacts;
+public class CommandExecutionException extends RuntimeException {
+
+    private final String commandId;
+
+    private final String reason;
+
+    public CommandExecutionException(String commandId, String reason) {
+        this.commandId = commandId;
+        this.reason = reason;
+    }
 
     /**
-     * Creates result of artifacts resolving.
+     * Command identifier getter.
      *
-     * @param artifacts List of artifacts paths.
+     * @return command identifier.
      */
-    public ResolveResult(List<Path> artifacts) {
-        this.artifacts = artifacts;
+    public String getCommandId() {
+        return commandId;
     }
 
     /**
-     * Returns list of artifacts' paths.
+     * Exception reason getter.
      *
-     * @return List of artifacts' paths.
+     * @return exception reason.
      */
-    public List<Path> artifacts() {
-        return Collections.unmodifiableList(artifacts);
+    public String getReason() {
+        return reason;
+    }
+
+    @Override
+    public String toString() {
+        return super.toString();
     }
 }
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/ConnectCommandException.java
similarity index 64%
copy from modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/exception/ConnectCommandException.java
index 32fbdbfdb..140ddbe1a 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/ConnectCommandException.java
@@ -15,22 +15,29 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
-
-import java.util.logging.Handler;
-import java.util.logging.LogRecord;
+package org.apache.ignite.cli.core.exception;
 
 /**
- * Adapter for {@link Handler} with empty implementations of all methods but {@link Handler#publish(LogRecord)}.
+ * Connect command exception.
  */
-public abstract class NoOpHandler extends Handler {
-    @Override
-    public void flush() {
-        // no-op
+public class ConnectCommandException extends RuntimeException {
+    private final String reason;
+
+    public ConnectCommandException(String reason) {
+        this.reason = reason;
+    }
+
+    /**
+     * Exception reason getter.
+     *
+     * @return exception reason.
+     */
+    public String getReason() {
+        return reason;
     }
 
     @Override
-    public void close() throws SecurityException {
-        // no-op
+    public String toString() {
+        return super.toString();
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/ExceptionHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/ExceptionHandler.java
new file mode 100644
index 000000000..e16798b69
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/ExceptionHandler.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.core.exception;
+
+
+import org.apache.ignite.lang.IgniteLogger;
+
+/**
+ * General interface of exception handler.
+ *
+ * @param <T> exception type.
+ */
+public interface ExceptionHandler<T extends Throwable> {
+    IgniteLogger logger = IgniteLogger.forClass(ExceptionHandler.class);
+
+    ExceptionHandler<Throwable> DEFAULT = new ExceptionHandler<>() {
+        @Override
+        public void handle(ExceptionWriter err, Throwable e) {
+            logger.error("Unhandled exception ", e);
+            err.write("Internal error!");
+        }
+
+        @Override
+        public Class<Throwable> applicableException() {
+            return Throwable.class;
+        }
+    };
+
+    /**
+     * Handler method.
+     *
+     * @param err writer instance for any error messages.
+     * @param e exception instance.
+     */
+    void handle(ExceptionWriter err, T e);
+
+    /**
+     * Exception class getter.
+     *
+     * @return class of applicable exception for the handler.
+     */
+    Class<T> applicableException();
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/ExceptionHandlers.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/ExceptionHandlers.java
new file mode 100644
index 000000000..09437e679
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/ExceptionHandlers.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.core.exception;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Exception handlers collector class.
+ */
+public class ExceptionHandlers {
+    private final Map<Class<? extends Throwable>, ExceptionHandler<? extends Throwable>> map = new HashMap<>();
+    private final ExceptionHandler<Throwable> defaultHandler;
+
+    public ExceptionHandlers() {
+        this(ExceptionHandler.DEFAULT);
+    }
+
+    public ExceptionHandlers(ExceptionHandler<Throwable> defaultHandler) {
+        this.defaultHandler = defaultHandler;
+    }
+
+    /**
+     * Appends exception handler to collection.
+     * If collection already contains handler for type {@param <T>} it will be replaced by {@param exceptionHandler}.
+     *
+     * @param exceptionHandler handler instance.
+     * @param <T> exception type.
+     */
+    public <T extends Throwable> void addExceptionHandler(ExceptionHandler<T> exceptionHandler) {
+        map.put(exceptionHandler.applicableException(), exceptionHandler);
+    }
+
+    /**
+     * Append all exception handlers.
+     *
+     * @param exceptionHandlers handlers to append.
+     */
+    public void addExceptionHandlers(ExceptionHandlers exceptionHandlers) {
+        map.putAll(exceptionHandlers.map);
+    }
+
+    /**
+     * Handle method.
+     *
+     * @param errOutput error output.
+     * @param e exception instance.
+     * @param <T> exception type.
+     */
+    public <T extends Throwable> void handleException(ExceptionWriter errOutput, T e) {
+        processException(errOutput, e instanceof WrappedException ? e.getCause() : e);
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T extends Throwable> void processException(ExceptionWriter errOutput, T e) {
+        ExceptionHandler<T> exceptionHandler = (ExceptionHandler<T>) map.get(e.getClass());
+        if (exceptionHandler != null) {
+            exceptionHandler.handle(errOutput, e);
+        } else {
+            defaultHandler.handle(errOutput, e);
+        }
+    }
+
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/HttpClientFactory.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/ExceptionWriter.java
similarity index 62%
copy from modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/HttpClientFactory.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/exception/ExceptionWriter.java
index 178725423..ec85568bf 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/HttpClientFactory.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/ExceptionWriter.java
@@ -15,27 +15,28 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.builtins.config;
+package org.apache.ignite.cli.core.exception;
 
-import io.micronaut.context.annotation.Factory;
-import jakarta.inject.Singleton;
-import java.net.http.HttpClient;
+import java.io.PrintWriter;
 
 /**
- * Factory for producing simple HTTP clients.
+ * Writer for exception error messages.
  */
-@Factory
-public class HttpClientFactory {
+public interface ExceptionWriter {
     /**
-     * Creates new HTTP client.
+     * Write provided exception message.
      *
-     * @return HttpClient
+     * @param errMessage error message.
      */
-    @Singleton
-    HttpClient httpClient() {
-        return HttpClient
-                .newBuilder()
-                .version(HttpClient.Version.HTTP_1_1)
-                .build();
+    void write(String errMessage);
+
+    /**
+     * Helper mapper.
+     *
+     * @param pw {@link PrintWriter} instance.
+     * @return {@link ExceptionWriter} instance based on {@param pw}.
+     */
+    static ExceptionWriter fromPrintWriter(PrintWriter pw) {
+        return pw::println;
     }
 }
diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/AbstractCliTest.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/WrappedException.java
similarity index 72%
copy from modules/cli/src/test/java/org/apache/ignite/cli/AbstractCliTest.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/exception/WrappedException.java
index 7387f57aa..0fd46f1a0 100644
--- a/modules/cli/src/test/java/org/apache/ignite/cli/AbstractCliTest.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/WrappedException.java
@@ -15,19 +15,19 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
-
-import org.junit.jupiter.api.BeforeAll;
+package org.apache.ignite.cli.core.exception;
 
 /**
- * Base class for any CLI tests.
+ * Wrapper for checked exception.
+ * This exception will be handled as cause type.
  */
-public abstract class AbstractCliTest {
+public class WrappedException extends RuntimeException {
     /**
-     * Sets up a dumb terminal before tests.
+     * Constructor.
+     *
+     * @param cause cause exception.
      */
-    @BeforeAll
-    static void beforeAll() {
-        System.setProperty("org.jline.terminal.dumb", "true");
+    public WrappedException(Throwable cause) {
+        super(cause);
     }
 }
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ApiExceptionHandler.java
similarity index 60%
copy from modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ApiExceptionHandler.java
index 32fbdbfdb..f581f579a 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ApiExceptionHandler.java
@@ -15,22 +15,23 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.core.exception.handler;
 
-import java.util.logging.Handler;
-import java.util.logging.LogRecord;
+import org.apache.ignite.cli.core.exception.ExceptionHandler;
+import org.apache.ignite.cli.core.exception.ExceptionWriter;
+import org.apache.ignite.rest.client.invoker.ApiException;
 
 /**
- * Adapter for {@link Handler} with empty implementations of all methods but {@link Handler#publish(LogRecord)}.
+ * Exception handler for {@link ApiException}.
  */
-public abstract class NoOpHandler extends Handler {
+public class ApiExceptionHandler implements ExceptionHandler<ApiException> {
     @Override
-    public void flush() {
-        // no-op
+    public void handle(ExceptionWriter err, ApiException e) {
+        err.write("Api error: " + e.getCause());
     }
 
     @Override
-    public void close() throws SecurityException {
-        // no-op
+    public Class<ApiException> applicableException() {
+        return ApiException.class;
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/CommandExecutionExceptionHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/CommandExecutionExceptionHandler.java
new file mode 100644
index 000000000..36240644f
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/CommandExecutionExceptionHandler.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.core.exception.handler;
+
+import org.apache.ignite.cli.core.exception.CommandExecutionException;
+import org.apache.ignite.cli.core.exception.ExceptionHandler;
+import org.apache.ignite.cli.core.exception.ExceptionWriter;
+import org.apache.ignite.lang.IgniteLogger;
+
+/**
+ * Exception handler for {@link CommandExecutionException}.
+ */
+public class CommandExecutionExceptionHandler implements ExceptionHandler<CommandExecutionException> {
+    private static final IgniteLogger log = IgniteLogger.forClass(CommandExecutionExceptionHandler.class);
+
+    @Override
+    public void handle(ExceptionWriter err, CommandExecutionException e) {
+        log.error("Command {} failed with reason {}", e.getCommandId(), e.getReason(), e);
+        err.write(String.format("Command %s failed with reason: %s", e.getCommandId(), e.getReason()));
+    }
+
+    @Override
+    public Class<CommandExecutionException> applicableException() {
+        return CommandExecutionException.class;
+    }
+}
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ConnectCommandExceptionHandler.java
similarity index 57%
copy from modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ConnectCommandExceptionHandler.java
index 32fbdbfdb..2519fda17 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ConnectCommandExceptionHandler.java
@@ -15,22 +15,23 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.core.exception.handler;
 
-import java.util.logging.Handler;
-import java.util.logging.LogRecord;
+import org.apache.ignite.cli.core.exception.ConnectCommandException;
+import org.apache.ignite.cli.core.exception.ExceptionHandler;
+import org.apache.ignite.cli.core.exception.ExceptionWriter;
 
 /**
- * Adapter for {@link Handler} with empty implementations of all methods but {@link Handler#publish(LogRecord)}.
+ * Exception handler for {@link ConnectCommandException}.
  */
-public abstract class NoOpHandler extends Handler {
+public class ConnectCommandExceptionHandler implements ExceptionHandler<ConnectCommandException> {
     @Override
-    public void flush() {
-        // no-op
+    public void handle(ExceptionWriter err, ConnectCommandException e) {
+        err.write(e.getReason());
     }
 
     @Override
-    public void close() throws SecurityException {
-        // no-op
+    public Class<ConnectCommandException> applicableException() {
+        return ConnectCommandException.class;
     }
 }
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ConnectExceptionHandler.java
similarity index 60%
copy from modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ConnectExceptionHandler.java
index 32fbdbfdb..516e2e6a9 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ConnectExceptionHandler.java
@@ -15,22 +15,23 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.core.exception.handler;
 
-import java.util.logging.Handler;
-import java.util.logging.LogRecord;
+import java.net.ConnectException;
+import org.apache.ignite.cli.core.exception.ExceptionHandler;
+import org.apache.ignite.cli.core.exception.ExceptionWriter;
 
 /**
- * Adapter for {@link Handler} with empty implementations of all methods but {@link Handler#publish(LogRecord)}.
+ * Exception handler for {@link ConnectException}.
  */
-public abstract class NoOpHandler extends Handler {
+public class ConnectExceptionHandler implements ExceptionHandler<ConnectException> {
     @Override
-    public void flush() {
-        // no-op
+    public void handle(ExceptionWriter err, ConnectException e) {
+        err.write("Connection failed " + e.getMessage());
     }
 
     @Override
-    public void close() throws SecurityException {
-        // no-op
+    public Class<ConnectException> applicableException() {
+        return ConnectException.class;
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/DefaultExceptionHandlers.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/DefaultExceptionHandlers.java
new file mode 100644
index 000000000..175fb7e71
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/DefaultExceptionHandlers.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.core.exception.handler;
+
+import org.apache.ignite.cli.core.exception.ExceptionHandlers;
+
+/**
+ * Default collection of exception handlers.
+ */
+public class DefaultExceptionHandlers extends ExceptionHandlers {
+
+    /**
+     * Constructor.
+     */
+    public DefaultExceptionHandlers() {
+        addExceptionHandler(new SqlExceptionHandler());
+        addExceptionHandler(new ConnectCommandExceptionHandler());
+        addExceptionHandler(new CommandExecutionExceptionHandler());
+        addExceptionHandler(new TimeoutExceptionHandler());
+        addExceptionHandler(new IgniteClientExceptionHandler());
+        addExceptionHandler(new IgniteCliExceptionHandler());
+        addExceptionHandler(new ConnectExceptionHandler());
+        addExceptionHandler(new ApiExceptionHandler());
+        addExceptionHandler(new UnknownCommandExceptionHandler());
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/EndOfFileExceptionHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/EndOfFileExceptionHandler.java
new file mode 100644
index 000000000..64d1f85ff
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/EndOfFileExceptionHandler.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.core.exception.handler;
+
+import java.util.function.Consumer;
+import org.apache.ignite.cli.core.exception.ExceptionHandler;
+import org.apache.ignite.cli.core.exception.ExceptionWriter;
+import org.jline.reader.EndOfFileException;
+import org.jline.reader.LineReader;
+
+/**
+ * Exception handler for {@link EndOfFileException}.
+ * From {@link EndOfFileException}
+ * <p>
+ *     This exception is thrown by {@link LineReader#readLine} when the user types ctrl-D)
+ * </p>
+ * This handler call {@param endAction} to stop some process.
+ */
+public class EndOfFileExceptionHandler implements ExceptionHandler<EndOfFileException> {
+    private final Consumer<Boolean> endAction;
+
+    /**
+     * Constructor.
+     *
+     * @param endAction handle action.
+     */
+    public EndOfFileExceptionHandler(Consumer<Boolean> endAction) {
+        this.endAction = endAction;
+    }
+
+    @Override
+    public void handle(ExceptionWriter err, EndOfFileException e) {
+        endAction.accept(true);
+    }
+
+    @Override
+    public Class<EndOfFileException> applicableException() {
+        return EndOfFileException.class;
+    }
+}
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/IgniteCliExceptionHandler.java
similarity index 59%
copy from modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/IgniteCliExceptionHandler.java
index 32fbdbfdb..03cf5a72b 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/IgniteCliExceptionHandler.java
@@ -15,22 +15,23 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.core.exception.handler;
 
-import java.util.logging.Handler;
-import java.util.logging.LogRecord;
+import org.apache.ignite.cli.core.exception.ExceptionHandler;
+import org.apache.ignite.cli.core.exception.ExceptionWriter;
+import org.apache.ignite.cli.deprecated.IgniteCliException;
 
 /**
- * Adapter for {@link Handler} with empty implementations of all methods but {@link Handler#publish(LogRecord)}.
+ * Exception handler for {@link IgniteCliException}.
  */
-public abstract class NoOpHandler extends Handler {
+public class IgniteCliExceptionHandler implements ExceptionHandler<IgniteCliException> {
     @Override
-    public void flush() {
-        // no-op
+    public void handle(ExceptionWriter err, IgniteCliException e) {
+        err.write(e.getMessage());
     }
 
     @Override
-    public void close() throws SecurityException {
-        // no-op
+    public Class<IgniteCliException> applicableException() {
+        return IgniteCliException.class;
     }
 }
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/IgniteClientExceptionHandler.java
similarity index 50%
copy from modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/IgniteClientExceptionHandler.java
index 32fbdbfdb..765d3e280 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/IgniteClientExceptionHandler.java
@@ -15,22 +15,27 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.core.exception.handler;
 
-import java.util.logging.Handler;
-import java.util.logging.LogRecord;
+import org.apache.ignite.cli.core.exception.ExceptionHandler;
+import org.apache.ignite.cli.core.exception.ExceptionWriter;
+import org.apache.ignite.client.IgniteClientException;
+import org.apache.ignite.lang.IgniteLogger;
 
 /**
- * Adapter for {@link Handler} with empty implementations of all methods but {@link Handler#publish(LogRecord)}.
+ * Exception handler for {@link IgniteClientException}.
  */
-public abstract class NoOpHandler extends Handler {
+public class IgniteClientExceptionHandler implements ExceptionHandler<IgniteClientException> {
+    private static final IgniteLogger log = IgniteLogger.forClass(IgniteClientExceptionHandler.class);
+
     @Override
-    public void flush() {
-        // no-op
+    public void handle(ExceptionWriter err, IgniteClientException e) {
+        log.error("Ignite client exception", e);
+        err.write("Ignite client exception with code: " + e.errorCode());
     }
 
     @Override
-    public void close() throws SecurityException {
-        // no-op
+    public Class<IgniteClientException> applicableException() {
+        return IgniteClientException.class;
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/PicocliExecutionExceptionHandler.java
similarity index 54%
copy from modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/PicocliExecutionExceptionHandler.java
index 2e0050be3..939db3c14 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/CommandSpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/PicocliExecutionExceptionHandler.java
@@ -15,15 +15,22 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.spec;
+package org.apache.ignite.cli.core.exception.handler;
 
+import org.apache.ignite.cli.core.exception.ExceptionHandlers;
 import picocli.CommandLine;
+import picocli.CommandLine.IExecutionExceptionHandler;
+import picocli.CommandLine.ParseResult;
 
 /**
- * Base class for commands without any subcommands.
+ * Implementation of {@link IExecutionExceptionHandler} based on {@link ExceptionHandlers}.
  */
-public abstract class CommandSpec extends SpecAdapter {
-    /** Help option specification. */
-    @CommandLine.Option(names = "--help", usageHelp = true, hidden = true)
-    protected boolean usageHelpRequested;
+public class PicocliExecutionExceptionHandler implements IExecutionExceptionHandler {
+    private final ExceptionHandlers exceptionHandlers = new DefaultExceptionHandlers();
+
+    @Override
+    public int handleExecutionException(Exception ex, CommandLine commandLine, ParseResult parseResult) {
+        exceptionHandlers.handleException(System.err::println, ex);
+        return 1;
+    }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/HttpClientFactory.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ReplExceptionHandlers.java
similarity index 62%
copy from modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/HttpClientFactory.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ReplExceptionHandlers.java
index 178725423..dbae73a2b 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/HttpClientFactory.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ReplExceptionHandlers.java
@@ -15,27 +15,23 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.builtins.config;
+package org.apache.ignite.cli.core.exception.handler;
 
-import io.micronaut.context.annotation.Factory;
-import jakarta.inject.Singleton;
-import java.net.http.HttpClient;
+import java.util.function.Consumer;
+import org.apache.ignite.cli.core.exception.ExceptionHandlers;
 
 /**
- * Factory for producing simple HTTP clients.
+ * Collection of exception handlers for REPL.
  */
-@Factory
-public class HttpClientFactory {
+public class ReplExceptionHandlers extends ExceptionHandlers {
+
     /**
-     * Creates new HTTP client.
+     * Constructor.
      *
-     * @return HttpClient
+     * @param stop REPL stop action.
      */
-    @Singleton
-    HttpClient httpClient() {
-        return HttpClient
-                .newBuilder()
-                .version(HttpClient.Version.HTTP_1_1)
-                .build();
+    public ReplExceptionHandlers(Consumer<Boolean> stop) {
+        addExceptionHandler(new EndOfFileExceptionHandler(stop));
+        addExceptionHandler(new UserInterruptExceptionHandler());
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/SqlExceptionHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/SqlExceptionHandler.java
new file mode 100644
index 000000000..3f60ad941
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/SqlExceptionHandler.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.core.exception.handler;
+
+import java.sql.SQLException;
+import org.apache.ignite.cli.core.exception.ExceptionHandler;
+import org.apache.ignite.cli.core.exception.ExceptionWriter;
+import org.apache.ignite.internal.jdbc.proto.SqlStateCode;
+import org.apache.ignite.lang.IgniteLogger;
+
+/**
+ * Exception handler for {@link SQLException}.
+ */
+public class SqlExceptionHandler implements ExceptionHandler<SQLException> {
+    private static final IgniteLogger log = IgniteLogger.forClass(SqlExceptionHandler.class);
+
+    public static final String PARSING_ERROR_MESSAGE = "SQL query parsing error: %s";
+
+    public static final String INVALID_PARAMETER_MESSAGE = "Invalid parameter value.";
+
+    public static final String CLIENT_CONNECTION_FAILED_MESSAGE = "Connection failed.";
+
+    public static final String CONNECTION_BROKE_MESSAGE = "Connection error.";
+
+    @Override
+    public void handle(ExceptionWriter err, SQLException e) {
+        switch (e.getSQLState()) {
+            case SqlStateCode.CONNECTION_FAILURE:
+            case SqlStateCode.CONNECTION_CLOSED:
+            case SqlStateCode.CONNECTION_REJECTED:
+                err.write(CONNECTION_BROKE_MESSAGE);
+                break;
+            case SqlStateCode.PARSING_EXCEPTION:
+                err.write(String.format(PARSING_ERROR_MESSAGE, e.getMessage()));
+                break;
+            case SqlStateCode.INVALID_PARAMETER_VALUE:
+                err.write(INVALID_PARAMETER_MESSAGE);
+                break;
+            case SqlStateCode.CLIENT_CONNECTION_FAILED:
+                err.write(CLIENT_CONNECTION_FAILED_MESSAGE);
+                break;
+            default:
+                log.error("Unrecognized error ", e);
+                err.write("Unrecognized error while process SQL query.");
+        }
+    }
+
+    @Override
+    public Class<SQLException> applicableException() {
+        return SQLException.class;
+    }
+}
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/TimeoutExceptionHandler.java
similarity index 52%
copy from modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/TimeoutExceptionHandler.java
index 32fbdbfdb..86a6881ba 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/TimeoutExceptionHandler.java
@@ -15,22 +15,27 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.core.exception.handler;
 
-import java.util.logging.Handler;
-import java.util.logging.LogRecord;
+import java.util.concurrent.TimeoutException;
+import org.apache.ignite.cli.core.exception.ExceptionHandler;
+import org.apache.ignite.cli.core.exception.ExceptionWriter;
+import org.apache.ignite.lang.IgniteLogger;
 
 /**
- * Adapter for {@link Handler} with empty implementations of all methods but {@link Handler#publish(LogRecord)}.
+ * Exception handler for {@link TimeoutException}.
  */
-public abstract class NoOpHandler extends Handler {
+public class TimeoutExceptionHandler implements ExceptionHandler<TimeoutException> {
+    private static final IgniteLogger log = IgniteLogger.forClass(TimeoutExceptionHandler.class);
+
     @Override
-    public void flush() {
-        // no-op
+    public void handle(ExceptionWriter err, TimeoutException e) {
+        log.error("Timeout exception ", e);
+        err.write("Command failed with timeout.");
     }
 
     @Override
-    public void close() throws SecurityException {
-        // no-op
+    public Class<TimeoutException> applicableException() {
+        return TimeoutException.class;
     }
 }
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/UnknownCommandExceptionHandler.java
similarity index 52%
copy from modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/UnknownCommandExceptionHandler.java
index 32fbdbfdb..989d05055 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/UnknownCommandExceptionHandler.java
@@ -15,22 +15,25 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.core.exception.handler;
 
-import java.util.logging.Handler;
-import java.util.logging.LogRecord;
+import org.apache.ignite.cli.core.exception.ExceptionHandler;
+import org.apache.ignite.cli.core.exception.ExceptionWriter;
+import org.jline.console.impl.SystemRegistryImpl.UnknownCommandException;
 
 /**
- * Adapter for {@link Handler} with empty implementations of all methods but {@link Handler#publish(LogRecord)}.
+ * Exception handler for {@link UnknownCommandException}.
+ * This exception is thrown by {@link org.jline.console.SystemRegistry#execute(String)} when the user types invalid or unknown command.
  */
-public abstract class NoOpHandler extends Handler {
+public class UnknownCommandExceptionHandler implements ExceptionHandler<UnknownCommandException> {
+
     @Override
-    public void flush() {
-        // no-op
+    public void handle(ExceptionWriter err, UnknownCommandException e) {
+        err.write(e.getMessage());
     }
 
     @Override
-    public void close() throws SecurityException {
-        // no-op
+    public Class<UnknownCommandException> applicableException() {
+        return UnknownCommandException.class;
     }
 }
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/UserInterruptExceptionHandler.java
similarity index 59%
copy from modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/UserInterruptExceptionHandler.java
index 32fbdbfdb..46da1ee2e 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/UserInterruptExceptionHandler.java
@@ -15,22 +15,23 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.core.exception.handler;
 
-import java.util.logging.Handler;
-import java.util.logging.LogRecord;
+import org.apache.ignite.cli.core.exception.ExceptionHandler;
+import org.apache.ignite.cli.core.exception.ExceptionWriter;
+import org.jline.reader.UserInterruptException;
 
 /**
- * Adapter for {@link Handler} with empty implementations of all methods but {@link Handler#publish(LogRecord)}.
+ * Exception handler for {@link UserInterruptException}.
  */
-public abstract class NoOpHandler extends Handler {
+public class UserInterruptExceptionHandler implements ExceptionHandler<UserInterruptException> {
     @Override
-    public void flush() {
-        // no-op
+    public void handle(ExceptionWriter err, UserInterruptException e) {
+        //NOOP
     }
 
     @Override
-    public void close() throws SecurityException {
-        // no-op
+    public Class<UserInterruptException> applicableException() {
+        return UserInterruptException.class;
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/Repl.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/Repl.java
new file mode 100644
index 000000000..78e0f7fd5
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/Repl.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.core.repl;
+
+import java.util.Map;
+import org.apache.ignite.cli.core.CallExecutionPipelineProvider;
+import org.apache.ignite.cli.core.call.CallExecutionPipeline;
+import org.apache.ignite.cli.core.exception.ExceptionHandlers;
+import org.apache.ignite.cli.core.repl.executor.RegistryCommandExecutor;
+import org.apache.ignite.cli.core.repl.prompt.PromptProvider;
+import org.apache.ignite.cli.core.repl.terminal.TerminalCustomizer;
+import org.jline.reader.Completer;
+import org.jline.terminal.Terminal;
+import picocli.CommandLine.IDefaultValueProvider;
+
+/**
+ * Data class with all information about REPL.
+ */
+public class Repl {
+
+    private final PromptProvider promptProvider;
+
+    private final Map<String, String> aliases;
+
+    private final TerminalCustomizer terminalCustomizer;
+
+    private final Class<?> commandClass;
+
+    private final IDefaultValueProvider defaultValueProvider;
+
+    private final CallExecutionPipelineProvider provider;
+
+    private final Completer completer;
+
+    private final String historyFileName;
+
+    private final boolean tailTipWidgetsEnabled;
+
+    /**
+     * Constructor.
+     *
+     * @param promptProvider REPL prompt provider.
+     * @param commandClass top level command class.
+     * @param defaultValueProvider default value provider.
+     * @param aliases map of aliases for commands.
+     * @param terminalCustomizer customizer of terminal.
+     * @param provider default call execution pipeline provider.
+     * @param completer completer instance.
+     * @param historyFileName file name for storing commands history.
+     * @param tailTipWidgetsEnabled whether tailtip widgets are enabled.
+     */
+    public Repl(PromptProvider promptProvider,
+            Class<?> commandClass,
+            IDefaultValueProvider defaultValueProvider,
+            Map<String, String> aliases,
+            TerminalCustomizer terminalCustomizer,
+            CallExecutionPipelineProvider provider,
+            Completer completer,
+            String historyFileName,
+            boolean tailTipWidgetsEnabled
+    ) {
+        this.promptProvider = promptProvider;
+        this.commandClass = commandClass;
+        this.defaultValueProvider = defaultValueProvider;
+        this.aliases = aliases;
+        this.terminalCustomizer = terminalCustomizer;
+        this.provider = provider;
+        this.completer = completer;
+        this.historyFileName = historyFileName;
+        this.tailTipWidgetsEnabled = tailTipWidgetsEnabled;
+    }
+
+    /**
+     * Builder provider method.
+     *
+     * @return new instance of builder {@link ReplBuilder}.
+     */
+    public static ReplBuilder builder() {
+        return new ReplBuilder();
+    }
+
+    public PromptProvider getPromptProvider() {
+        return promptProvider;
+    }
+
+    /**
+     * Getter for {@code commandClass} field.
+     *
+     * @return class with top level command.
+     */
+    public Class<?> commandClass() {
+        return commandClass;
+    }
+
+    /**
+     * Getter for {@code defaultValueProvider} field.
+     *
+     * @return default value provider.
+     */
+    public IDefaultValueProvider defaultValueProvider() {
+        return defaultValueProvider;
+    }
+
+    /**
+     * Getter for {@code aliases} field.
+     *
+     * @return map of command aliases.
+     */
+    public Map<String, String> getAliases() {
+        return aliases;
+    }
+
+    /**
+     * Method for {@param terminal} customization.
+     */
+    public void customizeTerminal(Terminal terminal) {
+        terminalCustomizer.customize(terminal);
+    }
+
+    public CallExecutionPipeline<?, ?> getPipeline(RegistryCommandExecutor executor, ExceptionHandlers exceptionHandlers, String line) {
+        return provider.get(executor, exceptionHandlers, line);
+    }
+
+    public Completer getCompleter() {
+        return completer;
+    }
+
+    public String getHistoryFileName() {
+        return historyFileName;
+    }
+
+    public boolean isTailTipWidgetsEnabled() {
+        return tailTipWidgetsEnabled;
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/ReplBuilder.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/ReplBuilder.java
new file mode 100644
index 000000000..838a8f076
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/ReplBuilder.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.core.repl;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.ignite.cli.core.CallExecutionPipelineProvider;
+import org.apache.ignite.cli.core.repl.prompt.PromptProvider;
+import org.apache.ignite.cli.core.repl.terminal.TerminalCustomizer;
+import org.jline.reader.Completer;
+import picocli.CommandLine.IDefaultValueProvider;
+
+/**
+ * Builder of {@link Repl}.
+ */
+public class ReplBuilder {
+
+    private PromptProvider promptProvider;
+
+    private final Map<String, String> aliases = new HashMap<>();
+
+    private Class<?> commandClass;
+
+    private IDefaultValueProvider defaultValueProvider;
+
+    private TerminalCustomizer terminalCustomizer = terminal -> {
+    };
+
+    private Completer completer;
+
+    private CallExecutionPipelineProvider provider;
+
+    private String historyFileName;
+
+    private boolean tailTipWidgetsEnabled;
+
+    /**
+     * Build methods.
+     *
+     * @return new instance of {@link Repl}.
+     */
+    public Repl build() {
+        return new Repl(
+                promptProvider,
+                commandClass,
+                defaultValueProvider,
+                aliases,
+                terminalCustomizer,
+                provider,
+                completer,
+                historyFileName,
+                tailTipWidgetsEnabled
+        );
+    }
+
+    public ReplBuilder withPromptProvider(PromptProvider promptProvider) {
+        this.promptProvider = promptProvider;
+        return this;
+    }
+
+    /**
+     * Builder setter of {@code commandClass} field.
+     *
+     * @param commandClass class with top level command.
+     * @return invoked builder instance {@link ReplBuilder}.
+     */
+    public ReplBuilder withCommandClass(Class<?> commandClass) {
+        this.commandClass = commandClass;
+        return this;
+    }
+
+    /**
+     * Builder setter of {@code defaultValueProvider} field.
+     *
+     * @param defaultValueProvider default value provider.
+     * @return invoked builder instance {@link ReplBuilder}.
+     */
+    public ReplBuilder withDefaultValueProvider(IDefaultValueProvider defaultValueProvider) {
+        this.defaultValueProvider = defaultValueProvider;
+        return this;
+    }
+
+    /**
+     * Builder setter of {@code aliases} field.
+     *
+     * @param aliases map of aliases for commands.
+     * @return invoked builder instance {@link ReplBuilder}.
+     */
+    public ReplBuilder withAliases(Map<String, String> aliases) {
+        this.aliases.putAll(aliases);
+        return this;
+    }
+
+    /**
+     * Builder setter of {@code terminalCustomizer} field.
+     *
+     * @param terminalCustomizer customizer of terminal {@link org.jline.terminal.Terminal}.
+     * @return invoked builder instance {@link ReplBuilder}.
+     */
+    public ReplBuilder withTerminalCustomizer(TerminalCustomizer terminalCustomizer) {
+        this.terminalCustomizer = terminalCustomizer;
+        return this;
+    }
+
+    public ReplBuilder withCompleter(Completer completer) {
+        this.completer = completer;
+        return this;
+    }
+
+    public ReplBuilder withCallExecutionPipelineProvider(CallExecutionPipelineProvider provider) {
+        this.provider = provider;
+        return this;
+    }
+
+    public ReplBuilder withHistoryFileName(String historyFileName) {
+        this.historyFileName = historyFileName;
+        return this;
+    }
+
+    public ReplBuilder withTailTipWidgets() {
+        this.tailTipWidgetsEnabled = true;
+        return this;
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/Session.java
similarity index 53%
copy from modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/repl/Session.java
index 9099a22db..c42147a46 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/Session.java
@@ -15,36 +15,44 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.core.repl;
 
-import io.micronaut.core.annotation.Introspected;
-import jakarta.inject.Inject;
 import jakarta.inject.Singleton;
-import picocli.CommandLine;
 
 /**
- * Version provider for Picocli interactions.
+ * Connection session that in fact is holder for state: connected or disconnected.
+ * Also has a nodeUrl if the state is connected.
  */
 @Singleton
-@Introspected
-public class VersionProvider implements CommandLine.IVersionProvider {
-
-    /** Actual Ignite CLI version info. */
-    private final CliVersionInfo cliVerInfo;
-
-    /**
-     * Creates version provider.
-     *
-     * @param cliVerInfo Actual Ignite CLI version container.
-     */
-    @Inject
-    public VersionProvider(CliVersionInfo cliVerInfo) {
-        this.cliVerInfo = cliVerInfo;
+public class Session {
+
+    private boolean connectedToNode;
+
+    private String nodeUrl;
+
+    private String jdbcUrl;
+
+    public boolean isConnectedToNode() {
+        return connectedToNode;
+    }
+
+    public void setConnectedToNode(boolean connectedToNode) {
+        this.connectedToNode = connectedToNode;
+    }
+
+    public String getNodeUrl() {
+        return nodeUrl;
+    }
+
+    public void setNodeUrl(String nodeUrl) {
+        this.nodeUrl = nodeUrl;
+    }
+
+    public String getJdbcUrl() {
+        return jdbcUrl;
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public String[] getVersion() {
-        return new String[]{"Apache Ignite CLI ver. " + cliVerInfo.ver};
+    public void setJdbcUrl(String jdbcUrl) {
+        this.jdbcUrl = jdbcUrl;
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/SessionDefaultValueProvider.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/SessionDefaultValueProvider.java
new file mode 100644
index 000000000..68541cfd3
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/SessionDefaultValueProvider.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.core.repl;
+
+import jakarta.inject.Singleton;
+import java.util.Objects;
+import org.apache.ignite.cli.config.Config;
+import picocli.CommandLine.IDefaultValueProvider;
+import picocli.CommandLine.Model.ArgSpec;
+import picocli.CommandLine.PropertiesDefaultProvider;
+
+/**
+ * Implementation of {@link IDefaultValueProvider} based on {@link Session}.
+ */
+@Singleton
+public class SessionDefaultValueProvider implements IDefaultValueProvider {
+
+    private final Session session;
+
+    private final IDefaultValueProvider defaultValueProvider;
+
+    /**
+     * Constructor.
+     *
+     * @param session session instance.
+     * @param config config instance.
+     */
+    public SessionDefaultValueProvider(Session session, Config config) {
+        this.session = session;
+        this.defaultValueProvider = new PropertiesDefaultProvider(config.getProperties());
+    }
+
+    @Override
+    public String defaultValue(ArgSpec argSpec) throws Exception {
+        if (session.isConnectedToNode()) {
+            if (Objects.equals(argSpec.descriptionKey(), "ignite.jdbc-url")) {
+                return session.getJdbcUrl();
+            }
+        }
+        return defaultValueProvider.defaultValue(argSpec);
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/package-info.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/config/ClientConnectorConfig.java
similarity index 73%
copy from modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/package-info.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/repl/config/ClientConnectorConfig.java
index 5cb93f287..60d92eada 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/package-info.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/config/ClientConnectorConfig.java
@@ -15,8 +15,16 @@
  * limitations under the License.
  */
 
+package org.apache.ignite.cli.core.repl.config;
+
+import org.apache.ignite.client.IgniteClientConfiguration;
+
 /**
- * Contains classes for Ignite module management.
+ * DTO class for client connector config.
  */
-
-package org.apache.ignite.cli.builtins.module;
+public class ClientConnectorConfig {
+    /**
+     * Ignite client port.
+     */
+    public int port = IgniteClientConfiguration.DFLT_PORT;
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/package-info.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/config/RootConfig.java
similarity index 79%
copy from modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/package-info.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/repl/config/RootConfig.java
index 5cb93f287..8c58aaaf0 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/package-info.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/config/RootConfig.java
@@ -15,8 +15,14 @@
  * limitations under the License.
  */
 
+package org.apache.ignite.cli.core.repl.config;
+
 /**
- * Contains classes for Ignite module management.
+ * DTO class for node configuration JSON.
  */
-
-package org.apache.ignite.cli.builtins.module;
+public class RootConfig {
+    /**
+     * Client connector part.
+     */
+    public ClientConnectorConfig clientConnector;
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/executor/RegistryCommandExecutor.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/executor/RegistryCommandExecutor.java
new file mode 100644
index 000000000..5077fde67
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/executor/RegistryCommandExecutor.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.core.repl.executor;
+
+
+import org.apache.ignite.cli.core.call.Call;
+import org.apache.ignite.cli.core.call.CallOutput;
+import org.apache.ignite.cli.core.call.DefaultCallOutput;
+import org.apache.ignite.cli.core.call.StringCallInput;
+import org.jline.console.SystemRegistry;
+
+/**
+ * Command executor based on {@link SystemRegistry}.
+ */
+public class RegistryCommandExecutor implements Call<StringCallInput, Object> {
+    private final SystemRegistry systemRegistry;
+
+    /**
+     * Constructor.
+     *
+     * @param systemRegistry {@link SystemRegistry} instance.
+     */
+    public RegistryCommandExecutor(SystemRegistry systemRegistry) {
+        this.systemRegistry = systemRegistry;
+    }
+
+    /**
+     * Executor method.
+     *
+     * @param input processed command line.
+     * @return Command output.
+     */
+    @Override
+    public CallOutput<Object> execute(StringCallInput input) {
+        try {
+            Object executionResult = systemRegistry.execute(input.getString());
+            if (executionResult == null) {
+                return DefaultCallOutput.empty();
+            }
+
+            return DefaultCallOutput.success(executionResult);
+        } catch (Exception e) {
+            return DefaultCallOutput.failure(e);
+        }
+    }
+
+    /**
+     * Clean up {@link SystemRegistry}.
+     */
+    public void cleanUp() {
+        systemRegistry.cleanUp();
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/executor/ReplExecutor.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/executor/ReplExecutor.java
new file mode 100644
index 000000000..4ab85fd74
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/executor/ReplExecutor.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.core.repl.executor;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
+import org.apache.ignite.cli.config.Config;
+import org.apache.ignite.cli.core.exception.handler.PicocliExecutionExceptionHandler;
+import org.apache.ignite.cli.core.exception.handler.ReplExceptionHandlers;
+import org.apache.ignite.cli.core.repl.Repl;
+import org.apache.ignite.cli.core.repl.expander.NoopExpander;
+import org.jline.console.impl.SystemRegistryImpl;
+import org.jline.reader.Completer;
+import org.jline.reader.LineReader;
+import org.jline.reader.LineReader.SuggestionType;
+import org.jline.reader.LineReaderBuilder;
+import org.jline.reader.MaskingCallback;
+import org.jline.reader.Parser;
+import org.jline.reader.impl.DefaultParser;
+import org.jline.terminal.Terminal;
+import org.jline.widget.TailTipWidgets;
+import picocli.CommandLine;
+import picocli.CommandLine.Help.Ansi;
+import picocli.CommandLine.IDefaultValueProvider;
+import picocli.shell.jline3.PicocliCommands;
+import picocli.shell.jline3.PicocliCommands.PicocliCommandsFactory;
+
+/**
+ * Executor of {@link Repl}.
+ */
+public class ReplExecutor {
+
+    private final Parser parser = new DefaultParser();
+
+    private final Supplier<Path> workDirProvider = () -> Paths.get(System.getProperty("user.dir"));
+
+    private final AtomicBoolean interrupted = new AtomicBoolean();
+
+    private final ReplExceptionHandlers exceptionHandlers = new ReplExceptionHandlers(interrupted::set);
+
+    private final PicocliCommandsFactory factory;
+
+    private final Terminal terminal;
+
+    /**
+     * Constructor.
+     *
+     * @param commandsFactory picocli commands factory.
+     * @param terminal terminal instance.
+     */
+    public ReplExecutor(PicocliCommandsFactory commandsFactory, Terminal terminal) {
+        this.factory = commandsFactory;
+        this.terminal = terminal;
+    }
+
+    /**
+     * Executor method. This is thread blocking method, until REPL stop executing.
+     *
+     * @param repl data class of executing REPL.
+     */
+    public void execute(Repl repl) {
+        try {
+            repl.customizeTerminal(terminal);
+
+            PicocliCommands picocliCommands = createPicocliCommands(repl);
+            SystemRegistryImpl registry = new SystemRegistryImpl(parser, terminal, workDirProvider, null);
+            registry.setCommandRegistries(picocliCommands);
+            registry.register("help", picocliCommands);
+
+            LineReader reader = createReader(repl.getCompleter() != null
+                    ? repl.getCompleter()
+                    : registry.completer());
+            if (repl.getHistoryFileName() != null) {
+                reader.variable(LineReader.HISTORY_FILE, new File(Config.getStateFolder(), repl.getHistoryFileName()));
+            }
+
+            RegistryCommandExecutor executor = new RegistryCommandExecutor(registry);
+            if (repl.isTailTipWidgetsEnabled()) {
+                TailTipWidgets widgets = new TailTipWidgets(reader, registry::commandDescription, 5,
+                        TailTipWidgets.TipType.COMPLETER);
+                widgets.enable();
+                // Workaround for jline issue where TailTipWidgets will produce NPE when passed a bracket
+                registry.setScriptDescription(cmdLine -> null);
+            }
+
+            while (!interrupted.get()) {
+                try {
+                    executor.cleanUp();
+                    String prompt = Ansi.AUTO.string(repl.getPromptProvider().getPrompt());
+                    String line = reader.readLine(prompt, null, (MaskingCallback) null, null);
+                    if (line.isEmpty()) {
+                        continue;
+                    }
+
+                    repl.getPipeline(executor, exceptionHandlers, line).runPipeline();
+                } catch (Throwable t) {
+                    exceptionHandlers.handleException(System.err::println, t);
+                }
+            }
+            reader.getHistory().save();
+        } catch (Throwable t) {
+            exceptionHandlers.handleException(System.err::println, t);
+        }
+    }
+
+    private LineReader createReader(Completer completer) {
+        LineReader result = LineReaderBuilder.builder()
+                .terminal(terminal)
+                .completer(completer)
+                .parser(parser)
+                .expander(new NoopExpander())
+                .variable(LineReader.LIST_MAX, 50)   // max tab completion candidates
+                .build();
+        result.setAutosuggestion(SuggestionType.COMPLETER);
+        return result;
+    }
+
+    private PicocliCommands createPicocliCommands(Repl repl) {
+        CommandLine cmd = new CommandLine(repl.commandClass(), factory);
+        IDefaultValueProvider defaultValueProvider = repl.defaultValueProvider();
+        if (defaultValueProvider != null) {
+            cmd.setDefaultValueProvider(defaultValueProvider);
+        }
+        cmd.setExecutionExceptionHandler(new PicocliExecutionExceptionHandler());
+        return new PicocliCommands(cmd);
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/executor/ReplExecutorProvider.java
similarity index 57%
copy from modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/repl/executor/ReplExecutorProvider.java
index 9099a22db..28380dcb0 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/executor/ReplExecutorProvider.java
@@ -15,36 +15,30 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.core.repl.executor;
 
-import io.micronaut.core.annotation.Introspected;
+import io.micronaut.configuration.picocli.MicronautFactory;
 import jakarta.inject.Inject;
 import jakarta.inject.Singleton;
-import picocli.CommandLine;
+import org.jline.terminal.Terminal;
+import picocli.shell.jline3.PicocliCommands.PicocliCommandsFactory;
 
 /**
- * Version provider for Picocli interactions.
+ * Provider of {@link ReplExecutor}.
  */
 @Singleton
-@Introspected
-public class VersionProvider implements CommandLine.IVersionProvider {
+public class ReplExecutorProvider {
+    private PicocliCommandsFactory factory;
 
-    /** Actual Ignite CLI version info. */
-    private final CliVersionInfo cliVerInfo;
-
-    /**
-     * Creates version provider.
-     *
-     * @param cliVerInfo Actual Ignite CLI version container.
-     */
     @Inject
-    public VersionProvider(CliVersionInfo cliVerInfo) {
-        this.cliVerInfo = cliVerInfo;
+    private Terminal terminal;
+
+    public ReplExecutor get() {
+        return new ReplExecutor(factory, terminal);
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public String[] getVersion() {
-        return new String[]{"Apache Ignite CLI ver. " + cliVerInfo.ver};
+    public void injectFactory(MicronautFactory micronautFactory) {
+        this.factory = new PicocliCommandsFactory(micronautFactory);
+        factory.setTerminal(terminal);
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/executor/SqlQueryCall.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/executor/SqlQueryCall.java
new file mode 100644
index 000000000..7c19262f1
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/executor/SqlQueryCall.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.core.repl.executor;
+
+import java.sql.SQLException;
+import org.apache.ignite.cli.core.call.Call;
+import org.apache.ignite.cli.core.call.CallOutput;
+import org.apache.ignite.cli.core.call.DefaultCallOutput;
+import org.apache.ignite.cli.core.call.StringCallInput;
+import org.apache.ignite.cli.sql.SqlManager;
+import org.apache.ignite.cli.sql.SqlQueryResult;
+
+/**
+ * Call implementation for SQL command execution.
+ */
+public class SqlQueryCall implements Call<StringCallInput, SqlQueryResult> {
+
+    private final SqlManager sqlManager;
+
+    /**
+     * Constructor.
+     *
+     * @param sqlManager SQL manager.
+     */
+    public SqlQueryCall(SqlManager sqlManager) {
+        this.sqlManager = sqlManager;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public CallOutput<SqlQueryResult> execute(StringCallInput input) {
+        try {
+            SqlQueryResult result = sqlManager.execute(input.getString());
+            return DefaultCallOutput.success(result);
+        } catch (SQLException e) {
+            return DefaultCallOutput.failure(e);
+        }
+    }
+}
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/expander/NoopExpander.java
similarity index 67%
rename from modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
rename to modules/cli/src/main/java/org/apache/ignite/cli/core/repl/expander/NoopExpander.java
index 32fbdbfdb..d6b0893f5 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/expander/NoopExpander.java
@@ -15,22 +15,24 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli;
+package org.apache.ignite.cli.core.repl.expander;
 
-import java.util.logging.Handler;
-import java.util.logging.LogRecord;
+import org.jline.reader.Expander;
+import org.jline.reader.History;
 
 /**
- * Adapter for {@link Handler} with empty implementations of all methods but {@link Handler#publish(LogRecord)}.
+ * Noop implementation of {@link Expander}.
  */
-public abstract class NoOpHandler extends Handler {
+public class NoopExpander implements Expander {
+    /** {@inheritDoc} */
     @Override
... 4175 lines suppressed ...