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

sk0x50 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 d980d03bc1 IGNITE-18148 CLI Added more dynamic completers. Fixes #1363
d980d03bc1 is described below

commit d980d03bc1401f027748ecbca1aa4f1fc0fc5831
Author: Aleksandr Pakhomov <[email protected]>
AuthorDate: Wed Nov 30 15:59:16 2022 +0200

    IGNITE-18148 CLI Added more dynamic completers. Fixes #1363
    
    Signed-off-by: Slava Koptilin <[email protected]>
---
 .../ignite/internal/cli/commands/BaseCommand.java  |  13 +-
 .../ignite/internal/cli/commands/Options.java      | 215 +++++++++++++++++++
 .../internal/cli/commands/OptionsConstants.java    |  62 ------
 .../ignite/internal/cli/commands/ProfileMixin.java |   6 +-
 .../internal/cli/commands/TopLevelCliCommand.java  |   5 +-
 .../profile/CliConfigProfileCreateCommand.java     |  11 +-
 .../cli/commands/cluster/ClusterUrlMixin.java      |   8 +-
 .../commands/cluster/init/ClusterInitOptions.java  |  26 ++-
 .../cluster/topology/LogicalTopologyCommand.java   |   5 +-
 .../topology/LogicalTopologyReplCommand.java       |   5 +-
 .../cluster/topology/PhysicalTopologyCommand.java  |   5 +-
 .../topology/PhysicalTopologyReplCommand.java      |   5 +-
 .../cli/commands/connect/ConnectCommand.java       |   4 +-
 .../cli/commands/connect/ConnectReplCommand.java   |   4 +-
 .../internal/cli/commands/node/NodeUrlMixin.java   |  15 +-
 .../internal/cli/commands/sql/SqlCommand.java      |  17 +-
 .../internal/cli/commands/sql/SqlReplCommand.java  |  16 +-
 .../cli/core/repl/completer/CompleterConf.java     | 145 +++++++++++++
 .../{DynamicCompleter.java => DummyCompleter.java} |  16 +-
 .../cli/core/repl/completer/DynamicCompleter.java  |  11 +-
 .../completer/DynamicCompleterActivationPoint.java |  71 +++++--
 .../repl/completer/DynamicCompleterFactory.java    |  90 +-------
 .../repl/completer/DynamicCompleterFilter.java     |  40 ++--
 .../repl/completer/DynamicCompleterRegistry.java   | 113 ++++++----
 .../repl/completer/NodeNameDynamicCompleter.java   |  61 ------
 .../cli/core/repl/completer/NodeUrlProvider.java   |   5 +-
 .../ClusterConfigDynamicCompleterFactory.java      |  71 +++++++
 .../{ => hocon}/HoconDynamicCompleter.java         |  30 +--
 .../hocon/NodeConfigDynamicCompleterFactory.java   |  70 +++++++
 .../node/NodeNameDynamicCompleterFactory.java}     |  34 ++-
 .../completer/node/StringDynamicCompleter.java     |  52 +++++
 .../core/repl/executor/IgnitePicocliCommands.java  |  12 +-
 .../cli/core/repl/executor/ReplExecutor.java       |   1 +
 .../completer/DynamicCompleterRegistryTest.java    | 233 ++++++++++++++++++---
 .../repl/completer/HoconDynamicCompleterTest.java  |   5 +-
 ...erTest.java => StringDynamicCompleterTest.java} |  42 ++--
 36 files changed, 1088 insertions(+), 436 deletions(-)

diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/BaseCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/BaseCommand.java
index e3e919cef9..8d1ed5a7ae 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/BaseCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/BaseCommand.java
@@ -17,6 +17,13 @@
 
 package org.apache.ignite.internal.cli.commands;
 
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.HELP_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.HELP_OPTION_DESC;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.HELP_OPTION_SHORT;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.VERBOSE_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.VERBOSE_OPTION_DESC;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.VERBOSE_OPTION_SHORT;
+
 import picocli.CommandLine.Model.CommandSpec;
 import picocli.CommandLine.Option;
 import picocli.CommandLine.Spec;
@@ -26,12 +33,14 @@ import picocli.CommandLine.Spec;
  */
 public abstract class BaseCommand {
     /** Help option specification. */
-    @Option(names = {"-h", "--help"}, usageHelp = true, description = "Show 
this help message and exit.")
+    @Option(names = {HELP_OPTION, HELP_OPTION_SHORT}, usageHelp = true, 
description = HELP_OPTION_DESC)
     protected boolean usageHelpRequested;
 
-    @Option(names = {"-v", "--verbose"}, description = "Show additional 
information.")
+    /** Verbose option specification. */
+    @Option(names = {VERBOSE_OPTION, VERBOSE_OPTION_SHORT}, description = 
VERBOSE_OPTION_DESC)
     protected boolean verbose;
 
+    /** Instance of picocli command specification. */
     @Spec
     protected CommandSpec spec;
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/Options.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/Options.java
new file mode 100644
index 0000000000..1125a9b0cb
--- /dev/null
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/Options.java
@@ -0,0 +1,215 @@
+/*
+ * 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.internal.cli.commands;
+
+import org.apache.ignite.internal.cli.config.ConfigConstants;
+
+/**
+ * Constants to use in {@code Option} annotations for commands.
+ */
+public enum Options {
+    CLUSTER_URL(Constants.CLUSTER_URL_OPTION, Constants.URL_OPTION_SHORT, 
Constants.CLUSTER_URL_OPTION_DESC),
+    NODE_URL(Constants.NODE_URL_OPTION, Constants.URL_OPTION_SHORT, 
Constants.NODE_URL_OPTION_DESC),
+
+    CLUSTER_NAME(Constants.CLUSTER_NAME_OPTION, 
Constants.CLUSTER_NAME_OPTION_SHORT, Constants.CLUSTER_NAME_OPTION_DESC),
+    NODE_NAME(Constants.NODE_NAME_OPTION, Constants.NODE_NAME_OPTION_SHORT, 
Constants.NODE_NAME_OPTION_DESC),
+
+    CMG_NODE_NAME(Constants.CMG_NODE_NAME_OPTION, 
Constants.CMG_NODE_NAME_OPTION_SHORT, Constants.CMG_NODE_NAME_OPTION_DESC),
+    META_STORAGE_NODE_NAME(
+            Constants.META_STORAGE_NODE_NAME_OPTION,
+            Constants.META_STORAGE_NODE_NAME_OPTION_SHORT,
+            Constants.META_STORAGE_NODE_NAME_OPTION_DESC
+    ),
+
+    PROFILE(Constants.PROFILE_OPTION, Constants.PROFILE_OPTION_SHORT, 
Constants.PROFILE_OPTION_DESC),
+    PROFILE_COPY_FROM(
+            Constants.PROFILE_COPY_FROM_OPTION,
+            Constants.PROFILE_COPY_FROM_OPTION_SHORT,
+            Constants.PROFILE_COPY_FROM_OPTION_DESC
+    ),
+    PROFILE_ACTIVATE(Constants.PROFILE_ACTIVATE_OPTION, 
Constants.PROFILE_ACTIVATE_OPTION_SHORT, 
Constants.PROFILE_ACTIVATE_OPTION_DESC),
+
+    SCRIPT_FILE(Constants.SCRIPT_FILE_OPTION, 
Constants.SCRIPT_FILE_OPTION_SHORT, Constants.SCRIPT_FILE_OPTION_DESC),
+    JDBC_URL(Constants.JDBC_URL_OPTION, Constants.JDBC_URL_OPTION_SHORT, 
Constants.JDBC_URL_OPTION_DESC),
+
+    PLAIN(Constants.PLAIN_OPTION, Constants.PLAIN_OPTION, 
Constants.PLAIN_OPTION_DESC),
+    VERBOSE(Constants.VERBOSE_OPTION, Constants.VERBOSE_OPTION_SHORT, 
Constants.VERBOSE_OPTION_DESC),
+    HELP(Constants.HELP_OPTION, Constants.HELP_OPTION_SHORT, 
Constants.HELP_OPTION_DESC),
+    VERSION(Constants.VERSION_OPTION, Constants.VERSION_OPTION, 
Constants.VERSION_OPTION_DESC);
+
+    private final String fullName;
+    private final String shortName;
+    private final String description;
+
+    Options(String fullName, String shortName, String description) {
+        this.fullName = fullName;
+        this.shortName = shortName;
+        this.description = description;
+    }
+
+    public String fullName() {
+        return fullName;
+    }
+
+    public String shortName() {
+        return shortName;
+    }
+
+    public String description() {
+        return description;
+    }
+
+    /** Constants for all options. */
+    public static final class Constants {
+        /** Cluster endpoint URL option long name. */
+        public static final String CLUSTER_URL_OPTION = 
"--cluster-endpoint-url";
+
+        /** Cluster endpoint URL option description. */
+        public static final String CLUSTER_URL_OPTION_DESC = "URL of cluster 
endpoint";
+
+        /** Cluster endpoint URL option description key. */
+        public static final String CLUSTER_URL_KEY = 
ConfigConstants.CLUSTER_URL;
+
+        /** Node URL option long name. */
+        public static final String NODE_URL_OPTION = "--node-url";
+
+        /** Node URL option description. */
+        public static final String NODE_URL_OPTION_DESC = "URL of ignite node";
+
+        /** Node URL or name option description. */
+        public static final String NODE_URL_OR_NAME_DESC = "URL or name of an 
Ignite node";
+
+        /** Profile name option long name. */
+        public static final String PROFILE_OPTION = "--profile";
+
+        /** Profile name option short name. */
+        public static final String PROFILE_OPTION_SHORT = "-p";
+
+        /** Profile name option description. */
+        public static final String PROFILE_OPTION_DESC = "Profile name";
+
+        /** URL option short name. */
+        public static final String URL_OPTION_SHORT = "-u";
+
+        /** Node name option long name. */
+        public static final String NODE_NAME_OPTION = "--node-name";
+
+        /** Node name option short name. */
+        public static final String NODE_NAME_OPTION_SHORT = "-n";
+
+        /** Node name option description. */
+        public static final String NODE_NAME_OPTION_DESC = "Name of an Ignite 
node";
+
+        /** Verbose option long name. */
+        public static final String VERBOSE_OPTION = "--verbose";
+
+        /** Verbose option short name. */
+        public static final String VERBOSE_OPTION_SHORT = "-v";
+
+        /** Verbose option description. */
+        public static final String VERBOSE_OPTION_DESC = "Show additional 
information: logs, REST calls";
+
+        /** Help option long name. */
+        public static final String HELP_OPTION = "--help";
+
+        /** Help option short name. */
+        public static final String HELP_OPTION_SHORT = "-h";
+
+        /** Verbose option description. */
+        public static final String HELP_OPTION_DESC = "Show help for the 
specified command";
+
+        /** Profile copy from option long name. */
+        public static final String PROFILE_COPY_FROM_OPTION = "--copy-from";
+
+        /** Profile copy from option short name. */
+        public static final String PROFILE_COPY_FROM_OPTION_SHORT = "-c";
+
+        /** Profile copy from option description. */
+        public static final String PROFILE_COPY_FROM_OPTION_DESC = "Profile 
whose content will be copied to new one";
+
+        /** Profile activate option long name. */
+        public static final String PROFILE_ACTIVATE_OPTION = "--activate";
+
+        /** Profile activate option short name. */
+        public static final String PROFILE_ACTIVATE_OPTION_SHORT = "-a";
+
+        /** Profile activate option description. */
+        public static final String PROFILE_ACTIVATE_OPTION_DESC = "Activate 
new profile as current or not";
+
+        /** Cluster management node name option long name. */
+        public static final String CMG_NODE_NAME_OPTION = "--cmg-node";
+
+        /** Cluster management node name option short name. */
+        public static final String CMG_NODE_NAME_OPTION_SHORT = "-c";
+
+        /** Cluster management node name option description. */
+        public static final String CMG_NODE_NAME_OPTION_DESC = "Name of the 
node (repeat like '--cmg-node node1 --cmg-node node2' "
+                + "to specify more than one node) that will host the Cluster 
Management Group."
+                + "If omitted, then --meta-storage-node values will also 
supply the nodes for the Cluster Management Group.";
+
+        /** Meta storage management node name option long name. */
+        public static final String META_STORAGE_NODE_NAME_OPTION = 
"--meta-storage-node";
+
+        /** Meta storage node name option short name. */
+        public static final String META_STORAGE_NODE_NAME_OPTION_SHORT = "-m";
+
+        /** Meta storage node name option description. */
+        public static final String META_STORAGE_NODE_NAME_OPTION_DESC = "Name 
of the node (repeat like '--meta-storage-node node1 "
+                + "--meta-storage-node node2' to specify more than one node) 
that will host the Meta Storage."
+                + "If the --cmg-node parameter is omitted, the same nodes will 
also host the Cluster Management Group.";
+
+        /** Cluster name option long name. */
+        public static final String CLUSTER_NAME_OPTION = "--cluster-name";
+
+        /** Cluster name option short name. */
+        public static final String CLUSTER_NAME_OPTION_SHORT = "-n";
+
+        /** Cluster name option description. */
+        public static final String CLUSTER_NAME_OPTION_DESC = "Human-readable 
name of the cluster";
+
+        /** Plain option long name. */
+        public static final String PLAIN_OPTION = "--plain";
+
+        /** Plain option description. */
+        public static final String PLAIN_OPTION_DESC = "Display output with 
plain formatting";
+
+        /** JDBC URL option long name. */
+        public static final String JDBC_URL_OPTION = "--jdbc-url";
+
+        /** JDBC URL option short name. */
+        public static final String JDBC_URL_OPTION_SHORT = "-u";
+
+        /** JDBC URL option description. */
+        public static final String JDBC_URL_OPTION_DESC = "JDBC url to ignite 
cluster";
+
+        /** SQL script file option long name. */
+        public static final String SCRIPT_FILE_OPTION = "--script-file";
+
+        /** SQL script file option short name. */
+        public static final String SCRIPT_FILE_OPTION_SHORT = "-f";
+
+        /** SQL script file option description. */
+        public static final String SCRIPT_FILE_OPTION_DESC = "Path to file 
with SQL commands to execute";
+
+        /** Version option long name. */
+        public static final String VERSION_OPTION = "--version";
+
+        /** Version option description. */
+        public static final String VERSION_OPTION_DESC = "Print version 
information and exit";
+    }
+}
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/OptionsConstants.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/OptionsConstants.java
deleted file mode 100644
index 98c56bfc38..0000000000
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/OptionsConstants.java
+++ /dev/null
@@ -1,62 +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.internal.cli.commands;
-
-import org.apache.ignite.internal.cli.config.ConfigConstants;
-
-/**
- * Constants to use in {@code Option} annotations for commands.
- */
-public class OptionsConstants {
-    /** Cluster endpoint URL option long name. */
-    public static final String CLUSTER_URL_OPTION = "--cluster-endpoint-url";
-
-    /** Cluster endpoint URL option description. */
-    public static final String CLUSTER_URL_DESC = "URL of cluster endpoint";
-
-    /** Cluster endpoint URL option description key. */
-    public static final String CLUSTER_URL_KEY = ConfigConstants.CLUSTER_URL;
-
-    /** Node URL option long name. */
-    public static final String NODE_URL_OPTION = "--node-url";
-
-    /** Node URL option description. */
-    public static final String NODE_URL_DESC = "URL of ignite node";
-
-    /** Node URL or name option description. */
-    public static final String NODE_URL_OR_NAME_DESC = "URL or name of an 
Ignite node";
-
-    /** Profile name option names. */
-    public static final String PROFILE_OPTION = "--profile";
-    public static final String PROFILE_OPTION_SHORT = "-p";
-
-    /** Profile name option description. */
-    public static final String PROFILE_OPTION_DESC = "Profile name";
-
-    /** URL option short name. */
-    public static final String URL_OPTION_SHORT = "-u";
-
-    /** Node name option long name. */
-    public static final String NODE_NAME_OPTION = "--node-name";
-
-    /** Node name option short name. */
-    public static final String NODE_NAME_OPTION_SHORT = "-n";
-
-    /** Node name option description. */
-    public static final String NODE_NAME_DESC = "Name of an Ignite node";
-}
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/ProfileMixin.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/ProfileMixin.java
index d0cf65817b..f82a6f0cca 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/ProfileMixin.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/ProfileMixin.java
@@ -17,9 +17,9 @@
 
 package org.apache.ignite.internal.cli.commands;
 
-import static 
org.apache.ignite.internal.cli.commands.OptionsConstants.PROFILE_OPTION;
-import static 
org.apache.ignite.internal.cli.commands.OptionsConstants.PROFILE_OPTION_DESC;
-import static 
org.apache.ignite.internal.cli.commands.OptionsConstants.PROFILE_OPTION_SHORT;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PROFILE_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PROFILE_OPTION_DESC;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PROFILE_OPTION_SHORT;
 
 import picocli.CommandLine.Option;
 
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/TopLevelCliCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/TopLevelCliCommand.java
index 444722a1c5..ee13d88282 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/TopLevelCliCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/TopLevelCliCommand.java
@@ -17,6 +17,9 @@
 
 package org.apache.ignite.internal.cli.commands;
 
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.VERSION_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.VERSION_OPTION_DESC;
+
 import org.apache.ignite.internal.cli.VersionProvider;
 import org.apache.ignite.internal.cli.commands.cliconfig.CliCommand;
 import org.apache.ignite.internal.cli.commands.cluster.ClusterCommand;
@@ -46,6 +49,6 @@ import picocli.CommandLine.Option;
         })
 public class TopLevelCliCommand extends BaseCommand {
     @SuppressWarnings("PMD.UnusedPrivateField")
-    @Option(names = {"--version"}, versionHelp = true, description = "Print 
version information and exit")
+    @Option(names = VERSION_OPTION, versionHelp = true, description = 
VERSION_OPTION_DESC)
     private boolean versionRequested;
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cliconfig/profile/CliConfigProfileCreateCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cliconfig/profile/CliConfigProfileCreateCommand.java
index 594120d149..cd87262fdc 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cliconfig/profile/CliConfigProfileCreateCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cliconfig/profile/CliConfigProfileCreateCommand.java
@@ -17,6 +17,13 @@
 
 package org.apache.ignite.internal.cli.commands.cliconfig.profile;
 
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PROFILE_ACTIVATE_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PROFILE_ACTIVATE_OPTION_DESC;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PROFILE_ACTIVATE_OPTION_SHORT;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PROFILE_COPY_FROM_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PROFILE_COPY_FROM_OPTION_DESC;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PROFILE_COPY_FROM_OPTION_SHORT;
+
 import jakarta.inject.Inject;
 import java.util.concurrent.Callable;
 import 
org.apache.ignite.internal.cli.call.cliconfig.profile.CliConfigProfileCreateCall;
@@ -35,10 +42,10 @@ public class CliConfigProfileCreateCommand extends 
BaseCommand implements Callab
     @Parameters(arity = "1", description = "Name of new profile")
     private String profileName;
 
-    @Option(names = {"--copy-from", "-c"}, description = "Profile whose 
content will be copied to new one")
+    @Option(names = {PROFILE_COPY_FROM_OPTION, 
PROFILE_COPY_FROM_OPTION_SHORT}, description = PROFILE_COPY_FROM_OPTION_DESC)
     private String copyFrom;
 
-    @Option(names = {"--activate", "-a"}, description = "Activate new profile 
as current or not")
+    @Option(names = {PROFILE_ACTIVATE_OPTION, PROFILE_ACTIVATE_OPTION_SHORT}, 
description = PROFILE_ACTIVATE_OPTION_DESC)
     private boolean activate;
 
     @Inject
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/ClusterUrlMixin.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/ClusterUrlMixin.java
index 685900ac3c..bbe1a3dcaa 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/ClusterUrlMixin.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/ClusterUrlMixin.java
@@ -17,9 +17,9 @@
 
 package org.apache.ignite.internal.cli.commands.cluster;
 
-import static 
org.apache.ignite.internal.cli.commands.OptionsConstants.CLUSTER_URL_DESC;
-import static 
org.apache.ignite.internal.cli.commands.OptionsConstants.CLUSTER_URL_OPTION;
-import static 
org.apache.ignite.internal.cli.commands.OptionsConstants.URL_OPTION_SHORT;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.CLUSTER_URL_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.CLUSTER_URL_OPTION_DESC;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.URL_OPTION_SHORT;
 
 import java.net.URL;
 import org.apache.ignite.internal.cli.core.converters.UrlConverter;
@@ -30,7 +30,7 @@ import picocli.CommandLine.Option;
  */
 public class ClusterUrlMixin {
     /** Cluster endpoint URL option. */
-    @Option(names = {URL_OPTION_SHORT, CLUSTER_URL_OPTION}, description = 
CLUSTER_URL_DESC, converter = UrlConverter.class)
+    @Option(names = {URL_OPTION_SHORT, CLUSTER_URL_OPTION}, description = 
CLUSTER_URL_OPTION_DESC, converter = UrlConverter.class)
     private URL clusterUrl;
 
     public String getClusterUrl() {
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/init/ClusterInitOptions.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/init/ClusterInitOptions.java
index 011512ab5e..75fd02f512 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/init/ClusterInitOptions.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/init/ClusterInitOptions.java
@@ -17,6 +17,16 @@
 
 package org.apache.ignite.internal.cli.commands.cluster.init;
 
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.CLUSTER_NAME_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.CLUSTER_NAME_OPTION_DESC;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.CLUSTER_NAME_OPTION_SHORT;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.CMG_NODE_NAME_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.CMG_NODE_NAME_OPTION_DESC;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.CMG_NODE_NAME_OPTION_SHORT;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.META_STORAGE_NODE_NAME_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.META_STORAGE_NODE_NAME_OPTION_DESC;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.META_STORAGE_NODE_NAME_OPTION_SHORT;
+
 import java.util.ArrayList;
 import java.util.List;
 import picocli.CommandLine.Option;
@@ -29,25 +39,19 @@ public class ClusterInitOptions {
      * List of names of the nodes (each represented by a separate command line 
argument) that will host the Meta Storage. If the
      * "--cmg-nodes" parameter is omitted, the same nodes will also host the 
Cluster Management Group.
      */
-    @Option(names = {"-m", "--meta-storage-node"}, required = true, 
description = {
-            "Name of the node (repeat like '--meta-storage-node node1 
--meta-storage-node node2' to specify more than one node)",
-            "that will host the Meta Storage.",
-            "If the --cmg-node parameter is omitted, the same nodes will also 
host the Cluster Management Group."
-    })
+    @Option(names = {META_STORAGE_NODE_NAME_OPTION, 
META_STORAGE_NODE_NAME_OPTION_SHORT},
+            required = true,
+            description = META_STORAGE_NODE_NAME_OPTION_DESC)
     private List<String> metaStorageNodes;
 
     /**
      * List of names of the nodes (each represented by a separate command line 
argument) that will host the Cluster Management Group.
      */
-    @Option(names = {"-c", "--cmg-node"}, description = {
-            "Name of the node (repeat like '--cmg-node node1 --cmg-node node2' 
to specify more than one node)",
-            "that will host the Cluster Management Group.",
-            "If omitted, then --meta-storage-node values will also supply the 
nodes for the Cluster Management Group."
-    })
+    @Option(names = {CMG_NODE_NAME_OPTION, CMG_NODE_NAME_OPTION_SHORT}, 
description = CMG_NODE_NAME_OPTION_DESC)
     private List<String> cmgNodes = new ArrayList<>();
 
     /** Name of the cluster. */
-    @Option(names = {"-n", "--cluster-name"}, required = true, description = 
"Human-readable name of the cluster")
+    @Option(names = {CLUSTER_NAME_OPTION, CLUSTER_NAME_OPTION_SHORT}, required 
= true, description = CLUSTER_NAME_OPTION_DESC)
     private String clusterName;
 
     public List<String> getMetaStorageNodes() {
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/LogicalTopologyCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/LogicalTopologyCommand.java
index f4b994aeb6..24b9a2f0a9 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/LogicalTopologyCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/LogicalTopologyCommand.java
@@ -17,6 +17,9 @@
 
 package org.apache.ignite.internal.cli.commands.cluster.topology;
 
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PLAIN_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PLAIN_OPTION_DESC;
+
 import jakarta.inject.Inject;
 import java.util.concurrent.Callable;
 import 
org.apache.ignite.internal.cli.call.cluster.topology.LogicalTopologyCall;
@@ -43,7 +46,7 @@ public class LogicalTopologyCommand extends BaseCommand 
implements Callable<Inte
     @Inject
     private LogicalTopologyCall call;
 
-    @Option(names = "--plain", description = "Display output with plain 
formatting")
+    @Option(names = PLAIN_OPTION, description = PLAIN_OPTION_DESC)
     private boolean plain;
 
     /** {@inheritDoc} */
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/LogicalTopologyReplCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/LogicalTopologyReplCommand.java
index 37c0f7f7a7..3069cab8cd 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/LogicalTopologyReplCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/LogicalTopologyReplCommand.java
@@ -17,6 +17,9 @@
 
 package org.apache.ignite.internal.cli.commands.cluster.topology;
 
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PLAIN_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PLAIN_OPTION_DESC;
+
 import jakarta.inject.Inject;
 import 
org.apache.ignite.internal.cli.call.cluster.topology.LogicalTopologyCall;
 import org.apache.ignite.internal.cli.commands.BaseCommand;
@@ -46,7 +49,7 @@ public class LogicalTopologyReplCommand extends BaseCommand 
implements Runnable
     @Inject
     private ConnectToClusterQuestion question;
 
-    @Option(names = "--plain", description = "Display output with plain 
formatting")
+    @Option(names = PLAIN_OPTION, description = PLAIN_OPTION_DESC)
     private boolean plain;
 
     /** {@inheritDoc} */
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/PhysicalTopologyCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/PhysicalTopologyCommand.java
index a28f3fc274..b6c32e3cc4 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/PhysicalTopologyCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/PhysicalTopologyCommand.java
@@ -17,6 +17,9 @@
 
 package org.apache.ignite.internal.cli.commands.cluster.topology;
 
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PLAIN_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PLAIN_OPTION_DESC;
+
 import jakarta.inject.Inject;
 import java.util.concurrent.Callable;
 import 
org.apache.ignite.internal.cli.call.cluster.topology.PhysicalTopologyCall;
@@ -42,7 +45,7 @@ public class PhysicalTopologyCommand extends BaseCommand 
implements Callable<Int
     @Inject
     private PhysicalTopologyCall call;
 
-    @Option(names = "--plain", description = "Display output with plain 
formatting")
+    @Option(names = PLAIN_OPTION, description = PLAIN_OPTION_DESC)
     private boolean plain;
 
     /** {@inheritDoc} */
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/PhysicalTopologyReplCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/PhysicalTopologyReplCommand.java
index b72f450f55..81e251234e 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/PhysicalTopologyReplCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/PhysicalTopologyReplCommand.java
@@ -17,6 +17,9 @@
 
 package org.apache.ignite.internal.cli.commands.cluster.topology;
 
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PLAIN_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PLAIN_OPTION_DESC;
+
 import jakarta.inject.Inject;
 import 
org.apache.ignite.internal.cli.call.cluster.topology.PhysicalTopologyCall;
 import org.apache.ignite.internal.cli.commands.BaseCommand;
@@ -45,7 +48,7 @@ public class PhysicalTopologyReplCommand extends BaseCommand 
implements Runnable
     @Inject
     private ConnectToClusterQuestion question;
 
-    @Option(names = "--plain", description = "Display output with plain 
formatting")
+    @Option(names = PLAIN_OPTION, description = PLAIN_OPTION_DESC)
     private boolean plain;
 
     /** {@inheritDoc} */
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/connect/ConnectCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/connect/ConnectCommand.java
index 9457086550..987d5344be 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/connect/ConnectCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/connect/ConnectCommand.java
@@ -17,8 +17,8 @@
 
 package org.apache.ignite.internal.cli.commands.connect;
 
-import static 
org.apache.ignite.internal.cli.commands.OptionsConstants.CLUSTER_URL_KEY;
-import static 
org.apache.ignite.internal.cli.commands.OptionsConstants.NODE_URL_OR_NAME_DESC;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.CLUSTER_URL_KEY;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.NODE_URL_OR_NAME_DESC;
 
 import jakarta.inject.Inject;
 import org.apache.ignite.internal.cli.ReplManager;
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/connect/ConnectReplCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/connect/ConnectReplCommand.java
index 6f73ea78b5..db12b83720 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/connect/ConnectReplCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/connect/ConnectReplCommand.java
@@ -17,8 +17,8 @@
 
 package org.apache.ignite.internal.cli.commands.connect;
 
-import static 
org.apache.ignite.internal.cli.commands.OptionsConstants.CLUSTER_URL_KEY;
-import static 
org.apache.ignite.internal.cli.commands.OptionsConstants.NODE_URL_OR_NAME_DESC;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.CLUSTER_URL_KEY;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.NODE_URL_OR_NAME_DESC;
 
 import jakarta.inject.Inject;
 import org.apache.ignite.internal.cli.call.connect.ConnectCall;
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/NodeUrlMixin.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/NodeUrlMixin.java
index 8106ef0698..c60f4544b9 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/NodeUrlMixin.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/NodeUrlMixin.java
@@ -17,12 +17,11 @@
 
 package org.apache.ignite.internal.cli.commands.node;
 
-import static 
org.apache.ignite.internal.cli.commands.OptionsConstants.NODE_NAME_DESC;
-import static 
org.apache.ignite.internal.cli.commands.OptionsConstants.NODE_NAME_OPTION;
-import static 
org.apache.ignite.internal.cli.commands.OptionsConstants.NODE_NAME_OPTION_SHORT;
-import static 
org.apache.ignite.internal.cli.commands.OptionsConstants.NODE_URL_DESC;
-import static 
org.apache.ignite.internal.cli.commands.OptionsConstants.NODE_URL_OPTION;
-import static 
org.apache.ignite.internal.cli.commands.OptionsConstants.URL_OPTION_SHORT;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.NODE_NAME_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.NODE_NAME_OPTION_SHORT;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.NODE_URL_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.NODE_URL_OPTION_DESC;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.URL_OPTION_SHORT;
 
 import jakarta.inject.Inject;
 import java.net.URL;
@@ -49,13 +48,13 @@ public class NodeUrlMixin {
         /**
          * Node URL option.
          */
-        @Option(names = {URL_OPTION_SHORT, NODE_URL_OPTION}, description = 
NODE_URL_DESC, converter = UrlConverter.class)
+        @Option(names = {URL_OPTION_SHORT, NODE_URL_OPTION}, description = 
NODE_URL_OPTION_DESC, converter = UrlConverter.class)
         private URL nodeUrl;
 
         /**
          * Node name option.
          */
-        @Option(names = {NODE_NAME_OPTION_SHORT, NODE_NAME_OPTION}, 
description = NODE_NAME_DESC)
+        @Option(names = {NODE_NAME_OPTION_SHORT, NODE_NAME_OPTION}, 
description = NODE_URL_OPTION_DESC)
         private String nodeName;
     }
 
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlCommand.java
index 7635404003..f36ff6e84e 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlCommand.java
@@ -17,6 +17,15 @@
 
 package org.apache.ignite.internal.cli.commands.sql;
 
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.JDBC_URL_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.JDBC_URL_OPTION_DESC;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.JDBC_URL_OPTION_SHORT;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PLAIN_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PLAIN_OPTION_DESC;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.SCRIPT_FILE_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.SCRIPT_FILE_OPTION_DESC;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.SCRIPT_FILE_OPTION_SHORT;
+
 import java.io.File;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
@@ -44,11 +53,11 @@ import picocli.CommandLine.Parameters;
  */
 @Command(name = "sql", description = "Executes SQL query")
 public class SqlCommand extends BaseCommand implements Callable<Integer> {
-    @Option(names = {"-u", "--jdbc-url"}, required = true,
-            descriptionKey = "ignite.jdbc-url", description = "JDBC url to 
ignite cluster")
+    @Option(names = {JDBC_URL_OPTION, JDBC_URL_OPTION_SHORT}, required = true,
+            descriptionKey = "ignite.jdbc-url", description = 
JDBC_URL_OPTION_DESC)
     private String jdbc;
 
-    @Option(names = "--plain", description = "Display output with plain 
formatting")
+    @Option(names = PLAIN_OPTION, description = PLAIN_OPTION_DESC)
     private boolean plain;
 
     @ArgGroup(multiplicity = "1")
@@ -58,7 +67,7 @@ public class SqlCommand extends BaseCommand implements 
Callable<Integer> {
         @Parameters(index = "0", description = "SQL query to execute")
         private String command;
 
-        @Option(names = {"-f", "--script-file"}, description = "Path to file 
with SQL commands to execute")
+        @Option(names = {SCRIPT_FILE_OPTION, SCRIPT_FILE_OPTION_SHORT}, 
description = SCRIPT_FILE_OPTION_DESC)
         private File file;
     }
 
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplCommand.java
index 05ea375939..13d94523bd 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplCommand.java
@@ -17,6 +17,14 @@
 
 package org.apache.ignite.internal.cli.commands.sql;
 
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.JDBC_URL_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.JDBC_URL_OPTION_DESC;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.JDBC_URL_OPTION_SHORT;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PLAIN_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PLAIN_OPTION_DESC;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.SCRIPT_FILE_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.SCRIPT_FILE_OPTION_SHORT;
+
 import jakarta.inject.Inject;
 import java.io.File;
 import java.io.IOException;
@@ -50,11 +58,11 @@ import picocli.CommandLine.Parameters;
  */
 @Command(name = "sql", description = "Executes SQL query")
 public class SqlReplCommand extends BaseCommand implements Runnable {
-    @Option(names = {"-u", "--jdbc-url"}, required = true,
-            descriptionKey = "ignite.jdbc-url", description = "JDBC url to 
ignite cluster")
+    @Option(names = {JDBC_URL_OPTION, JDBC_URL_OPTION_SHORT}, required = true,
+            descriptionKey = "ignite.jdbc-url", description = 
JDBC_URL_OPTION_DESC)
     private String jdbc;
 
-    @Option(names = "--plain", description = "Display output with plain 
formatting")
+    @Option(names = PLAIN_OPTION, description = PLAIN_OPTION_DESC)
     private boolean plain;
 
     @ArgGroup
@@ -64,7 +72,7 @@ public class SqlReplCommand extends BaseCommand implements 
Runnable {
         @Parameters(index = "0", description = "SQL query to execute")
         private String command;
 
-        @Option(names = {"-f", "--script-file"}, description = "Path to file 
with SQL commands to execute")
+        @Option(names = {SCRIPT_FILE_OPTION, SCRIPT_FILE_OPTION_SHORT}, 
description = SCRIPT_FILE_OPTION_SHORT)
         private File file;
     }
 
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/CompleterConf.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/CompleterConf.java
new file mode 100644
index 0000000000..4833e93393
--- /dev/null
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/CompleterConf.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.internal.cli.core.repl.completer;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.ignite.internal.cli.commands.Options;
+
+/**
+ * Configuration for dynamic completer. It declares for what command and 
option the completer could be applied.
+ * Also supports explicit declaration of options after which the completer 
should not be applied.
+  */
+public class CompleterConf {
+
+    private final List<String[]> commands;
+
+    private final Set<String> enableOptions;
+
+    private final Set<String> disableOptions;
+
+    private final boolean exclusiveEnableOptions;
+
+    private CompleterConf(List<String[]> commands, Set<String> enableOptions, 
Set<String> disableOptions, boolean exclusiveEnableOptions) {
+        if (commands == null) {
+            throw new IllegalArgumentException("commands must not be null");
+        }
+
+        this.commands = commands;
+        this.enableOptions = enableOptions;
+        this.disableOptions = disableOptions;
+        this.exclusiveEnableOptions = exclusiveEnableOptions;
+    }
+
+    public static CompleterConf everytime() {
+        return builder().build();
+    }
+
+    public static CompleterConf forCommand(String... words) {
+        return builder().command(words).build();
+    }
+
+    public static CompleterConfBuilder builder() {
+        return new CompleterConfBuilder();
+    }
+
+    public List<String[]> commands() {
+        return commands;
+    }
+
+    public Set<String> enableOptions() {
+        return enableOptions;
+    }
+
+    public Set<String> disableOptions() {
+        return disableOptions;
+    }
+
+    public boolean commandSpecific() {
+        return !commands.isEmpty();
+    }
+
+    public boolean hasEnableOptions() {
+        return enableOptions != null;
+    }
+
+    public boolean hasDisableOptions() {
+        return disableOptions != null;
+    }
+
+    public boolean isExclusiveEnableOptions() {
+        return exclusiveEnableOptions;
+    }
+
+    /** Builder for {@link CompleterConf}. */
+    public static class CompleterConfBuilder {
+        private final List<String[]> command = new ArrayList<>();
+
+        private Set<String> enableOptions;
+
+        private Set<String> disableOptions;
+
+        private boolean exclusiveEnableOptions;
+
+        private CompleterConfBuilder() {
+        }
+
+        /** Setup commands after those the completer should be called. */
+        public CompleterConfBuilder command(String... words) {
+            this.command.add(Arrays.copyOf(words, words.length));
+            return this;
+        }
+
+        /** Setup options after those the completer should be called. */
+        public CompleterConfBuilder enableOptions(Options... enableOptions) {
+            this.enableOptions = Stream.of(enableOptions)
+                    .flatMap(opt -> Stream.of(opt.fullName(), opt.shortName()))
+                    .collect(Collectors.toSet());
+            return this;
+        }
+
+        /** Setup options after those the completer should be called. */
+        public CompleterConfBuilder enableOptions(String... enableOptions) {
+            this.enableOptions = Set.of(enableOptions);
+            return this;
+        }
+
+        /** Setup options after those the completer should NOT be called. */
+        public CompleterConfBuilder disableOptions(String... disableOptions) {
+            this.disableOptions = Set.of(disableOptions);
+            return this;
+        }
+
+        /**
+         * If called than all enable options of current configuration will 
become disable options for all other completers.
+         * For example, --node-name should be completed by only one completer.
+         */
+        public CompleterConfBuilder exclusiveEnableOptions() {
+            this.exclusiveEnableOptions = true;
+            return this;
+        }
+
+        public CompleterConf build() {
+            return new CompleterConf(command, enableOptions, disableOptions, 
exclusiveEnableOptions);
+        }
+    }
+}
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleter.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DummyCompleter.java
similarity index 74%
copy from 
modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleter.java
copy to 
modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DummyCompleter.java
index 933222f324..24ab34ca41 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleter.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DummyCompleter.java
@@ -19,14 +19,10 @@ package org.apache.ignite.internal.cli.core.repl.completer;
 
 import java.util.List;
 
-/**
- * Dynamic completer returns completions that can be fetched in real time
- * from Ignite 3 node, file, or cached.
- */
-public interface DynamicCompleter {
-
-    /**
-     * Given typed words returns list of candidates that can be autocompleted.
-     */
-    List<String> complete(String[] words);
+/** Dummy completer that returns empty list on any input. */
+public class DummyCompleter implements DynamicCompleter {
+    @Override
+    public List<String> complete(String[] words) {
+        return List.of();
+    }
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleter.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleter.java
index 933222f324..df930c6fd2 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleter.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleter.java
@@ -20,13 +20,14 @@ package org.apache.ignite.internal.cli.core.repl.completer;
 import java.util.List;
 
 /**
- * Dynamic completer returns completions that can be fetched in real time
- * from Ignite 3 node, file, or cached.
+ * Dynamic completer returns completions that can be fetched in real time from 
Ignite 3 node, file, or cached.
+ *
+ * <p>The lifecycle of {@link DynamicCompleter} is determined by {@link 
DynamicCompleterFactory},
+ * use the factory to create an instance of {@link DynamicCompleter}.
  */
+@FunctionalInterface
 public interface DynamicCompleter {
 
-    /**
-     * Given typed words returns list of candidates that can be autocompleted.
-     */
+    /** Given typed words returns list of candidates that can be 
autocompleted. */
     List<String> complete(String[] words);
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterActivationPoint.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterActivationPoint.java
index 3d24ee4a1f..c1aaee0f1f 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterActivationPoint.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterActivationPoint.java
@@ -17,40 +17,65 @@
 
 package org.apache.ignite.internal.cli.core.repl.completer;
 
-import static 
org.apache.ignite.internal.cli.commands.OptionsConstants.NODE_NAME_OPTION;
-import static 
org.apache.ignite.internal.cli.commands.OptionsConstants.NODE_NAME_OPTION_SHORT;
-
 import jakarta.inject.Singleton;
+import org.apache.ignite.internal.cli.commands.Options;
+import 
org.apache.ignite.internal.cli.core.repl.completer.hocon.ClusterConfigDynamicCompleterFactory;
+import 
org.apache.ignite.internal.cli.core.repl.completer.hocon.NodeConfigDynamicCompleterFactory;
+import 
org.apache.ignite.internal.cli.core.repl.completer.node.NodeNameDynamicCompleterFactory;
 
-/**
- * Activation point that links commands with dynamic completers.
- */
+/** Activation point that links commands with dynamic completers. */
 @Singleton
 public class DynamicCompleterActivationPoint {
 
-    private final DynamicCompleterFactory factory;
+    private final NodeNameDynamicCompleterFactory 
nodeNameDynamicCompleterFactory;
+    private final ClusterConfigDynamicCompleterFactory 
clusterConfigDynamicCompleterFactory;
+    private final NodeConfigDynamicCompleterFactory 
nodeConfigDynamicCompleterFactory;
 
-    /** Default constructor. */
-    public DynamicCompleterActivationPoint(DynamicCompleterFactory factory) {
-        this.factory = factory;
+    /** Main constructor. */
+    public DynamicCompleterActivationPoint(
+            NodeNameDynamicCompleterFactory nodeNameDynamicCompleterFactory,
+            ClusterConfigDynamicCompleterFactory 
clusterConfigDynamicCompleterFactory,
+            NodeConfigDynamicCompleterFactory nodeConfigDynamicCompleterFactory
+    ) {
+        this.nodeNameDynamicCompleterFactory = nodeNameDynamicCompleterFactory;
+        this.clusterConfigDynamicCompleterFactory = 
clusterConfigDynamicCompleterFactory;
+        this.nodeConfigDynamicCompleterFactory = 
nodeConfigDynamicCompleterFactory;
     }
 
+
     /**
      * Registers all dynamic completers in given {@link 
DynamicCompleterRegistry}.
      */
     public void activateDynamicCompleter(DynamicCompleterRegistry registry) {
-        registry.register(new String[]{"cluster", "config", "show"},
-                new String[]{NODE_NAME_OPTION, NODE_NAME_OPTION_SHORT},
-                factory.clusterConfigCompleter(""));
-        registry.register(new String[]{"cluster", "config", "update"},
-                new String[]{NODE_NAME_OPTION, NODE_NAME_OPTION_SHORT},
-                factory.clusterConfigCompleter(""));
-        registry.register(new String[]{"node", "config", "show"},
-                new String[]{NODE_NAME_OPTION, NODE_NAME_OPTION_SHORT},
-                factory.nodeConfigCompleter(""));
-        registry.register(new String[]{"node", "config", "update"},
-                new String[]{NODE_NAME_OPTION, NODE_NAME_OPTION_SHORT},
-                factory.nodeConfigCompleter(""));
-        registry.register(factory.nodeNameCompleter(NODE_NAME_OPTION, 
NODE_NAME_OPTION_SHORT));
+        registry.register(
+                CompleterConf.builder()
+                        .command("cluster", "config", "show")
+                        .command("cluster", "config", "update").build(),
+                clusterConfigDynamicCompleterFactory
+        );
+        registry.register(
+                CompleterConf.builder()
+                        .command("node", "config", "show")
+                        .command("node", "config", "update").build(),
+                nodeConfigDynamicCompleterFactory
+        );
+        // exclusive option that disables other completers for node name
+        registry.register(
+                CompleterConf.builder()
+                        .enableOptions(Options.NODE_NAME)
+                        .exclusiveEnableOptions().build(),
+                nodeNameDynamicCompleterFactory
+        );
+        registry.register(
+                CompleterConf.forCommand("connect"),
+                nodeNameDynamicCompleterFactory
+        );
+        registry.register(
+                CompleterConf.builder()
+                        .command("cluster", "init")
+                        .enableOptions(Options.META_STORAGE_NODE_NAME, 
Options.CMG_NODE_NAME)
+                        .build(),
+                nodeNameDynamicCompleterFactory
+        );
     }
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterFactory.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterFactory.java
index 8927f1695b..fb5f8842c0 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterFactory.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterFactory.java
@@ -17,89 +17,13 @@
 
 package org.apache.ignite.internal.cli.core.repl.completer;
 
-import com.typesafe.config.Config;
-import com.typesafe.config.ConfigFactory;
-import io.micronaut.context.annotation.Bean;
-import java.util.Set;
-import org.apache.ignite.internal.cli.NodeNameRegistry;
-import org.apache.ignite.internal.cli.call.configuration.ClusterConfigShowCall;
-import 
org.apache.ignite.internal.cli.call.configuration.ClusterConfigShowCallInput;
-import org.apache.ignite.internal.cli.call.configuration.NodeConfigShowCall;
-import 
org.apache.ignite.internal.cli.call.configuration.NodeConfigShowCallInput;
-
 /**
- * Factory that creates {@link DynamicCompleter}s.
+ * Factory that is responsible for defining the lifecycle of {@link 
DynamicCompleter}.
+ * It is called each time the completer is needed. So, the factory should 
cache the completer
+ * if there is no need to update it.
  */
-@Bean
-public class DynamicCompleterFactory {
-    private final NodeConfigShowCall nodeConfigShowCall;
-    private final ClusterConfigShowCall clusterConfigShowCall;
-    private final NodeUrlProvider urlProvider;
-    private final NodeNameRegistry nodeNameRegistry;
-
-    /** Default constructor. */
-    public DynamicCompleterFactory(
-            NodeConfigShowCall nodeConfigShowCall,
-            ClusterConfigShowCall clusterConfigShowCall,
-            NodeUrlProvider urlProvider,
-            NodeNameRegistry nodeNameRegistry) {
-
-        this.nodeConfigShowCall = nodeConfigShowCall;
-        this.clusterConfigShowCall = clusterConfigShowCall;
-        this.urlProvider = urlProvider;
-        this.nodeNameRegistry = nodeNameRegistry;
-    }
-
-    /** Creates node config completer with given activation prefix. */
-    public LazyDynamicCompleter nodeConfigCompleter(String activationPrefix) {
-        return nodeConfigCompleter(Set.of(activationPrefix));
-    }
-
-    /** Creates node config completer with given set of activation prefixes. */
-    public LazyDynamicCompleter nodeConfigCompleter(Set<String> 
activationPrefixes) {
-        return new LazyDynamicCompleter(() -> {
-            try {
-                Config config = ConfigFactory.parseString(
-                        nodeConfigShowCall.execute(
-                                // todo 
https://issues.apache.org/jira/browse/IGNITE-17416
-                                
NodeConfigShowCallInput.builder().nodeUrl(urlProvider.resolveUrl(new 
String[]{""})).build()
-                        ).body().getValue()
-                );
-                return new HoconDynamicCompleter(activationPrefixes, config);
-            } catch (Exception e) {
-                return new HoconDynamicCompleter(activationPrefixes, 
ConfigFactory.parseString(""));
-            }
-        });
-    }
-
-    /** Creates cluster config completer with given activation prefix. */
-    public LazyDynamicCompleter clusterConfigCompleter(String 
activationPrefix) {
-        return clusterConfigCompleter(Set.of(activationPrefix));
-    }
-
-    /** Creates cluster config completer with given set of activation 
prefixes. */
-    public LazyDynamicCompleter clusterConfigCompleter(Set<String> 
activationPrefixes) {
-        return new LazyDynamicCompleter(() -> {
-            try {
-                Config config = ConfigFactory.parseString(
-                        clusterConfigShowCall.execute(
-                                // todo 
https://issues.apache.org/jira/browse/IGNITE-17416
-                                
ClusterConfigShowCallInput.builder().clusterUrl(urlProvider.resolveUrl(new 
String[]{""})).build()
-                        ).body().getValue()
-                );
-                return new HoconDynamicCompleter(activationPrefixes, config);
-            } catch (Exception e) {
-                return new HoconDynamicCompleter(activationPrefixes, 
ConfigFactory.parseString(""));
-            }
-        });
-    }
-
-    public DynamicCompleter nodeNameCompleter(String... activationPrefixes) {
-        return nodeNameCompleter(Set.of(activationPrefixes));
-    }
-
-    public DynamicCompleter nodeNameCompleter(Set<String> activationPrefixes) {
-        return new NodeNameDynamicCompleter(activationPrefixes, 
nodeNameRegistry);
-    }
-
+@FunctionalInterface
+public interface DynamicCompleterFactory {
+    /** Return an instance of {@link DynamicCompleter}. */
+    DynamicCompleter getDynamicCompleter(String[] words);
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterFilter.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterFilter.java
index ef345f98d1..59f5459438 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterFilter.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterFilter.java
@@ -17,11 +17,17 @@
 
 package org.apache.ignite.internal.cli.core.repl.completer;
 
-import static 
org.apache.ignite.internal.cli.commands.OptionsConstants.CLUSTER_URL_OPTION;
-import static 
org.apache.ignite.internal.cli.commands.OptionsConstants.NODE_URL_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.CLUSTER_URL_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.HELP_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.HELP_OPTION_SHORT;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.NODE_URL_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.VERBOSE_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.VERBOSE_OPTION_SHORT;
 
 import jakarta.inject.Singleton;
 import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
 import org.apache.ignite.internal.cli.core.repl.Session;
 
 /**
@@ -43,25 +49,31 @@ public class DynamicCompleterFilter implements 
CompleterFilter {
 
     @Override
     public String[] filter(String[] words, String[] candidates) {
+        List<String> notOptionsCandidates = Arrays.stream(candidates)
+                .filter(candidate -> !candidate.startsWith("-"))
+                .collect(Collectors.toList());
+
+        if (!notOptionsCandidates.isEmpty()) {
+            return notOptionsCandidates.toArray(String[]::new);
+        }
+
         return Arrays.stream(candidates)
                 .filter(candidate -> filterClusterUrl(words, candidate))
-                .filter(candidate -> filterHelp(words, candidate))
+                .filter(candidate -> filterCommonOptions(words, candidate))
                 .toArray(String[]::new);
     }
 
-    private boolean filterHelp(String[] words, String candidate) {
-        if (optionTyped(words)) {
-            return true;
-        }
-
-        return !(candidate.equals("--help") || candidate.equals("-h"));
+    private boolean filterCommonOptions(String[] words, String candidate) {
+        return optionTyped(words)
+                || !(HELP_OPTION.equals(candidate)
+                || HELP_OPTION_SHORT.equals(candidate)
+                || VERBOSE_OPTION_SHORT.equals(candidate)
+                || VERBOSE_OPTION.equals(candidate));
     }
 
     private boolean filterClusterUrl(String[] words, String candidate) {
-        if (optionTyped(words)) {
-            return true;
-        }
-
-        return !session.isConnectedToNode() || 
(!candidate.equals(CLUSTER_URL_OPTION) && !candidate.equals(NODE_URL_OPTION));
+        return optionTyped(words)
+                || !session.isConnectedToNode()
+                || (!candidate.equals(CLUSTER_URL_OPTION) && 
!candidate.equals(NODE_URL_OPTION));
     }
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterRegistry.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterRegistry.java
index db2348f759..3dc6da5d60 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterRegistry.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterRegistry.java
@@ -18,12 +18,14 @@
 package org.apache.ignite.internal.cli.core.repl.completer;
 
 import static 
org.apache.ignite.internal.cli.util.ArrayUtils.findLastNotEmptyWord;
+import static 
org.apache.ignite.internal.cli.util.ArrayUtils.findLastNotEmptyWordBeforeWordFromEnd;
 
 import jakarta.inject.Singleton;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
-import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 /**
@@ -38,60 +40,97 @@ public class DynamicCompleterRegistry {
     public List<DynamicCompleter> findCompleters(String[] words) {
         return completionStrategiesList.stream()
                 .filter(strategy -> strategy.canBeApplied(words))
-                .map(CompletionStrategy::completer)
+                .map(strategy -> strategy.completer(words))
                 .collect(Collectors.toList());
     }
 
-    public void register(DynamicCompleter completer) {
-        completionStrategiesList.add(new CompletionStrategy(ignored -> true, 
completer));
-    }
-
     /** Registers dynamic completer that can be found by given predicate. */
-    public void register(Predicate<String[]> predicate, DynamicCompleter 
completer) {
-        completionStrategiesList.add(new CompletionStrategy(predicate, 
completer));
-    }
+    public void register(CompleterConf conf, DynamicCompleterFactory factory) {
+        if (conf.isExclusiveEnableOptions()) {
+            // add disable option for all strategies because current 
configuration has exclusive enable option
+            completionStrategiesList.forEach(strategy -> 
strategy.exclusiveDisableOptions.addAll(conf.enableOptions()));
+        }
 
-    /** Registers dynamic completer that can be found by given prefix. */
-    public void register(String[] prefixWords, DynamicCompleter completer) {
-        register((String[] words) -> samePrefix(words, prefixWords), 
completer);
-    }
+        Set<String> exclusiveDisableOptions = completionStrategiesList.stream()
+                .filter(strategy -> strategy.conf.isExclusiveEnableOptions())
+                .flatMap(strategy -> strategy.conf.enableOptions().stream())
+                .collect(Collectors.toSet());
 
-    /** Registers dynamic completer that can be found by given prefix. */
-    public void register(String[] prefixWords, String[] stopPostfixWords, 
DynamicCompleter completer) {
-        register((String[] words) -> samePrefix(words, prefixWords) && 
notSamePostfix(words, stopPostfixWords), completer);
+        CompletionStrategy strategy = new CompletionStrategy(conf, factory);
+        strategy.exclusiveDisableOptions.addAll(exclusiveDisableOptions);
+        completionStrategiesList.add(strategy);
     }
 
-    private boolean samePrefix(String[] words, String[] prefixWords) {
-        if (words.length < prefixWords.length) {
-            return false;
+    private static class CompletionStrategy {
+        private final CompleterConf conf;
+
+        private final DynamicCompleterFactory factory;
+
+        private final Set<String> exclusiveDisableOptions = new HashSet<>();
+
+        private CompletionStrategy(CompleterConf conf, DynamicCompleterFactory 
factory) {
+            this.conf = conf;
+            this.factory = factory;
         }
-        for (int i = 0; i < prefixWords.length; i++) {
-            if (!words[i].equals(prefixWords[i])) {
+
+        private static boolean samePrefix(String[] words, String[] 
prefixWords) {
+            if (words.length < prefixWords.length) {
                 return false;
             }
+            for (int i = 0; i < prefixWords.length; i++) {
+                if (!words[i].equals(prefixWords[i])) {
+                    return false;
+                }
+            }
+            return true;
         }
-        return true;
-    }
-
-    private boolean notSamePostfix(String[] words, String[] stopPostfixWords) {
-        return words.length > 0 && 
!Set.of(stopPostfixWords).contains(findLastNotEmptyWord(words));
-    }
 
-    private static class CompletionStrategy {
-        private final Predicate<String[]> predicate;
-        private final DynamicCompleter completer;
+        boolean canBeApplied(String[] words) {
+            // empty command means can be applied to all words
+            if (!conf.commandSpecific()) {
+                return canBeAppliedCommandMatch(words);
+            }
 
-        private CompletionStrategy(Predicate<String[]> predicate, 
DynamicCompleter completer) {
-            this.predicate = predicate;
-            this.completer = completer;
+            Optional<String[]> commandsMatch = 
conf.commands().stream().filter(command -> samePrefix(words, 
command)).findFirst();
+            return commandsMatch.isPresent() && 
canBeAppliedCommandMatch(words);
         }
 
-        boolean canBeApplied(String[] words) {
-            return predicate.test(words);
+        private boolean canBeAppliedCommandMatch(String[] words) {
+            String cursorWord = words[words.length - 1];
+            String lastNotEmptyWord = findLastNotEmptyWord(words);
+            String preLastNotEmptyWord = 
findLastNotEmptyWordBeforeWordFromEnd(words, lastNotEmptyWord);
+
+            if (cursorWord.equals(lastNotEmptyWord)) {
+                if (exclusiveDisableOptions.contains(lastNotEmptyWord) || 
exclusiveDisableOptions.contains(preLastNotEmptyWord)) {
+                    return false;
+                }
+            } else if (exclusiveDisableOptions.contains(lastNotEmptyWord)) {
+                return false;
+            }
+
+            if (conf.hasEnableOptions()) {
+                if (cursorWord.equals(lastNotEmptyWord)) {
+                    return conf.enableOptions().contains(lastNotEmptyWord) // 
command subcommand --enable-option
+                            || 
conf.enableOptions().contains(preLastNotEmptyWord); // command subcommand 
--enable-option lastWord
+                } else {
+                    return conf.enableOptions().contains(lastNotEmptyWord); // 
command subcommand --enable-option <space>
+                }
+            }
+
+            if (conf.hasDisableOptions()) {
+                if (cursorWord.equals(lastNotEmptyWord)) {
+                    return !conf.disableOptions().contains(lastNotEmptyWord)
+                             && 
!conf.disableOptions().contains(preLastNotEmptyWord);
+                } else {
+                    return !conf.disableOptions().contains(lastNotEmptyWord);
+                }
+            }
+
+            return true;
         }
 
-        DynamicCompleter completer() {
-            return completer;
+        DynamicCompleter completer(String[] words) {
+            return factory.getDynamicCompleter(words);
         }
     }
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/NodeNameDynamicCompleter.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/NodeNameDynamicCompleter.java
deleted file mode 100644
index a4d5be7e8b..0000000000
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/NodeNameDynamicCompleter.java
+++ /dev/null
@@ -1,61 +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.internal.cli.core.repl.completer;
-
-import static 
org.apache.ignite.internal.cli.util.ArrayUtils.findLastNotEmptyWord;
-import static 
org.apache.ignite.internal.cli.util.ArrayUtils.findLastNotEmptyWordBeforeWordFromEnd;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-import org.apache.ignite.internal.cli.NodeNameRegistry;
-
-/**
- * Completes typed words with node names.
- */
-public class NodeNameDynamicCompleter implements DynamicCompleter {
-
-    /** Words, after those the completer should have been activated. */
-    private final Set<String> activationPostfixes;
-
-    /** Node names registry. */
-    private final NodeNameRegistry nodeNameRegistry;
-
-    /** Default constructor that creates an instance from a given set of 
postfixes that trigger the completion. */
-    public NodeNameDynamicCompleter(Set<String> activationPostfixes, 
NodeNameRegistry nodeNameRegistry) {
-        this.activationPostfixes = activationPostfixes;
-        this.nodeNameRegistry = nodeNameRegistry;
-    }
-
-    @Override
-    public List<String> complete(String[] words) {
-        String lastWord = findLastNotEmptyWord(words);
-        String beforeLastWord = findLastNotEmptyWordBeforeWordFromEnd(words, 
lastWord);
-        if (activationPostfixes.contains(lastWord)) {
-            return new ArrayList<>(nodeNameRegistry.getAllNames());
-        } else if (activationPostfixes.contains(beforeLastWord)) {
-            return nodeNameRegistry.getAllNames().stream()
-                    .filter(it -> it.startsWith(lastWord))
-                    .collect(Collectors.toList());
-        } else {
-            return Collections.emptyList();
-        }
-    }
-}
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/NodeUrlProvider.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/NodeUrlProvider.java
index 89f407abec..90126810da 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/NodeUrlProvider.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/NodeUrlProvider.java
@@ -17,8 +17,9 @@
 
 package org.apache.ignite.internal.cli.core.repl.completer;
 
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.CLUSTER_URL_OPTION;
+
 import jakarta.inject.Singleton;
-import org.apache.ignite.internal.cli.commands.OptionsConstants;
 import org.apache.ignite.internal.cli.config.ConfigConstants;
 import org.apache.ignite.internal.cli.config.ConfigManagerProvider;
 import org.apache.ignite.internal.cli.core.repl.Session;
@@ -54,7 +55,7 @@ public class NodeUrlProvider {
 
     private String findClusterUrlIn(String[] words) {
         for (String word : words) {
-            String prefix = OptionsConstants.CLUSTER_URL_OPTION + "=";
+            String prefix = CLUSTER_URL_OPTION + "=";
             if (word.startsWith(prefix)) {
                 return word.substring(prefix.length());
             }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/hocon/ClusterConfigDynamicCompleterFactory.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/hocon/ClusterConfigDynamicCompleterFactory.java
new file mode 100644
index 0000000000..e745ae105a
--- /dev/null
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/hocon/ClusterConfigDynamicCompleterFactory.java
@@ -0,0 +1,71 @@
+/*
+ * 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.internal.cli.core.repl.completer.hocon;
+
+import com.typesafe.config.Config;
+import com.typesafe.config.ConfigFactory;
+import jakarta.inject.Singleton;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.ignite.internal.cli.call.configuration.ClusterConfigShowCall;
+import 
org.apache.ignite.internal.cli.call.configuration.ClusterConfigShowCallInput;
+import org.apache.ignite.internal.cli.core.repl.completer.DummyCompleter;
+import org.apache.ignite.internal.cli.core.repl.completer.DynamicCompleter;
+import 
org.apache.ignite.internal.cli.core.repl.completer.DynamicCompleterFactory;
+import org.apache.ignite.internal.cli.core.repl.completer.NodeUrlProvider;
+
+/** Factory for cluster config show/update command. */
+@Singleton
+public class ClusterConfigDynamicCompleterFactory implements 
DynamicCompleterFactory {
+
+    private final NodeUrlProvider urlProvider;
+
+    private final ClusterConfigShowCall clusterConfigShowCall;
+
+    private final AtomicReference<DynamicCompleter> cachedCompleter = new 
AtomicReference<>(null);
+
+    public ClusterConfigDynamicCompleterFactory(NodeUrlProvider urlProvider, 
ClusterConfigShowCall clusterConfigShowCall) {
+        this.urlProvider = urlProvider;
+        this.clusterConfigShowCall = clusterConfigShowCall;
+    }
+
+    @Override
+    public DynamicCompleter getDynamicCompleter(String[] words) {
+        if (cachedCompleter.getAcquire() != null) {
+            return cachedCompleter.getAcquire();
+        } else {
+            // call REST on the background in order not to freeze UI thread
+            CompletableFuture.runAsync(() -> {
+                try {
+                    Config config = ConfigFactory.parseString(
+                            clusterConfigShowCall.execute(
+                                    // todo 
https://issues.apache.org/jira/browse/IGNITE-17416
+                                    
ClusterConfigShowCallInput.builder().clusterUrl(urlProvider.resolveUrl(new 
String[]{""})).build()
+                            ).body().getValue()
+                    );
+                    cachedCompleter.compareAndExchangeRelease(null, new 
HoconDynamicCompleter(config));
+                } catch (Exception ignored) {
+                    // no-op
+                }
+            });
+        }
+
+        // return dummy completer this time, but hope the next call will 
return cached completer
+        return new DummyCompleter();
+    }
+}
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/HoconDynamicCompleter.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/hocon/HoconDynamicCompleter.java
similarity index 73%
rename from 
modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/HoconDynamicCompleter.java
rename to 
modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/hocon/HoconDynamicCompleter.java
index d02a20bb5e..ff71bf6afd 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/HoconDynamicCompleter.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/hocon/HoconDynamicCompleter.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.cli.core.repl.completer;
+package org.apache.ignite.internal.cli.core.repl.completer.hocon;
 
 import static 
org.apache.ignite.internal.cli.util.ArrayUtils.findLastNotEmptyWord;
 
@@ -25,10 +25,9 @@ import java.util.List;
 import java.util.Map.Entry;
 import java.util.Set;
 import java.util.stream.Collectors;
+import org.apache.ignite.internal.cli.core.repl.completer.DynamicCompleter;
 
-/**
- * Completes typed words with hocon schema keys.
- */
+/** Completes typed words with hocon schema keys. */
 public class HoconDynamicCompleter implements DynamicCompleter {
 
     private final Config config;
@@ -39,12 +38,8 @@ public class HoconDynamicCompleter implements 
DynamicCompleter {
     /** List of all possible completions. */
     private final List<String> completions;
 
-    /** Words, after those the completer should have been activated. */
-    private final Set<String> activationPostfixes;
-
     /** Default constructor that creates an instance from a given set of 
postfixes that trigger the completion. */
-    public HoconDynamicCompleter(Set<String> activationPostfixes, Config 
config) {
-        this.activationPostfixes = activationPostfixes;
+    public HoconDynamicCompleter(Config config) {
         this.config = config;
         this.leafs = 
config.entrySet().stream().map(Entry::getKey).collect(Collectors.toSet());
         this.completions = this.compile();
@@ -61,23 +56,21 @@ public class HoconDynamicCompleter implements 
DynamicCompleter {
 
     /**
      * Completes the given typed words with the config keys that are in the 
same level as the last typed words.
-     * <p/>
-     * Example: given typed words ["cluster", "config", "show", "a"], The last 
word is "a", only root config values will be
-     * suggested to autocomplete: "aimem", "aipersist". If user hits "aimem" 
and types dot "." then only subkeys of "aimem." will be
-     * suggested: "aimem.pageSize", "aimem.regions".
+     *
+     * <p>Example: given typed words ["cluster", "config", "show", "a"], The 
last word is "a", only root config values will be suggested to
+     * autocomplete: "aimem", "aipersist". If user hits "aimem" and types dot 
"." then only subkeys of "aimem." will be suggested:
+     * "aimem.pageSize", "aimem.regions".
      */
     @Override
     public List<String> complete(String[] words) {
-        final String lastWord = findLastNotEmptyWord(words);
+        String lastWord = findLastNotEmptyWord(words);
 
-        if (activationPostfixes.contains(lastWord)
-                // activation profile contains empty string and the last typed 
word is empty
-                || (activationPostfixes.contains("") && words[words.length - 
1].isEmpty())) {
+        if (words[words.length - 1].isEmpty()) {
             // roots
             return completions.stream().filter(s -> s.split("\\.").length == 
1).collect(Collectors.toList());
         }
 
-        final int deepLevel = lastWord.endsWith(".")
+        int deepLevel = lastWord.endsWith(".")
                 ? lastWord.split("\\.").length + 1
                 : lastWord.split("\\.").length;
 
@@ -87,7 +80,6 @@ public class HoconDynamicCompleter implements 
DynamicCompleter {
     }
 
 
-
     private void walkAndAdd(String keyPrefix, Set<String> keySet, List<String> 
result) {
         keySet.forEach(key -> {
             if (!leafs.contains(keyPrefix + key)) {
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/hocon/NodeConfigDynamicCompleterFactory.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/hocon/NodeConfigDynamicCompleterFactory.java
new file mode 100644
index 0000000000..7de52d4c85
--- /dev/null
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/hocon/NodeConfigDynamicCompleterFactory.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.internal.cli.core.repl.completer.hocon;
+
+import com.typesafe.config.Config;
+import com.typesafe.config.ConfigFactory;
+import jakarta.inject.Singleton;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.ignite.internal.cli.call.configuration.NodeConfigShowCall;
+import 
org.apache.ignite.internal.cli.call.configuration.NodeConfigShowCallInput;
+import org.apache.ignite.internal.cli.core.repl.completer.DummyCompleter;
+import org.apache.ignite.internal.cli.core.repl.completer.DynamicCompleter;
+import 
org.apache.ignite.internal.cli.core.repl.completer.DynamicCompleterFactory;
+import org.apache.ignite.internal.cli.core.repl.completer.NodeUrlProvider;
+
+/** Factory for node config show/update completer. */
+@Singleton
+public class NodeConfigDynamicCompleterFactory implements 
DynamicCompleterFactory {
+    private final NodeUrlProvider urlProvider;
+
+    private final NodeConfigShowCall nodeConfigShowCall;
+
+    private final AtomicReference<DynamicCompleter> cachedCompleter = new 
AtomicReference<>(null);
+
+    public NodeConfigDynamicCompleterFactory(NodeUrlProvider urlProvider, 
NodeConfigShowCall nodeConfigShowCall) {
+        this.urlProvider = urlProvider;
+        this.nodeConfigShowCall = nodeConfigShowCall;
+    }
+
+    @Override
+    public DynamicCompleter getDynamicCompleter(String[] words) {
+        if (cachedCompleter.getAcquire() != null) {
+            return cachedCompleter.getAcquire();
+        } else {
+            // call REST on the background in order not to freeze UI thread
+            CompletableFuture.runAsync(() -> {
+                try {
+                    Config config = ConfigFactory.parseString(
+                            nodeConfigShowCall.execute(
+                                    // todo 
https://issues.apache.org/jira/browse/IGNITE-17416
+                                    
NodeConfigShowCallInput.builder().nodeUrl(urlProvider.resolveUrl(new 
String[]{""})).build()
+                            ).body().getValue()
+                    );
+                    cachedCompleter.compareAndExchangeRelease(null, new 
HoconDynamicCompleter(config));
+                } catch (Exception ignored) {
+                    // no-op
+                }
+            });
+        }
+
+        // return dummy completer this time, but hope the next call will 
return cached completer
+        return new DummyCompleter();
+    }
+}
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/ProfileMixin.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/node/NodeNameDynamicCompleterFactory.java
similarity index 50%
copy from 
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/ProfileMixin.java
copy to 
modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/node/NodeNameDynamicCompleterFactory.java
index d0cf65817b..3d26caa1fc 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/ProfileMixin.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/node/NodeNameDynamicCompleterFactory.java
@@ -15,27 +15,25 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.cli.commands;
+package org.apache.ignite.internal.cli.core.repl.completer.node;
 
-import static 
org.apache.ignite.internal.cli.commands.OptionsConstants.PROFILE_OPTION;
-import static 
org.apache.ignite.internal.cli.commands.OptionsConstants.PROFILE_OPTION_DESC;
-import static 
org.apache.ignite.internal.cli.commands.OptionsConstants.PROFILE_OPTION_SHORT;
+import jakarta.inject.Singleton;
+import org.apache.ignite.internal.cli.NodeNameRegistry;
+import org.apache.ignite.internal.cli.core.repl.completer.DynamicCompleter;
+import 
org.apache.ignite.internal.cli.core.repl.completer.DynamicCompleterFactory;
 
-import picocli.CommandLine.Option;
+/** Factory for --node-name option completer. */
+@Singleton
+public class NodeNameDynamicCompleterFactory implements 
DynamicCompleterFactory {
 
-/**
- * Mixin for profile option.
- */
-public class ProfileMixin {
-    @Option(names = {PROFILE_OPTION_SHORT, PROFILE_OPTION}, description = 
PROFILE_OPTION_DESC)
-    private String profileName;
+    private final NodeNameRegistry nodeNameRegistry;
+
+    public NodeNameDynamicCompleterFactory(NodeNameRegistry nodeNameRegistry) {
+        this.nodeNameRegistry = nodeNameRegistry;
+    }
 
-    /**
-     * Gets profile name.
-     *
-     * @return profile name
-     */
-    public String getProfileName() {
-        return profileName;
+    @Override
+    public DynamicCompleter getDynamicCompleter(String[] words) {
+        return new StringDynamicCompleter(nodeNameRegistry.getAllNames());
     }
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/node/StringDynamicCompleter.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/node/StringDynamicCompleter.java
new file mode 100644
index 0000000000..020c98224b
--- /dev/null
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/node/StringDynamicCompleter.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.internal.cli.core.repl.completer.node;
+
+import static 
org.apache.ignite.internal.cli.util.ArrayUtils.findLastNotEmptyWord;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.ignite.internal.cli.core.repl.completer.DynamicCompleter;
+
+/**
+ * Completes typed words with provided list of strings.
+ */
+public class StringDynamicCompleter implements DynamicCompleter {
+
+    /** Values that will ve suggested. */
+    private final Set<String> values;
+
+    /** Default constructor. */
+    public StringDynamicCompleter(Set<String> values) {
+        this.values = values;
+    }
+
+    @Override
+    public List<String> complete(String[] words) {
+        if (words[words.length - 1].isBlank()) {
+            return new ArrayList<>(values);
+        }
+
+        String lastWord = findLastNotEmptyWord(words);
+        return values.stream()
+                .filter(it -> it.startsWith(lastWord))
+                .collect(Collectors.toList());
+    }
+}
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/IgnitePicocliCommands.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/IgnitePicocliCommands.java
index db4d26ef35..704f547ab4 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/IgnitePicocliCommands.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/IgnitePicocliCommands.java
@@ -201,7 +201,7 @@ public class IgnitePicocliCommands implements 
CommandRegistry {
             }
 
             List<DynamicCompleter> completers = 
completerRegistry.findCompleters(words);
-            if (completers.size() > 0) {
+            if (!completers.isEmpty()) {
                 try {
                     completers.stream()
                             .map(c -> c.complete(words))
@@ -213,13 +213,17 @@ public class IgnitePicocliCommands implements 
CommandRegistry {
                 }
             }
 
+            if (!candidates.isEmpty()) {
+                return;
+            }
+
             String[] filteredCandidates = 
completerFilters.get(0).filter(words, staticCandidates.toArray(String[]::new));
             for (int i = 1; i < completerFilters.size(); i++) {
                 filteredCandidates = completerFilters.get(i).filter(words, 
filteredCandidates);
             }
 
             for (String c : filteredCandidates) {
-                candidates.add(new Candidate(c));
+                candidates.add(staticCandidate(c));
             }
         }
 
@@ -230,5 +234,9 @@ public class IgnitePicocliCommands implements 
CommandRegistry {
 
             return new Candidate(one, one, null, null, null, null, false, 
sortingPriority);
         }
+
+        private Candidate staticCandidate(String one) {
+            return new Candidate(one, one, null, null, null, null, true, 10);
+        }
     }
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/ReplExecutor.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/ReplExecutor.java
index 1290263099..a8bfeabc8f 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/ReplExecutor.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/executor/ReplExecutor.java
@@ -168,6 +168,7 @@ public class ReplExecutor {
         CommandLineContextProvider.setCmd(cmd);
         cmd.setExecutionExceptionHandler(new 
PicocliExecutionExceptionHandler());
         cmd.registerConverter(NodeNameOrUrl.class, new 
NodeNameOrUrlConverter(nodeNameRegistry));
+
         DynamicCompleterRegistry completerRegistry = 
factory.create(DynamicCompleterRegistry.class);
         DynamicCompleterActivationPoint activationPoint = 
factory.create(DynamicCompleterActivationPoint.class);
         activationPoint.activateDynamicCompleter(completerRegistry);
diff --git 
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterRegistryTest.java
 
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterRegistryTest.java
index cf7f453f98..52b52206c2 100644
--- 
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterRegistryTest.java
+++ 
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterRegistryTest.java
@@ -18,12 +18,16 @@
 package org.apache.ignite.internal.cli.core.repl.completer;
 
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.both;
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.is;
 
 import java.util.List;
+import org.apache.ignite.internal.cli.commands.Options;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 
 class DynamicCompleterRegistryTest {
@@ -36,6 +40,11 @@ class DynamicCompleterRegistryTest {
 
     DynamicCompleter completer3;
 
+    /** Makes reading easier. */
+    private static String[] words(String... words) {
+        return words;
+    }
+
     @BeforeEach
     void setUp() {
         registry = new DynamicCompleterRegistry();
@@ -45,28 +54,30 @@ class DynamicCompleterRegistryTest {
     }
 
     @Test
-    void findsCompletersRegisteredWithPredicate() {
+    void findsCompleterRegisteredWithMultyCommand() {
         // Given
-        registry.register(words -> words[0].equals("command1"), completer1);
-        registry.register(words -> words[0].equals("command2"), completer2);
-        registry.register(words -> words[0].equals("command2"), completer3);
-
-        // When find completers for "command1"
-        List<DynamicCompleter> completers = registry.findCompleters(new 
String[]{"command1"});
+        registry.register(
+                CompleterConf.builder()
+                        .command("command1")
+                        .command("command2").build(),
+                words -> completer1
+        );
 
         // Then
-        assertThat(completers, containsInAnyOrder(completer1));
+        assertThat(registry.findCompleters(words("command1")), 
containsInAnyOrder(completer1));
+        // And
+        assertThat(registry.findCompleters(words("command2")), 
containsInAnyOrder(completer1));
     }
 
     @Test
     void findsCompletersRegisteredWithStaticCommand() {
         // Given
-        registry.register(new String[]{"command1", "subcommand1"}, completer1);
-        registry.register(new String[]{"command1", "subcommand1"}, completer2);
-        registry.register(new String[]{"command2"}, completer3);
+        registry.register(CompleterConf.forCommand("command1", "subcommand1"), 
words -> completer1);
+        registry.register(CompleterConf.forCommand("command1", "subcommand1"), 
words -> completer2);
+        registry.register(CompleterConf.forCommand("command2"), words -> 
completer3);
 
         // When find completers for "command1"
-        List<DynamicCompleter> completers = registry.findCompleters(new 
String[]{"command1", "subcommand1"});
+        List<DynamicCompleter> completers = 
registry.findCompleters(words("command1", "subcommand1"));
 
         // Then
         assertThat(completers, containsInAnyOrder(completer1, completer2));
@@ -75,33 +86,207 @@ class DynamicCompleterRegistryTest {
     @Test
     void returnsEmptyCollectionIfThereIsNoSuitableCompleter() {
         // Given
-        registry.register(new String[]{"command1", "subcommand1"}, completer1);
-        registry.register(new String[]{"command1", "subcommand1"}, completer2);
-        registry.register(new String[]{"command2"}, completer3);
+        registry.register(CompleterConf.forCommand("command1", "subcommand1"), 
words -> completer1);
+        registry.register(CompleterConf.forCommand("command1", "subcommand1"), 
words -> completer2);
+        registry.register(CompleterConf.forCommand("command2"), words -> 
completer3);
 
         // Then
-        assertThat(registry.findCompleters(new String[]{"command1"}), 
is(empty()));
-        assertThat(registry.findCompleters(new String[]{"command3"}), 
is(empty()));
+        assertThat(registry.findCompleters(words("command1")), is(empty()));
+        assertThat(registry.findCompleters(words("command3")), is(empty()));
         // But if command start with one of the registered prefixes
         assertThat(
-                registry.findCompleters(new String[]{"command1", 
"subcommand1", "subsubcommand1"}),
+                registry.findCompleters(words("command1", "subcommand1", 
"subsubcommand1")),
                 containsInAnyOrder(completer1, completer2)
         );
     }
 
     @Test
-    void doesntReturnCompleterWithStopPostfixWords() {
+    void doesntReturnCompleterWithDisableOptions() {
         // Given
-        registry.register(new String[]{"command1", "subcommand1"}, new 
String[]{"--stopWord"}, completer1);
-        registry.register(new String[]{"command1", "subcommand1"}, new 
String[]{"--stopWord"}, completer2);
+        registry.register(
+                CompleterConf.builder().command("command1", 
"subcommand1").disableOptions("--stopWord").build(),
+                words -> completer1
+        );
 
         // Then
         assertThat(
-                registry.findCompleters(new String[]{"command1", 
"subcommand1", "subsubcommand1"}),
-                containsInAnyOrder(completer1, completer2)
+                registry.findCompleters(words("command1", "subcommand1", 
"subsub1")),
+                containsInAnyOrder(completer1)
+        );
+        assertThat(
+                registry.findCompleters(words("command1", "subcommand1", 
"subsub1", "")),
+                containsInAnyOrder(completer1)
+        );
+        assertThat(
+                registry.findCompleters(words("command1", "subcommand1", "", 
"")),
+                containsInAnyOrder(completer1)
         );
 
         // But if command ends with a stop word
-        assertThat(registry.findCompleters(new String[]{"command1", 
"subcommand1", "--stopWord"}), is(empty()));
+        assertThat(
+                registry.findCompleters(words("command1", "subcommand1", 
"--stopWord")),
+                is(empty())
+        );
+        assertThat(
+                registry.findCompleters(words("command1", "subcommand1", 
"--stopWord", "")),
+                is(empty())
+        );
+        assertThat(
+                registry.findCompleters(words("command1", "subcommand1", 
"--stopWord", "do-not-complete-me")),
+                is(empty())
+        );
+        assertThat(
+                registry.findCompleters(words("command1", "subcommand1", 
"--stopWord", "do-not-complete-me", "but-complete-me")),
+                containsInAnyOrder(completer1)
+        );
+    }
+
+    @Test
+    @DisplayName("If enable options are provided than they are taken into 
account")
+    void enableOptions() {
+        // Given completer with enable option
+        registry.register(
+                CompleterConf.builder()
+                        .command("command1")
+                        .enableOptions("--complete-after-me", "-com")
+                        .build(),
+                words -> completer1
+        );
+
+        // Then for words without those options there are no completers
+        assertThat(registry.findCompleters(words("command1")), is(empty()));
+        // And enable option should not be completed itself
+        assertThat(registry.findCompleters(words("command1", "-co")), 
is(empty()));
+
+        // But if any of the enable options are provided then completer is 
found
+        assertThat(registry.findCompleters(words("command1", "some", 
"--complete-after-me", "")), containsInAnyOrder(completer1));
+        assertThat(registry.findCompleters(words("command1", "-com", "")), 
containsInAnyOrder(completer1));
+        assertThat(registry.findCompleters(words("command1", "-com", " ")), 
containsInAnyOrder(completer1));
+        assertThat(registry.findCompleters(words("command1", "-com", 
"autocompleteme")), containsInAnyOrder(completer1));
+    }
+
+    @Test
+    @DisplayName("Combination of completers with the same enable/disable 
options")
+    void combinationOfCompleters() {
+        // Given first completer with disable option
+        registry.register(
+                CompleterConf.builder()
+                        .command("node", "config", "show")
+                        .disableOptions("-n").build(),
+                words -> completer1
+        );
+        // And the second completer with the same enable option
+        registry.register(
+                CompleterConf.builder().enableOptions("-n").build(),
+                (words -> completer2)
+        );
+
+        // Then they do not intersect
+        assertThat(
+                registry.findCompleters(words("node", "config", "show", 
"-n")), // "-n" is competed by completer2
+                both(hasSize(1)).and(containsInAnyOrder(completer2))
+        );
+        assertThat(
+                registry.findCompleters(words("node", "config", "show", "-n", 
"nodeName")),
+                both(hasSize(1)).and(containsInAnyOrder(completer2))
+        );
+        assertThat(
+                registry.findCompleters(words("node", "config", "show", "-n", 
"nodeName", " ")),
+                both(hasSize(1)).and(containsInAnyOrder(completer1))
+        );
+        assertThat(
+                registry.findCompleters(words("node", "config", "show", "-n", 
"nodeName", "completeMe")),
+                both(hasSize(1)).and(containsInAnyOrder(completer1))
+        );
+    }
+
+    @Test
+    @DisplayName("If some completer has exclusive enable option set then all 
other completers have this option as disable option")
+    void exclusiveEnableOption() {
+        // Given completer with exclusive enable option -n
+        registry.register(
+                CompleterConf.builder()
+                        .command("node", "config", "show")
+                        .enableOptions(Options.NODE_NAME)
+                        .exclusiveEnableOptions().build(),
+                words -> completer1
+        );
+        // And completer for the same command
+        registry.register(
+                CompleterConf.forCommand("node", "config", "show"),
+                words -> completer2
+        );
+        // And common completer for other option
+        registry.register(
+                CompleterConf.builder().enableOptions("-l").build(),
+                words -> completer3
+        );
+
+        // Then exclusive option is on the priority and no other completer is 
used for -n
+        assertThat(
+                registry.findCompleters(words("node", "config", "show", "-n")),
+                both(hasSize(1)).and(containsInAnyOrder(completer1))
+        );
+        assertThat(
+                registry.findCompleters(words("node", "config", "show", "-n", 
"")),
+                both(hasSize(1)).and(containsInAnyOrder(completer1))
+        );
+        assertThat(
+                registry.findCompleters(words("node", "config", "show", "-n", 
"nod")),
+                both(hasSize(1)).and(containsInAnyOrder(completer1))
+        );
+        // But other cases work well
+        assertThat(
+                registry.findCompleters(words("node", "config", "show", "-l", 
"nod")),
+                both(hasSize(2)).and(containsInAnyOrder(completer2, 
completer3))
+        );
+        assertThat(
+                registry.findCompleters(words("node", "config", "show", "-n", 
"node", " ")),
+                both(hasSize(1)).and(containsInAnyOrder(completer2))
+        );
+    }
+
+    @Test
+    @DisplayName(
+            "If some completer has exclusive enable option set then all other 
completers have this option as disable option, "
+                    + "registration order changed"
+    )
+    void exclusiveEnableOptionRegisterInAnotherOrder() {
+        // Given completers without exclusive enable option registered first
+        registry.register(
+                CompleterConf.forCommand("node", "config", "show"),
+                words -> completer2
+        );
+        registry.register(
+                CompleterConf.builder().enableOptions("-l").build(),
+                words -> completer3
+        );
+        // And exclusiveEnableOption is registered last
+        registry.register(
+                CompleterConf.builder()
+                        .command("node", "config", "show")
+                        .enableOptions("-n")
+                        .exclusiveEnableOptions().build(),
+                words -> completer1
+        );
+
+        // Then exclusive option is on the priority and no other completer is 
used for -n
+        assertThat(
+                registry.findCompleters(words("node", "config", "show", "-n", 
"nod")),
+                both(hasSize(1)).and(containsInAnyOrder(completer1))
+        );
+        assertThat(
+                registry.findCompleters(words("node", "config", "show", "-n")),
+                both(hasSize(1)).and(containsInAnyOrder(completer1))
+        );
+        // But other cases work well
+        assertThat(
+                registry.findCompleters(words("node", "config", "show", "-l", 
"nod")),
+                both(hasSize(2)).and(containsInAnyOrder(completer2, 
completer3))
+        );
+        assertThat(
+                registry.findCompleters(words("node", "config", "show", "-n", 
"node", "")),
+                both(hasSize(1)).and(containsInAnyOrder(completer2))
+        );
     }
 }
diff --git 
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/HoconDynamicCompleterTest.java
 
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/HoconDynamicCompleterTest.java
index b86356884f..6fa99a0496 100644
--- 
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/HoconDynamicCompleterTest.java
+++ 
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/HoconDynamicCompleterTest.java
@@ -23,7 +23,7 @@ import static org.hamcrest.Matchers.hasSize;
 
 import com.typesafe.config.ConfigFactory;
 import java.util.List;
-import java.util.Set;
+import 
org.apache.ignite.internal.cli.core.repl.completer.hocon.HoconDynamicCompleter;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
@@ -31,8 +31,7 @@ class HoconDynamicCompleterTest {
     HoconDynamicCompleter completer;
 
     private static HoconDynamicCompleter completerFrom(String configString) {
-        Set<String> activationPostfixes = Set.of("");
-        return new HoconDynamicCompleter(activationPostfixes, 
ConfigFactory.parseString(configString));
+        return new 
HoconDynamicCompleter(ConfigFactory.parseString(configString));
     }
 
     @Test
diff --git 
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/NodeNameDynamicCompleterTest.java
 
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/StringDynamicCompleterTest.java
similarity index 53%
rename from 
modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/NodeNameDynamicCompleterTest.java
rename to 
modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/StringDynamicCompleterTest.java
index d6b5f1ab5f..7445377329 100644
--- 
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/NodeNameDynamicCompleterTest.java
+++ 
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/StringDynamicCompleterTest.java
@@ -17,51 +17,41 @@
 
 package org.apache.ignite.internal.cli.core.repl.completer;
 
-import static 
org.apache.ignite.internal.cli.commands.OptionsConstants.NODE_NAME_OPTION;
-import static 
org.apache.ignite.internal.cli.commands.OptionsConstants.NODE_NAME_OPTION_SHORT;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.containsInAnyOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
 
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
 import java.util.Set;
 import java.util.stream.Stream;
-import org.apache.ignite.internal.cli.NodeNameRegistry;
+import 
org.apache.ignite.internal.cli.core.repl.completer.node.StringDynamicCompleter;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
 
 
-/** Tests for {@link NodeNameDynamicCompleter}. */
-class NodeNameDynamicCompleterTest {
-
-    private static final Set<String> prefixes = Set.of(NODE_NAME_OPTION, 
NODE_NAME_OPTION_SHORT);
-    private static final List<String> nodeNames = List.of("node1", "node2", 
"remoteNode");
-    private NodeNameDynamicCompleter completer;
-
-    @BeforeEach
-    void setUp() {
-        NodeNameRegistry nodeNameRegistry = mock(NodeNameRegistry.class);
-        when(nodeNameRegistry.getAllNames()).thenReturn(new 
HashSet<>(nodeNames));
-        completer = new NodeNameDynamicCompleter(prefixes, nodeNameRegistry);
-    }
+/** Tests for {@link StringDynamicCompleter}. */
+class StringDynamicCompleterTest {
+    private static final Set<String> candidates = Set.of("node1", "node2", 
"remoteNode");
+    private StringDynamicCompleter completer;
 
     private static Stream<Arguments> words() {
         return Stream.of(
-                Arguments.of(new String[]{"-n"}, nodeNames),
-                Arguments.of(new String[]{"--node-name"}, nodeNames),
-                Arguments.of(new String[]{"-n", "node"}, List.of("node1", 
"node2")),
-                Arguments.of(new String[]{"node", "config", "show"}, 
Collections.emptyList())
+                Arguments.of(new String[]{"n"}, Set.of("node1", "node2")),
+                Arguments.of(new String[]{""}, candidates),
+                Arguments.of(new String[]{" "}, candidates),
+                Arguments.of(new String[]{"node1", ""}, candidates),
+                Arguments.of(new String[]{"-n", "node"}, Set.of("node1", 
"node2"))
         );
     }
 
+    @BeforeEach
+    void setUp() {
+        completer = new StringDynamicCompleter(candidates);
+    }
+
     @ParameterizedTest
     @MethodSource("words")
-    void complete(String[] words, List<String> expectedCompletions) {
+    void complete(String[] words, Set<String> expectedCompletions) {
         assertThat(completer.complete(words), 
containsInAnyOrder(expectedCompletions.toArray(new String[0])));
     }
 }

Reply via email to