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

dlmarion pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/accumulo.git


The following commit(s) were added to refs/heads/main by this push:
     new d773ae47e0 Added Dynamic Command Groups, ClientKeywordExecutable 
(#6121)
d773ae47e0 is described below

commit d773ae47e073979feb9edaacf976bdde0d3b0298
Author: Dave Marion <[email protected]>
AuthorDate: Thu Feb 12 14:03:58 2026 -0500

    Added Dynamic Command Groups, ClientKeywordExecutable (#6121)
    
    This change does the following:
    
    Reverts prior changes to UsageGroup enum
    Adds a CommandGroups class that uses a ServiceLoader to load CommandGroup 
definitions
    Added method to KeywordExecutable to retrieve the CommandGroup for each 
command
    Marked KeywordExecutable.usageGroup as deprecated, removed all uses of it
    Modified Main to use the new CommandGroup for organizing and executing 
commands
    Adds a ClientKeywordExecutable object
    Modifies Help, Version, and Shell to extend ClientKeywordExecutable
    Modifies more server utilities to extend ServerKeywordExecutable
---
 .../accumulo/core/cli/ClientKeywordExecutable.java |  36 ++--
 .../accumulo/core/file/rfile/GenerateSplits.java   |   7 +
 .../apache/accumulo/core/file/rfile/PrintInfo.java |   7 +
 .../accumulo/core/file/rfile/SplitLarge.java       |   7 +
 .../org/apache/accumulo/core/util/CreateToken.java |   7 +
 .../java/org/apache/accumulo/core/util/Help.java   |  18 +-
 .../org/apache/accumulo/core/util/Version.java     |  17 +-
 .../apache/accumulo/core/cli/TestClientOpts.java   |  11 ++
 .../miniclusterImpl/MiniClusterExecutable.java     |   6 +-
 .../server/conf/CheckAccumuloProperties.java       |   7 +
 .../server/conf/CheckCompactionConfig.java         |   7 +
 .../accumulo/server/conf/util/ZooInfoViewer.java   |   7 +
 .../accumulo/server/conf/util/ZooPropEditor.java   |   7 +
 .../apache/accumulo/server/init/Initialize.java    |   6 +-
 .../accumulo/server/util/CancelCompaction.java     |   8 +-
 .../apache/accumulo/server/util/DumpZookeeper.java |   7 +
 .../server/util/FindCompactionTmpFiles.java        |   8 +-
 .../java/org/apache/accumulo/server/util/Info.java |   6 +-
 .../accumulo/server/util/ListCompactions.java      |  38 ++---
 .../accumulo/server/util/ListCompactors.java       |  27 ++-
 .../accumulo/server/util/LoginProperties.java      |   7 +
 .../server/util/ServerKeywordExecutable.java       |  19 ++-
 .../apache/accumulo/server/util/UpgradeUtil.java   |   7 +
 .../apache/accumulo/server/util/ZooKeeperMain.java |   6 +-
 .../org/apache/accumulo/server/util/ZooZap.java    |   7 +
 .../server/util/adminCommand/ChangeSecret.java     |   6 +-
 .../util/adminCommand/DeleteZooInstance.java       |   6 +-
 .../server/util/adminCommand/DumpConfig.java       |   6 +-
 .../accumulo/server/util/adminCommand/Fate.java    |   6 +-
 .../server/util/adminCommand/ListInstances.java    |   6 +-
 .../server/util/adminCommand/ListVolumesUsed.java  |   6 +-
 .../accumulo/server/util/adminCommand/Locks.java   |   6 +-
 .../server/util/adminCommand/PingServer.java       |   6 +-
 .../server/util/adminCommand/RestoreZookeeper.java |   6 +-
 .../server/util/adminCommand/ServiceStatus.java    |   6 +-
 .../accumulo/server/util/adminCommand/StopAll.java |   6 +-
 .../server/util/adminCommand/StopManager.java      |   6 +-
 .../server/util/adminCommand/StopServers.java      |   6 +-
 .../server/util/adminCommand/SystemCheck.java      |   6 +-
 .../util/adminCommand/VerifyTabletAssignments.java |   6 +-
 .../accumulo/compactor/CompactorExecutable.java    |   6 +-
 .../java/org/apache/accumulo/gc/GCExecutable.java  |   6 +-
 .../apache/accumulo/manager/ManagerExecutable.java |   6 +-
 .../apache/accumulo/monitor/MonitorExecutable.java |   6 +-
 .../accumulo/tserver/ScanServerExecutable.java     |   6 +-
 .../apache/accumulo/tserver/TServerExecutable.java |   6 +-
 .../apache/accumulo/tserver/logger/LogReader.java  |   7 +
 .../apache/accumulo/tserver/util/CreateEmpty.java  |   7 +
 .../main/java/org/apache/accumulo/shell/Shell.java |  72 +++-----
 .../org/apache/accumulo/shell/ShellCompletor.java  |  18 +-
 .../org/apache/accumulo/shell/ShellOptionsJC.java  | 128 ++------------
 .../org/apache/accumulo/shell/ShellConfigTest.java |  11 +-
 .../apache/accumulo/shell/ShellOptionsJCTest.java  |  64 -------
 start/pom.xml                                      |   5 +
 .../main/java/org/apache/accumulo/start/Main.java  |  89 ++++++----
 .../apache/accumulo/start/spi/CommandGroup.java    |  34 ++--
 .../apache/accumulo/start/spi/CommandGroups.java   | 185 +++++++++++++++++++++
 .../accumulo/start/spi/KeywordExecutable.java      |   8 +-
 .../org/apache/accumulo/test/shell/MockShell.java  |  31 +++-
 .../shell/ShellAuthenticatorIT_SimpleSuite.java    |  40 +++--
 .../org/apache/accumulo/test/shell/ShellIT.java    |  30 ++--
 .../accumulo/test/start/CommandGroupsIT.java       |  46 +++++
 .../apache/accumulo/test/start/KeywordStartIT.java | 135 ++++++++-------
 63 files changed, 788 insertions(+), 538 deletions(-)

diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/ServerKeywordExecutable.java
 b/core/src/main/java/org/apache/accumulo/core/cli/ClientKeywordExecutable.java
similarity index 55%
copy from 
server/base/src/main/java/org/apache/accumulo/server/util/ServerKeywordExecutable.java
copy to 
core/src/main/java/org/apache/accumulo/core/cli/ClientKeywordExecutable.java
index 406afd2db2..c305832795 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/ServerKeywordExecutable.java
+++ 
b/core/src/main/java/org/apache/accumulo/core/cli/ClientKeywordExecutable.java
@@ -16,45 +16,41 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.accumulo.server.util;
+package org.apache.accumulo.core.cli;
 
-import org.apache.accumulo.core.conf.AccumuloConfiguration;
-import org.apache.accumulo.core.conf.Property;
-import org.apache.accumulo.server.ServerContext;
-import org.apache.accumulo.server.cli.ServerUtilOpts;
-import org.apache.accumulo.server.security.SecurityUtil;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 
 import com.beust.jcommander.JCommander;
+import com.beust.jcommander.ParameterException;
 
-public abstract class ServerKeywordExecutable<O extends ServerUtilOpts>
+public abstract class ClientKeywordExecutable<OPTS extends ClientOpts>
     implements KeywordExecutable {
 
-  private final O options;
+  private final OPTS options;
 
-  public ServerKeywordExecutable(O options) {
+  public ClientKeywordExecutable(OPTS options) {
     this.options = options;
   }
 
   @Override
   public final void execute(String[] args) throws Exception {
     JCommander cl = new JCommander(this.options);
-    cl.setProgramName("accumulo " + usageGroup().name().toLowerCase() + " " + 
keyword());
-    cl.parse(args);
+    cl.setProgramName("accumulo "
+        + (commandGroup().key().isBlank() ? "" : commandGroup().key() + " ") + 
keyword());
+    try {
+      cl.parse(args);
+    } catch (ParameterException e) {
+      cl.usage();
+      return;
+    }
 
     if (this.options.help) {
       cl.usage();
       return;
     }
-    // Login as the server on secure HDFS
-    try (ServerContext context = options.getServerContext()) {
-      AccumuloConfiguration conf = 
options.getServerContext().getConfiguration();
-      if (conf.getBoolean(Property.INSTANCE_RPC_SASL_ENABLED)) {
-        SecurityUtil.serverLogin(conf);
-      }
-      execute(cl, options);
-    }
+    execute(cl, options);
   }
 
-  public abstract void execute(JCommander cl, O options) throws Exception;
+  public abstract void execute(JCommander cl, OPTS options) throws Exception;
+
 }
diff --git 
a/core/src/main/java/org/apache/accumulo/core/file/rfile/GenerateSplits.java 
b/core/src/main/java/org/apache/accumulo/core/file/rfile/GenerateSplits.java
index b63b46f273..0707940104 100644
--- a/core/src/main/java/org/apache/accumulo/core/file/rfile/GenerateSplits.java
+++ b/core/src/main/java/org/apache/accumulo/core/file/rfile/GenerateSplits.java
@@ -51,6 +51,8 @@ import 
org.apache.accumulo.core.metadata.UnreferencedTabletFile;
 import org.apache.accumulo.core.spi.crypto.CryptoEnvironment;
 import org.apache.accumulo.core.spi.crypto.CryptoService;
 import org.apache.accumulo.core.util.TextUtil;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.apache.datasketches.quantiles.ItemsSketch;
 import org.apache.datasketches.quantilescommon.QuantileSearchCriteria;
@@ -109,6 +111,11 @@ public class GenerateSplits implements KeywordExecutable {
     return "Generate split points from a set of 1 or more rfiles";
   }
 
+  @Override
+  public CommandGroup commandGroup() {
+    return CommandGroups.OTHER;
+  }
+
   public static void main(String[] args) throws Exception {
     new GenerateSplits().execute(args);
   }
diff --git 
a/core/src/main/java/org/apache/accumulo/core/file/rfile/PrintInfo.java 
b/core/src/main/java/org/apache/accumulo/core/file/rfile/PrintInfo.java
index 1c3f5c90f4..303a7bf29e 100644
--- a/core/src/main/java/org/apache/accumulo/core/file/rfile/PrintInfo.java
+++ b/core/src/main/java/org/apache/accumulo/core/file/rfile/PrintInfo.java
@@ -43,6 +43,8 @@ import org.apache.accumulo.core.spi.crypto.NoFileEncrypter;
 import org.apache.accumulo.core.summary.SummaryReader;
 import org.apache.accumulo.core.util.LocalityGroupUtil;
 import org.apache.accumulo.core.util.NumUtil;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
@@ -150,6 +152,11 @@ public class PrintInfo implements KeywordExecutable {
     return "Prints rfile info";
   }
 
+  @Override
+  public CommandGroup commandGroup() {
+    return CommandGroups.OTHER;
+  }
+
   protected Class<? extends BiFunction<Key,Value,String>> getFormatter(String 
formatterClazz)
       throws ClassNotFoundException {
     @SuppressWarnings("unchecked")
diff --git 
a/core/src/main/java/org/apache/accumulo/core/file/rfile/SplitLarge.java 
b/core/src/main/java/org/apache/accumulo/core/file/rfile/SplitLarge.java
index 928b252b16..8c5bed6fa0 100644
--- a/core/src/main/java/org/apache/accumulo/core/file/rfile/SplitLarge.java
+++ b/core/src/main/java/org/apache/accumulo/core/file/rfile/SplitLarge.java
@@ -33,6 +33,8 @@ import org.apache.accumulo.core.file.rfile.RFile.Reader;
 import org.apache.accumulo.core.file.rfile.RFile.Writer;
 import org.apache.accumulo.core.file.rfile.bcfile.BCFile;
 import org.apache.accumulo.core.spi.crypto.CryptoService;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.FileSystem;
@@ -69,6 +71,11 @@ public class SplitLarge implements KeywordExecutable {
     return "Splits an RFile into large and small key/value files";
   }
 
+  @Override
+  public CommandGroup commandGroup() {
+    return CommandGroups.OTHER;
+  }
+
   @Override
   public void execute(String[] args) throws Exception {
     Configuration conf = new Configuration();
diff --git a/core/src/main/java/org/apache/accumulo/core/util/CreateToken.java 
b/core/src/main/java/org/apache/accumulo/core/util/CreateToken.java
index 80cfa9dec9..8caace446b 100644
--- a/core/src/main/java/org/apache/accumulo/core/util/CreateToken.java
+++ b/core/src/main/java/org/apache/accumulo/core/util/CreateToken.java
@@ -27,6 +27,8 @@ import 
org.apache.accumulo.core.client.security.tokens.AuthenticationToken.Prope
 import 
org.apache.accumulo.core.client.security.tokens.AuthenticationToken.TokenProperty;
 import org.apache.accumulo.core.client.security.tokens.PasswordToken;
 import org.apache.accumulo.core.conf.ClientProperty;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 
 import com.beust.jcommander.Parameter;
@@ -76,6 +78,11 @@ public class CreateToken implements KeywordExecutable {
     return "Creates authentication token";
   }
 
+  @Override
+  public CommandGroup commandGroup() {
+    return CommandGroups.OTHER;
+  }
+
   @Override
   public void execute(String[] args) {
     Opts opts = new Opts();
diff --git a/core/src/main/java/org/apache/accumulo/core/util/Help.java 
b/core/src/main/java/org/apache/accumulo/core/util/Help.java
index eabed9088d..844aea8def 100644
--- a/core/src/main/java/org/apache/accumulo/core/util/Help.java
+++ b/core/src/main/java/org/apache/accumulo/core/util/Help.java
@@ -18,21 +18,31 @@
  */
 package org.apache.accumulo.core.util;
 
+import org.apache.accumulo.core.cli.ClientKeywordExecutable;
+import org.apache.accumulo.core.cli.ClientOpts;
 import org.apache.accumulo.start.Main;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 
+import com.beust.jcommander.JCommander;
 import com.google.auto.service.AutoService;
 
 @AutoService(KeywordExecutable.class)
-public class Help implements KeywordExecutable {
+public class Help extends ClientKeywordExecutable<ClientOpts> {
+
+  public Help() {
+    super(new ClientOpts());
+  }
+
   @Override
   public String keyword() {
     return "help";
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.CORE;
+  public CommandGroup commandGroup() {
+    return CommandGroups.CLIENT;
   }
 
   @Override
@@ -41,7 +51,7 @@ public class Help implements KeywordExecutable {
   }
 
   @Override
-  public void execute(final String[] args) {
+  public void execute(JCommander cl, ClientOpts options) throws Exception {
     Main.printUsage();
   }
 }
diff --git a/core/src/main/java/org/apache/accumulo/core/util/Version.java 
b/core/src/main/java/org/apache/accumulo/core/util/Version.java
index ef5ef9303d..2e0c6b5cd5 100644
--- a/core/src/main/java/org/apache/accumulo/core/util/Version.java
+++ b/core/src/main/java/org/apache/accumulo/core/util/Version.java
@@ -18,13 +18,22 @@
  */
 package org.apache.accumulo.core.util;
 
+import org.apache.accumulo.core.cli.ClientKeywordExecutable;
+import org.apache.accumulo.core.cli.ClientOpts;
 import org.apache.accumulo.start.Main;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 
+import com.beust.jcommander.JCommander;
 import com.google.auto.service.AutoService;
 
 @AutoService(KeywordExecutable.class)
-public class Version implements KeywordExecutable {
+public class Version extends ClientKeywordExecutable<ClientOpts> {
+
+  public Version() {
+    super(new ClientOpts());
+  }
 
   @Override
   public String keyword() {
@@ -32,8 +41,8 @@ public class Version implements KeywordExecutable {
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.CORE;
+  public CommandGroup commandGroup() {
+    return CommandGroups.CLIENT;
   }
 
   @Override
@@ -42,7 +51,7 @@ public class Version implements KeywordExecutable {
   }
 
   @Override
-  public void execute(final String[] args) throws Exception {
+  public void execute(JCommander cl, ClientOpts options) throws Exception {
     Class<?> runTMP = 
Main.getClassLoader().loadClass("org.apache.accumulo.core.Constants");
     System.out.println(runTMP.getField("VERSION").get(null));
   }
diff --git 
a/core/src/test/java/org/apache/accumulo/core/cli/TestClientOpts.java 
b/core/src/test/java/org/apache/accumulo/core/cli/TestClientOpts.java
index 43e7e7289a..3ff4f2e921 100644
--- a/core/src/test/java/org/apache/accumulo/core/cli/TestClientOpts.java
+++ b/core/src/test/java/org/apache/accumulo/core/cli/TestClientOpts.java
@@ -19,6 +19,7 @@
 package org.apache.accumulo.core.cli;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.util.Properties;
@@ -64,4 +65,14 @@ public class TestClientOpts {
     assertTrue(opts.getToken().equals(new PasswordToken("mypass")));
     assertEquals("myinst", props.getProperty("instance.name"));
   }
+
+  @Test
+  public void testSasl() throws Exception {
+    ClientOpts opts = new ClientOpts();
+    String[] args =
+        new String[] {"--password", "mypass", "-u", "userabc", "-o", 
"instance.name=myinst", "-o",
+            "instance.zookeepers=zoo1,zoo2", "-o", "auth.principal=user123", 
"--sasl"};
+    assertThrows(IllegalArgumentException.class, () -> opts.parseArgs("test", 
args));
+  }
+
 }
diff --git 
a/minicluster/src/main/java/org/apache/accumulo/miniclusterImpl/MiniClusterExecutable.java
 
b/minicluster/src/main/java/org/apache/accumulo/miniclusterImpl/MiniClusterExecutable.java
index 9f19f4ec84..644290678b 100644
--- 
a/minicluster/src/main/java/org/apache/accumulo/miniclusterImpl/MiniClusterExecutable.java
+++ 
b/minicluster/src/main/java/org/apache/accumulo/miniclusterImpl/MiniClusterExecutable.java
@@ -19,6 +19,8 @@
 package org.apache.accumulo.miniclusterImpl;
 
 import org.apache.accumulo.minicluster.MiniAccumuloRunner;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 
 import com.google.auto.service.AutoService;
@@ -32,8 +34,8 @@ public class MiniClusterExecutable implements 
KeywordExecutable {
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.PROCESS;
+  public CommandGroup commandGroup() {
+    return CommandGroups.PROCESS;
   }
 
   @Override
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/conf/CheckAccumuloProperties.java
 
b/server/base/src/main/java/org/apache/accumulo/server/conf/CheckAccumuloProperties.java
index ed8fdb06b5..c77b483539 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/conf/CheckAccumuloProperties.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/conf/CheckAccumuloProperties.java
@@ -25,6 +25,8 @@ import org.apache.accumulo.core.conf.SiteConfiguration;
 import org.apache.accumulo.server.ServerDirs;
 import org.apache.accumulo.server.fs.VolumeManagerImpl;
 import org.apache.accumulo.server.util.adminCommand.SystemCheck.Check;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.apache.hadoop.conf.Configuration;
 
@@ -49,6 +51,11 @@ public class CheckAccumuloProperties implements 
KeywordExecutable {
         + "'admin check run " + Check.SERVER_CONFIG + "'";
   }
 
+  @Override
+  public CommandGroup commandGroup() {
+    return CommandGroups.OTHER;
+  }
+
   @SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = 
"intentional user-provided path")
   @Override
   public void execute(String[] args) throws IOException {
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/conf/CheckCompactionConfig.java
 
b/server/base/src/main/java/org/apache/accumulo/server/conf/CheckCompactionConfig.java
index abdd2d3c87..07c7c8fc6d 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/conf/CheckCompactionConfig.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/conf/CheckCompactionConfig.java
@@ -40,6 +40,8 @@ import 
org.apache.accumulo.core.spi.compaction.CompactionServiceId;
 import org.apache.accumulo.core.util.ConfigurationImpl;
 import org.apache.accumulo.core.util.compaction.CompactionPlannerInitParams;
 import org.apache.accumulo.core.util.compaction.CompactionServicesConfig;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -78,6 +80,11 @@ public class CheckCompactionConfig implements 
KeywordExecutable {
     return "Verifies compaction config within a given file";
   }
 
+  @Override
+  public CommandGroup commandGroup() {
+    return CommandGroups.OTHER;
+  }
+
   public static void main(String[] args) throws Exception {
     new CheckCompactionConfig().execute(args);
   }
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/conf/util/ZooInfoViewer.java
 
b/server/base/src/main/java/org/apache/accumulo/server/conf/util/ZooInfoViewer.java
index a255749c17..bdc6cd23fd 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/conf/util/ZooInfoViewer.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/conf/util/ZooInfoViewer.java
@@ -68,6 +68,8 @@ import 
org.apache.accumulo.server.conf.store.impl.PropStoreWatcher;
 import org.apache.accumulo.server.conf.store.impl.ReadyMonitor;
 import org.apache.accumulo.server.conf.store.impl.ZooPropStore;
 import org.apache.accumulo.server.zookeeper.ZooAclUtil;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.apache.zookeeper.KeeperException;
 import org.apache.zookeeper.data.ACL;
@@ -107,6 +109,11 @@ public class ZooInfoViewer implements KeywordExecutable {
     return "view Accumulo instance and property information stored in 
ZooKeeper";
   }
 
+  @Override
+  public CommandGroup commandGroup() {
+    return CommandGroups.OTHER;
+  }
+
   @Override
   public void execute(String[] args) throws Exception {
     nullWatcher = new NullWatcher(new 
ReadyMonitor(ZooInfoViewer.class.getSimpleName(), 20_000L));
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/conf/util/ZooPropEditor.java
 
b/server/base/src/main/java/org/apache/accumulo/server/conf/util/ZooPropEditor.java
index d6f9743c0d..7cd094c285 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/conf/util/ZooPropEditor.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/conf/util/ZooPropEditor.java
@@ -48,6 +48,8 @@ import 
org.apache.accumulo.server.conf.store.impl.PropStoreWatcher;
 import org.apache.accumulo.server.conf.store.impl.ReadyMonitor;
 import org.apache.accumulo.server.conf.store.impl.ZooPropStore;
 import org.apache.accumulo.server.util.PropUtil;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.apache.zookeeper.KeeperException;
 import org.slf4j.Logger;
@@ -78,6 +80,11 @@ public class ZooPropEditor implements KeywordExecutable {
         + " Prefer using the shell if it is available";
   }
 
+  @Override
+  public CommandGroup commandGroup() {
+    return CommandGroups.OTHER;
+  }
+
   @Override
   public void execute(String[] args) throws Exception {
     nullWatcher = new NullWatcher(new 
ReadyMonitor(ZooInfoViewer.class.getSimpleName(), 20_000L));
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/init/Initialize.java 
b/server/base/src/main/java/org/apache/accumulo/server/init/Initialize.java
index 84a9d8826d..b12d8db33c 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/init/Initialize.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/init/Initialize.java
@@ -56,6 +56,8 @@ import org.apache.accumulo.server.fs.VolumeManager;
 import org.apache.accumulo.server.fs.VolumeManagerImpl;
 import org.apache.accumulo.server.security.SecurityUtil;
 import org.apache.accumulo.server.util.SystemPropUtil;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.hadoop.conf.Configuration;
@@ -542,8 +544,8 @@ public class Initialize implements KeywordExecutable {
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.CORE;
+  public CommandGroup commandGroup() {
+    return CommandGroups.CORE;
   }
 
   @Override
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/CancelCompaction.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/CancelCompaction.java
index 594e871de9..98f0b0e47a 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/CancelCompaction.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/CancelCompaction.java
@@ -25,6 +25,8 @@ import org.apache.accumulo.core.trace.TraceUtil;
 import org.apache.accumulo.core.util.compaction.ExternalCompactionUtil;
 import org.apache.accumulo.server.ServerContext;
 import org.apache.accumulo.server.cli.ServerUtilOpts;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 
 import com.beust.jcommander.JCommander;
@@ -53,8 +55,8 @@ public class CancelCompaction implements KeywordExecutable {
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.COMPACTION;
+  public CommandGroup commandGroup() {
+    return CommandGroups.COMPACTION;
   }
 
   protected void cancelCompaction(ServerContext context, String ecid) {
@@ -76,7 +78,7 @@ public class CancelCompaction implements KeywordExecutable {
     ServerUtilOpts opts = new ServerUtilOpts();
 
     JCommander cl = new JCommander(opts);
-    cl.setProgramName("accumulo " + usageGroup().name().toLowerCase() + " " + 
keyword());
+    cl.setProgramName("accumulo " + commandGroup().key() + " " + keyword());
 
     CancelCommand cancelOps = new CancelCommand();
     cl.addCommand(cancelOps);
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/DumpZookeeper.java 
b/server/base/src/main/java/org/apache/accumulo/server/util/DumpZookeeper.java
index bf9e80574e..41e9b41394 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/DumpZookeeper.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/DumpZookeeper.java
@@ -26,6 +26,8 @@ import java.util.Base64;
 import org.apache.accumulo.core.cli.ConfigOpts;
 import org.apache.accumulo.core.fate.zookeeper.ZooReaderWriter;
 import org.apache.accumulo.core.zookeeper.ZooSession;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.apache.zookeeper.KeeperException;
 import org.apache.zookeeper.data.Stat;
@@ -48,6 +50,11 @@ public class DumpZookeeper implements KeywordExecutable {
     return "Writes Zookeeper data as human readable or XML to a file.";
   }
 
+  @Override
+  public CommandGroup commandGroup() {
+    return CommandGroups.OTHER;
+  }
+
   private static class Encoded {
     public String encoding;
     public String value;
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/FindCompactionTmpFiles.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/FindCompactionTmpFiles.java
index 97174a33b0..7865971915 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/FindCompactionTmpFiles.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/FindCompactionTmpFiles.java
@@ -40,6 +40,8 @@ import org.apache.accumulo.core.util.UtilWaitThread;
 import org.apache.accumulo.core.volume.Volume;
 import org.apache.accumulo.server.ServerContext;
 import org.apache.accumulo.server.cli.ServerUtilOpts;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.apache.hadoop.fs.FileStatus;
 import org.apache.hadoop.fs.Path;
@@ -67,15 +69,15 @@ public class FindCompactionTmpFiles implements 
KeywordExecutable {
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.COMPACTION;
+  public CommandGroup commandGroup() {
+    return CommandGroups.COMPACTION;
   }
 
   @Override
   public void execute(String[] args) throws Exception {
     ServerUtilOpts opts = new ServerUtilOpts();
     JCommander cl = new JCommander(opts);
-    cl.setProgramName("accumulo " + usageGroup().name().toLowerCase() + " " + 
keyword());
+    cl.setProgramName("accumulo " + commandGroup().key() + " " + keyword());
 
     FindCompactionTmpFilesCommand findOps = new 
FindCompactionTmpFilesCommand();
     cl.addCommand(findOps);
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/Info.java 
b/server/base/src/main/java/org/apache/accumulo/server/util/Info.java
index e1f3129106..35b05b50de 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/util/Info.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/util/Info.java
@@ -24,6 +24,8 @@ import org.apache.accumulo.core.client.admin.servers.ServerId;
 import org.apache.accumulo.core.conf.SiteConfiguration;
 import org.apache.accumulo.core.util.MonitorUtil;
 import org.apache.accumulo.server.ServerContext;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.apache.zookeeper.KeeperException;
 
@@ -38,8 +40,8 @@ public class Info implements KeywordExecutable {
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.CORE;
+  public CommandGroup commandGroup() {
+    return CommandGroups.CORE;
   }
 
   @Override
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/ListCompactions.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/ListCompactions.java
index 9cb414a80e..c90e5987d5 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/ListCompactions.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/ListCompactions.java
@@ -36,6 +36,9 @@ import 
org.apache.accumulo.core.util.compaction.RunningCompaction;
 import org.apache.accumulo.core.util.compaction.RunningCompactionInfo;
 import org.apache.accumulo.server.ServerContext;
 import org.apache.accumulo.server.cli.ServerUtilOpts;
+import org.apache.accumulo.server.util.ListCompactions.RunningCommandOpts;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 
 import com.beust.jcommander.JCommander;
@@ -46,7 +49,7 @@ import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 
 @AutoService(KeywordExecutable.class)
-public class ListCompactions implements KeywordExecutable {
+public class ListCompactions extends 
ServerKeywordExecutable<RunningCommandOpts> {
 
   public static class RunningCompactionSummary {
     private final String ecid;
@@ -153,7 +156,7 @@ public class ListCompactions implements KeywordExecutable {
   }
 
   @Parameters(commandNames = "running", commandDescription = "list the running 
compactions")
-  static class RunningCommand {
+  static class RunningCommandOpts extends ServerUtilOpts {
     @Parameter(names = {"-d", "--details"},
         description = "display details about the running compactions")
     boolean details = false;
@@ -162,6 +165,10 @@ public class ListCompactions implements KeywordExecutable {
     boolean jsonOutput = false;
   }
 
+  public ListCompactions() {
+    super(new RunningCommandOpts());
+  }
+
   @Override
   public String keyword() {
     return "list";
@@ -173,8 +180,8 @@ public class ListCompactions implements KeywordExecutable {
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.COMPACTION;
+  public CommandGroup commandGroup() {
+    return CommandGroups.COMPACTION;
   }
 
   protected List<RunningCompactionSummary> getRunningCompactions(ServerContext 
context,
@@ -213,25 +220,10 @@ public class ListCompactions implements KeywordExecutable 
{
   }
 
   @Override
-  public void execute(String[] args) throws Exception {
-    ServerUtilOpts opts = new ServerUtilOpts();
-    JCommander cl = new JCommander(opts);
-    cl.setProgramName("accumulo " + usageGroup().name().toLowerCase() + " " + 
keyword());
-
-    RunningCommand runningOpts = new RunningCommand();
-    cl.addCommand(runningOpts);
-
-    cl.parse(args);
-
-    if (opts.help || cl.getParsedCommand() == null) {
-      cl.usage();
-      return;
-    }
-
-    ServerContext context = opts.getServerContext();
-    List<RunningCompactionSummary> compactions =
-        getRunningCompactions(context, runningOpts.details);
-    if (runningOpts.jsonOutput) {
+  public void execute(JCommander cl, RunningCommandOpts options) throws 
Exception {
+    ServerContext context = options.getServerContext();
+    List<RunningCompactionSummary> compactions = 
getRunningCompactions(context, options.details);
+    if (options.jsonOutput) {
       try {
         Gson gson = new GsonBuilder().setPrettyPrinting().create();
         System.out.println(gson.toJson(compactions));
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/ListCompactors.java 
b/server/base/src/main/java/org/apache/accumulo/server/util/ListCompactors.java
index ca8cb43cdb..57223b58dc 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/ListCompactors.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/ListCompactors.java
@@ -28,13 +28,19 @@ import 
org.apache.accumulo.core.client.admin.servers.ServerId;
 import org.apache.accumulo.core.data.ResourceGroupId;
 import org.apache.accumulo.server.ServerContext;
 import org.apache.accumulo.server.cli.ServerUtilOpts;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 
 import com.beust.jcommander.JCommander;
 import com.google.auto.service.AutoService;
 
 @AutoService(KeywordExecutable.class)
-public class ListCompactors implements KeywordExecutable {
+public class ListCompactors extends ServerKeywordExecutable<ServerUtilOpts> {
+
+  public ListCompactors() {
+    super(new ServerUtilOpts());
+  }
 
   @Override
   public String keyword() {
@@ -47,8 +53,8 @@ public class ListCompactors implements KeywordExecutable {
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.PROCESS;
+  public CommandGroup commandGroup() {
+    return CommandGroups.PROCESS;
   }
 
   protected Map<ResourceGroupId,List<ServerId>> 
listCompactorsByQueue(ServerContext context) {
@@ -65,18 +71,8 @@ public class ListCompactors implements KeywordExecutable {
   }
 
   @Override
-  public void execute(String[] args) throws Exception {
-    ServerUtilOpts opts = new ServerUtilOpts();
-    JCommander cl = new JCommander(opts);
-    cl.setProgramName("accumulo " + usageGroup().name().toLowerCase() + " " + 
keyword());
-    cl.parse(args);
-
-    if (opts.help || cl.getParsedCommand() == null) {
-      cl.usage();
-      return;
-    }
-
-    var map = listCompactorsByQueue(opts.getServerContext());
+  public void execute(JCommander cl, ServerUtilOpts options) throws Exception {
+    var map = listCompactorsByQueue(options.getServerContext());
     if (map.isEmpty()) {
       System.out.println("No Compactors found.");
     } else {
@@ -87,4 +83,5 @@ public class ListCompactors implements KeywordExecutable {
       });
     }
   }
+
 }
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/LoginProperties.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/LoginProperties.java
index ee0e180cc4..5d1e6e5438 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/LoginProperties.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/LoginProperties.java
@@ -26,6 +26,8 @@ import org.apache.accumulo.core.conf.Property;
 import org.apache.accumulo.core.conf.SiteConfiguration;
 import org.apache.accumulo.server.ServerContext;
 import org.apache.accumulo.server.security.handler.Authenticator;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 
 import com.google.auto.service.AutoService;
@@ -43,6 +45,11 @@ public class LoginProperties implements KeywordExecutable {
     return "Prints Accumulo login info";
   }
 
+  @Override
+  public CommandGroup commandGroup() {
+    return CommandGroups.OTHER;
+  }
+
   @Override
   public void execute(String[] args) throws Exception {
     try (var context = new ServerContext(SiteConfiguration.auto())) {
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/ServerKeywordExecutable.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/ServerKeywordExecutable.java
index 406afd2db2..e452c38490 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/ServerKeywordExecutable.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/ServerKeywordExecutable.java
@@ -26,21 +26,28 @@ import org.apache.accumulo.server.security.SecurityUtil;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 
 import com.beust.jcommander.JCommander;
+import com.beust.jcommander.ParameterException;
 
-public abstract class ServerKeywordExecutable<O extends ServerUtilOpts>
+public abstract class ServerKeywordExecutable<OPTS extends ServerUtilOpts>
     implements KeywordExecutable {
 
-  private final O options;
+  private final OPTS options;
 
-  public ServerKeywordExecutable(O options) {
+  public ServerKeywordExecutable(OPTS options) {
     this.options = options;
   }
 
   @Override
   public final void execute(String[] args) throws Exception {
     JCommander cl = new JCommander(this.options);
-    cl.setProgramName("accumulo " + usageGroup().name().toLowerCase() + " " + 
keyword());
-    cl.parse(args);
+    cl.setProgramName("accumulo " + commandGroup().key() + " " + keyword());
+    try {
+      cl.parse(args);
+    } catch (ParameterException e) {
+      System.out.println("Error parsing arguments");
+      cl.usage();
+      return;
+    }
 
     if (this.options.help) {
       cl.usage();
@@ -56,5 +63,5 @@ public abstract class ServerKeywordExecutable<O extends 
ServerUtilOpts>
     }
   }
 
-  public abstract void execute(JCommander cl, O options) throws Exception;
+  public abstract void execute(JCommander cl, OPTS options) throws Exception;
 }
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/UpgradeUtil.java 
b/server/base/src/main/java/org/apache/accumulo/server/util/UpgradeUtil.java
index 51aab14009..42021366af 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/util/UpgradeUtil.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/util/UpgradeUtil.java
@@ -55,6 +55,8 @@ import org.apache.accumulo.server.security.SecurityUtil;
 import org.apache.accumulo.server.util.upgrade.PreUpgradeValidation;
 import org.apache.accumulo.server.util.upgrade.UpgradeProgress;
 import org.apache.accumulo.server.util.upgrade.UpgradeProgressTracker;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.apache.zookeeper.KeeperException;
 import org.apache.zookeeper.data.Stat;
@@ -103,6 +105,11 @@ public class UpgradeUtil implements KeywordExecutable {
     return "utility used to perform various upgrade steps for an Accumulo 
instance.";
   }
 
+  @Override
+  public CommandGroup commandGroup() {
+    return CommandGroups.OTHER;
+  }
+
   private static class UpgradeUsageFormatter extends DefaultUsageFormatter {
 
     public UpgradeUsageFormatter(JCommander commander) {
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/ZooKeeperMain.java 
b/server/base/src/main/java/org/apache/accumulo/server/util/ZooKeeperMain.java
index 9165343992..827b98b648 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/ZooKeeperMain.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/ZooKeeperMain.java
@@ -22,6 +22,8 @@ import org.apache.accumulo.core.cli.Help;
 import org.apache.accumulo.core.conf.SiteConfiguration;
 import org.apache.accumulo.core.fate.zookeeper.ZooUtil;
 import org.apache.accumulo.server.ServerContext;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 
 import com.beust.jcommander.Parameter;
@@ -51,8 +53,8 @@ public class ZooKeeperMain implements KeywordExecutable {
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.PROCESS;
+  public CommandGroup commandGroup() {
+    return CommandGroups.PROCESS;
   }
 
   @Override
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/ZooZap.java 
b/server/base/src/main/java/org/apache/accumulo/server/util/ZooZap.java
index d20d517599..0ac4d763f0 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/util/ZooZap.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/util/ZooZap.java
@@ -39,6 +39,8 @@ import 
org.apache.accumulo.core.lock.ServiceLockPaths.ResourceGroupPredicate;
 import org.apache.accumulo.core.lock.ServiceLockPaths.ServiceLockPath;
 import org.apache.accumulo.server.ServerContext;
 import org.apache.accumulo.server.security.SecurityUtil;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.apache.zookeeper.KeeperException;
 import org.slf4j.Logger;
@@ -69,6 +71,11 @@ public class ZooZap implements KeywordExecutable {
     return "Utility for zapping Zookeeper locks";
   }
 
+  @Override
+  public CommandGroup commandGroup() {
+    return CommandGroups.OTHER;
+  }
+
   static class Opts extends Help {
     @Parameter(names = "-manager", description = "remove manager locks")
     boolean zapManager = false;
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/ChangeSecret.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/ChangeSecret.java
index c8918c4a70..2196a29e78 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/ChangeSecret.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/ChangeSecret.java
@@ -40,6 +40,8 @@ import org.apache.accumulo.server.ServerDirs;
 import org.apache.accumulo.server.cli.ServerUtilOpts;
 import org.apache.accumulo.server.fs.VolumeManager;
 import org.apache.accumulo.server.util.ServerKeywordExecutable;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.hadoop.conf.Configuration;
@@ -71,8 +73,8 @@ public class ChangeSecret extends 
ServerKeywordExecutable<ServerUtilOpts> {
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.ADMIN;
+  public CommandGroup commandGroup() {
+    return CommandGroups.ADMIN;
   }
 
   @Override
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/DeleteZooInstance.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/DeleteZooInstance.java
index ed87f3d76b..72aa7487de 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/DeleteZooInstance.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/DeleteZooInstance.java
@@ -32,6 +32,8 @@ import org.apache.accumulo.server.ServerContext;
 import org.apache.accumulo.server.cli.ServerUtilOpts;
 import org.apache.accumulo.server.util.ServerKeywordExecutable;
 import 
org.apache.accumulo.server.util.adminCommand.DeleteZooInstance.DeleteZooInstanceOpts;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.apache.zookeeper.KeeperException;
 import org.slf4j.Logger;
@@ -70,8 +72,8 @@ public class DeleteZooInstance extends 
ServerKeywordExecutable<DeleteZooInstance
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.ADMIN;
+  public CommandGroup commandGroup() {
+    return CommandGroups.ADMIN;
   }
 
   @Override
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/DumpConfig.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/DumpConfig.java
index 45d2f68db5..b189d5958d 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/DumpConfig.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/DumpConfig.java
@@ -49,6 +49,8 @@ import org.apache.accumulo.server.ServerContext;
 import org.apache.accumulo.server.cli.ServerUtilOpts;
 import org.apache.accumulo.server.util.ServerKeywordExecutable;
 import org.apache.accumulo.server.util.adminCommand.DumpConfig.DumpConfigOpts;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 
 import com.beust.jcommander.JCommander;
@@ -124,8 +126,8 @@ public class DumpConfig extends 
ServerKeywordExecutable<DumpConfigOpts> {
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.ADMIN;
+  public CommandGroup commandGroup() {
+    return CommandGroups.ADMIN;
   }
 
   @Override
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/Fate.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/Fate.java
index b8f0c7bbd4..d7ac92d813 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/Fate.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/Fate.java
@@ -69,6 +69,8 @@ import org.apache.accumulo.server.cli.ServerUtilOpts;
 import org.apache.accumulo.server.util.ServerKeywordExecutable;
 import org.apache.accumulo.server.util.adminCommand.Fate.FateOpts;
 import org.apache.accumulo.server.util.fateCommand.FateSummaryReport;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.apache.zookeeper.KeeperException;
 import org.slf4j.Logger;
@@ -190,8 +192,8 @@ public class Fate extends ServerKeywordExecutable<FateOpts> 
{
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.ADMIN;
+  public CommandGroup commandGroup() {
+    return CommandGroups.ADMIN;
   }
 
   @Override
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/ListInstances.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/ListInstances.java
index d695dc445e..adc1d80b90 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/ListInstances.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/ListInstances.java
@@ -40,6 +40,8 @@ import org.apache.accumulo.core.zookeeper.ZooSession;
 import org.apache.accumulo.server.cli.ServerUtilOpts;
 import org.apache.accumulo.server.util.ServerKeywordExecutable;
 import org.apache.accumulo.server.util.adminCommand.ListInstances.Opts;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -81,8 +83,8 @@ public class ListInstances extends 
ServerKeywordExecutable<Opts> {
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.ADMIN;
+  public CommandGroup commandGroup() {
+    return CommandGroups.ADMIN;
   }
 
   @Override
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/ListVolumesUsed.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/ListVolumesUsed.java
index ad65253aef..47f89c0ca5 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/ListVolumesUsed.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/ListVolumesUsed.java
@@ -31,6 +31,8 @@ import org.apache.accumulo.server.cli.ServerUtilOpts;
 import org.apache.accumulo.server.fs.VolumeManager.FileType;
 import org.apache.accumulo.server.log.WalStateManager;
 import org.apache.accumulo.server.util.ServerKeywordExecutable;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.apache.hadoop.fs.Path;
 
@@ -50,8 +52,8 @@ public class ListVolumesUsed extends 
ServerKeywordExecutable<ServerUtilOpts> {
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.ADMIN;
+  public CommandGroup commandGroup() {
+    return CommandGroups.ADMIN;
   }
 
   @Override
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/Locks.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/Locks.java
index fa93670cdf..4ff6f6c7c5 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/Locks.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/Locks.java
@@ -20,6 +20,8 @@ package org.apache.accumulo.server.util.adminCommand;
 
 import org.apache.accumulo.server.cli.ServerUtilOpts;
 import org.apache.accumulo.server.util.ServerKeywordExecutable;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 
 import com.beust.jcommander.JCommander;
@@ -38,8 +40,8 @@ public class Locks extends 
ServerKeywordExecutable<ServerUtilOpts> {
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.ADMIN;
+  public CommandGroup commandGroup() {
+    return CommandGroups.ADMIN;
   }
 
   @Override
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/PingServer.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/PingServer.java
index 1422e0e9a5..f64219bc62 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/PingServer.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/PingServer.java
@@ -28,6 +28,8 @@ import org.apache.accumulo.server.ServerContext;
 import org.apache.accumulo.server.cli.ServerUtilOpts;
 import org.apache.accumulo.server.util.ServerKeywordExecutable;
 import org.apache.accumulo.server.util.adminCommand.PingServer.PingCommandOpts;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 
 import com.beust.jcommander.JCommander;
@@ -52,8 +54,8 @@ public class PingServer extends 
ServerKeywordExecutable<PingCommandOpts> {
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.ADMIN;
+  public CommandGroup commandGroup() {
+    return CommandGroups.ADMIN;
   }
 
   @Override
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/RestoreZookeeper.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/RestoreZookeeper.java
index c7d8b325c2..59cd4cba7f 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/RestoreZookeeper.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/RestoreZookeeper.java
@@ -36,6 +36,8 @@ import org.apache.accumulo.server.ServerContext;
 import org.apache.accumulo.server.cli.ServerUtilOpts;
 import org.apache.accumulo.server.util.ServerKeywordExecutable;
 import 
org.apache.accumulo.server.util.adminCommand.RestoreZookeeper.RestoreZooCommandOpts;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.apache.zookeeper.KeeperException;
 import org.xml.sax.Attributes;
@@ -131,8 +133,8 @@ public class RestoreZookeeper extends 
ServerKeywordExecutable<RestoreZooCommandO
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.ADMIN;
+  public CommandGroup commandGroup() {
+    return CommandGroups.ADMIN;
   }
 
   @Override
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/ServiceStatus.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/ServiceStatus.java
index ac01847eab..d584fb2836 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/ServiceStatus.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/ServiceStatus.java
@@ -40,6 +40,8 @@ import 
org.apache.accumulo.server.util.ServerKeywordExecutable;
 import 
org.apache.accumulo.server.util.adminCommand.ServiceStatus.ServiceStatusCmdOpts;
 import org.apache.accumulo.server.util.serviceStatus.ServiceStatusReport;
 import org.apache.accumulo.server.util.serviceStatus.StatusSummary;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.apache.zookeeper.KeeperException;
 import org.slf4j.Logger;
@@ -75,8 +77,8 @@ public class ServiceStatus extends 
ServerKeywordExecutable<ServiceStatusCmdOpts>
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.ADMIN;
+  public CommandGroup commandGroup() {
+    return CommandGroups.ADMIN;
   }
 
   @Override
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/StopAll.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/StopAll.java
index d2eeb27b7e..6a87078ad6 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/StopAll.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/StopAll.java
@@ -29,6 +29,8 @@ import org.apache.accumulo.core.trace.TraceUtil;
 import org.apache.accumulo.server.ServerContext;
 import org.apache.accumulo.server.cli.ServerUtilOpts;
 import org.apache.accumulo.server.util.ServerKeywordExecutable;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -57,8 +59,8 @@ public class StopAll extends 
ServerKeywordExecutable<ServerUtilOpts> {
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.ADMIN;
+  public CommandGroup commandGroup() {
+    return CommandGroups.ADMIN;
   }
 
   @Override
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/StopManager.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/StopManager.java
index 19e05d5aea..38eb04979f 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/StopManager.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/StopManager.java
@@ -23,6 +23,8 @@ import org.apache.accumulo.core.trace.TraceUtil;
 import org.apache.accumulo.server.ServerContext;
 import org.apache.accumulo.server.cli.ServerUtilOpts;
 import org.apache.accumulo.server.util.ServerKeywordExecutable;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 
 import com.beust.jcommander.JCommander;
@@ -41,8 +43,8 @@ public class StopManager extends 
ServerKeywordExecutable<ServerUtilOpts> {
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.ADMIN;
+  public CommandGroup commandGroup() {
+    return CommandGroups.ADMIN;
   }
 
   @Override
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/StopServers.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/StopServers.java
index fee585092e..7b7033e5b5 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/StopServers.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/StopServers.java
@@ -45,6 +45,8 @@ import org.apache.accumulo.server.cli.ServerUtilOpts;
 import org.apache.accumulo.server.util.ServerKeywordExecutable;
 import org.apache.accumulo.server.util.ZooZap;
 import 
org.apache.accumulo.server.util.adminCommand.StopServers.StopServersOpts;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.apache.thrift.TException;
 import org.slf4j.Logger;
@@ -85,8 +87,8 @@ public class StopServers extends 
ServerKeywordExecutable<StopServersOpts> {
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.ADMIN;
+  public CommandGroup commandGroup() {
+    return CommandGroups.ADMIN;
   }
 
   @Override
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/SystemCheck.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/SystemCheck.java
index 88c15b989d..9bc21c6580 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/SystemCheck.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/SystemCheck.java
@@ -42,6 +42,8 @@ import 
org.apache.accumulo.server.util.checkCommand.SystemConfigCheckRunner;
 import org.apache.accumulo.server.util.checkCommand.SystemFilesCheckRunner;
 import org.apache.accumulo.server.util.checkCommand.TableLocksCheckRunner;
 import org.apache.accumulo.server.util.checkCommand.UserFilesCheckRunner;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 
 import com.beust.jcommander.JCommander;
@@ -167,8 +169,8 @@ public class SystemCheck extends 
ServerKeywordExecutable<CheckCommandOpts> {
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.ADMIN;
+  public CommandGroup commandGroup() {
+    return CommandGroups.ADMIN;
   }
 
   @Override
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/VerifyTabletAssignments.java
 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/VerifyTabletAssignments.java
index cacd24658d..683003698f 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/VerifyTabletAssignments.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/adminCommand/VerifyTabletAssignments.java
@@ -53,6 +53,8 @@ import org.apache.accumulo.server.ServerContext;
 import org.apache.accumulo.server.cli.ServerUtilOpts;
 import org.apache.accumulo.server.util.ServerKeywordExecutable;
 import 
org.apache.accumulo.server.util.adminCommand.VerifyTabletAssignments.VerifyTabletAssignmentsOpts;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.apache.hadoop.io.Text;
 import org.apache.thrift.TException;
@@ -89,8 +91,8 @@ public class VerifyTabletAssignments extends 
ServerKeywordExecutable<VerifyTable
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.ADMIN;
+  public CommandGroup commandGroup() {
+    return CommandGroups.ADMIN;
   }
 
   @Override
diff --git 
a/server/compactor/src/main/java/org/apache/accumulo/compactor/CompactorExecutable.java
 
b/server/compactor/src/main/java/org/apache/accumulo/compactor/CompactorExecutable.java
index d7c6b400fa..90623fcd88 100644
--- 
a/server/compactor/src/main/java/org/apache/accumulo/compactor/CompactorExecutable.java
+++ 
b/server/compactor/src/main/java/org/apache/accumulo/compactor/CompactorExecutable.java
@@ -18,6 +18,8 @@
  */
 package org.apache.accumulo.compactor;
 
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 
 import com.google.auto.service.AutoService;
@@ -31,8 +33,8 @@ public class CompactorExecutable implements KeywordExecutable 
{
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.PROCESS;
+  public CommandGroup commandGroup() {
+    return CommandGroups.PROCESS;
   }
 
   @Override
diff --git a/server/gc/src/main/java/org/apache/accumulo/gc/GCExecutable.java 
b/server/gc/src/main/java/org/apache/accumulo/gc/GCExecutable.java
index 44ed078f7c..af11cbdde3 100644
--- a/server/gc/src/main/java/org/apache/accumulo/gc/GCExecutable.java
+++ b/server/gc/src/main/java/org/apache/accumulo/gc/GCExecutable.java
@@ -18,6 +18,8 @@
  */
 package org.apache.accumulo.gc;
 
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 
 import com.google.auto.service.AutoService;
@@ -30,8 +32,8 @@ public class GCExecutable implements KeywordExecutable {
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.PROCESS;
+  public CommandGroup commandGroup() {
+    return CommandGroups.PROCESS;
   }
 
   @Override
diff --git 
a/server/manager/src/main/java/org/apache/accumulo/manager/ManagerExecutable.java
 
b/server/manager/src/main/java/org/apache/accumulo/manager/ManagerExecutable.java
index 71ea5afcc1..73c1cff906 100644
--- 
a/server/manager/src/main/java/org/apache/accumulo/manager/ManagerExecutable.java
+++ 
b/server/manager/src/main/java/org/apache/accumulo/manager/ManagerExecutable.java
@@ -18,6 +18,8 @@
  */
 package org.apache.accumulo.manager;
 
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 
 import com.google.auto.service.AutoService;
@@ -31,8 +33,8 @@ public class ManagerExecutable implements KeywordExecutable {
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.PROCESS;
+  public CommandGroup commandGroup() {
+    return CommandGroups.PROCESS;
   }
 
   @Override
diff --git 
a/server/monitor/src/main/java/org/apache/accumulo/monitor/MonitorExecutable.java
 
b/server/monitor/src/main/java/org/apache/accumulo/monitor/MonitorExecutable.java
index e76909d651..de38eaee6a 100644
--- 
a/server/monitor/src/main/java/org/apache/accumulo/monitor/MonitorExecutable.java
+++ 
b/server/monitor/src/main/java/org/apache/accumulo/monitor/MonitorExecutable.java
@@ -18,6 +18,8 @@
  */
 package org.apache.accumulo.monitor;
 
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 
 import com.google.auto.service.AutoService;
@@ -31,8 +33,8 @@ public class MonitorExecutable implements KeywordExecutable {
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.PROCESS;
+  public CommandGroup commandGroup() {
+    return CommandGroups.PROCESS;
   }
 
   @Override
diff --git 
a/server/tserver/src/main/java/org/apache/accumulo/tserver/ScanServerExecutable.java
 
b/server/tserver/src/main/java/org/apache/accumulo/tserver/ScanServerExecutable.java
index d3bb036457..4643a97589 100644
--- 
a/server/tserver/src/main/java/org/apache/accumulo/tserver/ScanServerExecutable.java
+++ 
b/server/tserver/src/main/java/org/apache/accumulo/tserver/ScanServerExecutable.java
@@ -18,6 +18,8 @@
  */
 package org.apache.accumulo.tserver;
 
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 
 import com.google.auto.service.AutoService;
@@ -31,8 +33,8 @@ public class ScanServerExecutable implements 
KeywordExecutable {
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.PROCESS;
+  public CommandGroup commandGroup() {
+    return CommandGroups.PROCESS;
   }
 
   @Override
diff --git 
a/server/tserver/src/main/java/org/apache/accumulo/tserver/TServerExecutable.java
 
b/server/tserver/src/main/java/org/apache/accumulo/tserver/TServerExecutable.java
index c59a22fce7..dcbcd072b7 100644
--- 
a/server/tserver/src/main/java/org/apache/accumulo/tserver/TServerExecutable.java
+++ 
b/server/tserver/src/main/java/org/apache/accumulo/tserver/TServerExecutable.java
@@ -18,6 +18,8 @@
  */
 package org.apache.accumulo.tserver;
 
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 
 import com.google.auto.service.AutoService;
@@ -31,8 +33,8 @@ public class TServerExecutable implements KeywordExecutable {
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.PROCESS;
+  public CommandGroup commandGroup() {
+    return CommandGroups.PROCESS;
   }
 
   @Override
diff --git 
a/server/tserver/src/main/java/org/apache/accumulo/tserver/logger/LogReader.java
 
b/server/tserver/src/main/java/org/apache/accumulo/tserver/logger/LogReader.java
index ae7e25118b..260823dac6 100644
--- 
a/server/tserver/src/main/java/org/apache/accumulo/tserver/logger/LogReader.java
+++ 
b/server/tserver/src/main/java/org/apache/accumulo/tserver/logger/LogReader.java
@@ -45,6 +45,8 @@ import org.apache.accumulo.core.spi.crypto.NoFileEncrypter;
 import org.apache.accumulo.core.tabletserver.log.LogEntry;
 import org.apache.accumulo.server.ServerContext;
 import org.apache.accumulo.server.fs.VolumeManager;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.apache.accumulo.tserver.log.DfsLogger;
 import org.apache.accumulo.tserver.log.DfsLogger.LogHeaderIncompleteException;
@@ -102,6 +104,11 @@ public class LogReader implements KeywordExecutable {
     return "Prints WAL Info";
   }
 
+  @Override
+  public CommandGroup commandGroup() {
+    return CommandGroups.OTHER;
+  }
+
   @SuppressFBWarnings(value = "DM_EXIT",
       justification = "System.exit is fine here because it's a utility class 
executed by a main()")
   @Override
diff --git 
a/server/tserver/src/main/java/org/apache/accumulo/tserver/util/CreateEmpty.java
 
b/server/tserver/src/main/java/org/apache/accumulo/tserver/util/CreateEmpty.java
index b9f5d9fac9..436c236284 100644
--- 
a/server/tserver/src/main/java/org/apache/accumulo/tserver/util/CreateEmpty.java
+++ 
b/server/tserver/src/main/java/org/apache/accumulo/tserver/util/CreateEmpty.java
@@ -40,6 +40,8 @@ import org.apache.accumulo.core.spi.crypto.CryptoService;
 import org.apache.accumulo.core.spi.file.rfile.compression.NoCompression;
 import org.apache.accumulo.server.ServerContext;
 import org.apache.accumulo.server.fs.VolumeManager;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.apache.accumulo.tserver.logger.LogFileKey;
 import org.apache.accumulo.tserver.logger.LogFileValue;
@@ -118,6 +120,11 @@ public class CreateEmpty implements KeywordExecutable {
     return "Creates empty RFiles (RF) or empty write-ahead log (WAL) files for 
emergency recovery";
   }
 
+  @Override
+  public CommandGroup commandGroup() {
+    return CommandGroups.OTHER;
+  }
+
   @Override
   public void execute(String[] args) throws Exception {
 
diff --git a/shell/src/main/java/org/apache/accumulo/shell/Shell.java 
b/shell/src/main/java/org/apache/accumulo/shell/Shell.java
index 18a469b390..f1b939889b 100644
--- a/shell/src/main/java/org/apache/accumulo/shell/Shell.java
+++ b/shell/src/main/java/org/apache/accumulo/shell/Shell.java
@@ -20,6 +20,8 @@ package org.apache.accumulo.shell;
 
 import static java.nio.charset.StandardCharsets.ISO_8859_1;
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.accumulo.shell.ShellOptions.helpLongOption;
+import static org.apache.accumulo.shell.ShellOptions.helpOption;
 
 import java.io.BufferedWriter;
 import java.io.File;
@@ -51,6 +53,7 @@ import java.util.stream.Stream;
 
 import org.apache.accumulo.core.Constants;
 import org.apache.accumulo.core.classloader.ClassLoaderUtil;
+import org.apache.accumulo.core.cli.ClientKeywordExecutable;
 import org.apache.accumulo.core.cli.ClientOpts.PasswordConverter;
 import org.apache.accumulo.core.client.Accumulo;
 import org.apache.accumulo.core.client.AccumuloClient;
@@ -168,6 +171,8 @@ import org.apache.accumulo.shell.commands.UserCommand;
 import org.apache.accumulo.shell.commands.UserPermissionsCommand;
 import org.apache.accumulo.shell.commands.UsersCommand;
 import org.apache.accumulo.shell.commands.WhoAmICommand;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.DefaultParser;
@@ -201,7 +206,7 @@ import io.opentelemetry.context.Scope;
  * and quoted strings with escape sequences
  */
 @AutoService(KeywordExecutable.class)
-public class Shell extends ShellOptions implements KeywordExecutable {
+public class Shell extends ClientKeywordExecutable<ShellOptionsJC> {
 
   public static final Logger log = LoggerFactory.getLogger(Shell.class);
   private static final Logger audit = 
LoggerFactory.getLogger(Shell.class.getName() + ".audit");
@@ -266,9 +271,12 @@ public class Shell extends ShellOptions implements 
KeywordExecutable {
   }
 
   // no arg constructor should do minimal work since it's used in Main 
ServiceLoader
-  public Shell() {}
+  public Shell() {
+    super(new ShellOptionsJC());
+  }
 
   public Shell(LineReader reader) {
+    super(new ShellOptionsJC());
     this.reader = reader;
     this.terminal = reader.getTerminal();
     this.writer = terminal.writer();
@@ -310,7 +318,7 @@ public class Shell extends ShellOptions implements 
KeywordExecutable {
    * @return true if the shell was successfully configured, false otherwise.
    * @throws IOException if problems occur creating the LineReader
    */
-  public boolean config(String... args) throws IOException {
+  public boolean config(JCommander jc, ShellOptionsJC options) throws 
IOException {
     if (this.terminal == null) {
       this.terminal =
           
TerminalBuilder.builder().jansi(false).systemOutput(SystemOutput.SysOut).build();
@@ -320,26 +328,6 @@ public class Shell extends ShellOptions implements 
KeywordExecutable {
     }
     this.writer = this.terminal.writer();
 
-    ShellOptionsJC options = new ShellOptionsJC();
-    JCommander jc = new JCommander();
-
-    jc.setProgramName("accumulo shell");
-    jc.addObject(options);
-    try {
-      jc.parse(args);
-    } catch (ParameterException e) {
-      jc.usage();
-      exitCode = 1;
-      return false;
-    }
-
-    if (options.isHelpEnabled()) {
-      jc.usage();
-      // Not an error
-      exitCode = 0;
-      return false;
-    }
-
     if (options.getUnrecognizedOptions() != null) {
       logError("Unrecognized Options: " + options.getUnrecognizedOptions());
       jc.usage();
@@ -350,7 +338,7 @@ public class Shell extends ShellOptions implements 
KeywordExecutable {
     authTimeout = Duration.ofMinutes(options.getAuthTimeout());
     disableAuthTimeout = options.isAuthTimeoutDisabled();
 
-    clientProperties = options.getClientProperties();
+    clientProperties = options.getClientProps();
     if (ClientProperty.SASL_ENABLED.getBoolean(clientProperties)) {
       log.debug("SASL is enabled, disabling authorization timeout");
       disableAuthTimeout = true;
@@ -360,25 +348,8 @@ public class Shell extends ShellOptions implements 
KeywordExecutable {
     this.setTableName("");
 
     if (accumuloClient == null) {
-      if (ClientProperty.INSTANCE_ZOOKEEPERS.isEmpty(clientProperties)) {
-        throw new IllegalArgumentException("ZooKeepers must be set using -z or 
-zh on command line"
-            + " or in accumulo-client.properties");
-      }
-      if (ClientProperty.INSTANCE_NAME.isEmpty(clientProperties)) {
-        throw new IllegalArgumentException("Instance name must be set using -z 
or -zi on command "
-            + "line or in accumulo-client.properties");
-      }
-      final String principal;
-      try {
-        principal = options.getUsername();
-      } catch (Exception e) {
-        logError(e.getMessage());
-        exitCode = 1;
-        return false;
-      }
-      String authenticationString = options.getPassword();
-      final AuthenticationToken token =
-          getAuthenticationToken(principal, authenticationString, "Password: 
");
+      final String principal = 
ClientProperty.AUTH_PRINCIPAL.getValue(clientProperties);
+      final AuthenticationToken token = 
ClientProperty.getAuthenticationToken(clientProperties);
       try {
         this.setTableName("");
         accumuloClient = 
Accumulo.newClient().from(clientProperties).as(principal, token).build();
@@ -509,8 +480,8 @@ public class Shell extends ShellOptions implements 
KeywordExecutable {
   }
 
   @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.CORE;
+  public CommandGroup commandGroup() {
+    return CommandGroups.CLIENT;
   }
 
   @Override
@@ -518,15 +489,12 @@ public class Shell extends ShellOptions implements 
KeywordExecutable {
     return "Runs Accumulo shell";
   }
 
-  @SuppressFBWarnings(value = "DM_EXIT", justification = "System.exit() from a 
main class is okay")
   @Override
-  public void execute(final String[] args) throws IOException {
+  public void execute(JCommander cl, ShellOptionsJC options) throws Exception {
     try {
-      if (!config(args)) {
-        System.exit(getExitCode());
+      if (config(cl, options)) {
+        start();
       }
-
-      System.exit(start());
     } finally {
       shutdown();
     }
@@ -541,7 +509,7 @@ public class Shell extends ShellOptions implements 
KeywordExecutable {
     return builder;
   }
 
-  public static void main(String[] args) throws IOException {
+  public static void main(String[] args) throws Exception {
     LineReader reader = newLineReaderBuilder().build();
     new Shell(reader).execute(args);
   }
diff --git a/shell/src/main/java/org/apache/accumulo/shell/ShellCompletor.java 
b/shell/src/main/java/org/apache/accumulo/shell/ShellCompletor.java
index 12c4c40d63..4b38a089ad 100644
--- a/shell/src/main/java/org/apache/accumulo/shell/ShellCompletor.java
+++ b/shell/src/main/java/org/apache/accumulo/shell/ShellCompletor.java
@@ -98,15 +98,15 @@ public class ShellCompletor implements Completer {
 
           // we're in a subcommand so try to match the universal
           // option flags if we're there
-          if (current_string_token.trim().equals("-" + Shell.tableOption)) {
+          if (current_string_token.trim().equals("-" + 
ShellOptions.tableOption)) {
             
candidates.addAll(options.get(Shell.Command.CompletionSet.TABLENAMES));
-            prefix += "-" + Shell.tableOption + " ";
-          } else if (current_string_token.trim().equals("-" + 
Shell.userOption)) {
+            prefix += "-" + ShellOptions.tableOption + " ";
+          } else if (current_string_token.trim().equals("-" + 
ShellOptions.userOption)) {
             
candidates.addAll(options.get(Shell.Command.CompletionSet.USERNAMES));
-            prefix += "-" + Shell.userOption + " ";
-          } else if (current_string_token.trim().equals("-" + 
Shell.namespaceOption)) {
+            prefix += "-" + ShellOptions.userOption + " ";
+          } else if (current_string_token.trim().equals("-" + 
ShellOptions.namespaceOption)) {
             
candidates.addAll(options.get(Shell.Command.CompletionSet.NAMESPACES));
-            prefix += "-" + Shell.namespaceOption + " ";
+            prefix += "-" + ShellOptions.namespaceOption + " ";
           } else if (current_command_token != null) {
             Token next = 
current_command_token.getSubcommand(current_string_token);
             if (next != null) {
@@ -152,11 +152,11 @@ public class ShellCompletor implements Completer {
         return prefix.length();
       }
 
-      if (current_string_token.trim().equals("-" + Shell.tableOption)) {
+      if (current_string_token.trim().equals("-" + ShellOptions.tableOption)) {
         inTableFlag = true;
-      } else if (current_string_token.trim().equals("-" + Shell.userOption)) {
+      } else if (current_string_token.trim().equals("-" + 
ShellOptions.userOption)) {
         inUserFlag = true;
-      } else if (current_string_token.trim().equals("-" + 
Shell.namespaceOption)) {
+      } else if (current_string_token.trim().equals("-" + 
ShellOptions.namespaceOption)) {
         inNamespaceFlag = true;
       } else {
         inUserFlag = inTableFlag = inNamespaceFlag = false;
diff --git a/shell/src/main/java/org/apache/accumulo/shell/ShellOptionsJC.java 
b/shell/src/main/java/org/apache/accumulo/shell/ShellOptionsJC.java
index a837f308de..627b69e2b8 100644
--- a/shell/src/main/java/org/apache/accumulo/shell/ShellOptionsJC.java
+++ b/shell/src/main/java/org/apache/accumulo/shell/ShellOptionsJC.java
@@ -21,17 +21,12 @@ package org.apache.accumulo.shell;
 import java.io.File;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.Properties;
 import java.util.TreeMap;
 
 import org.apache.accumulo.core.cli.ClientOpts;
-import org.apache.accumulo.core.clientImpl.ClientInfoImpl;
-import org.apache.accumulo.core.conf.ClientProperty;
-import org.apache.hadoop.security.UserGroupInformation;
 
 import com.beust.jcommander.DynamicParameter;
 import com.beust.jcommander.IParameterValidator;
@@ -41,18 +36,7 @@ import com.beust.jcommander.converters.FileConverter;
 
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 
-public class ShellOptionsJC {
-
-  @Parameter(names = {"-u", "--user"}, description = "username")
-  private String username = null;
-
-  // Note: Don't use "password = true" because then it will prompt even if we 
have a token
-  @Parameter(names = {"-p", "--password"},
-      description = "password (can be specified as '<password>', 
'pass:<password>',"
-          + " 'file:<local file containing the password>', 'env:<variable 
containing"
-          + " the pass>', or stdin)",
-      converter = ClientOpts.PasswordConverter.class)
-  private String authenticationString;
+public class ShellOptionsJC extends ClientOpts {
 
   @DynamicParameter(names = {"-l"},
       description = "command line properties in the format key=value. Reuse -l 
for each property")
@@ -62,9 +46,6 @@ public class ShellOptionsJC {
       description = "disables tab completion (for less overhead when 
scripting)")
   private boolean tabCompletionDisabled;
 
-  @Parameter(names = {"-?", "--help"}, help = true, description = "display 
this help")
-  private boolean helpEnabled;
-
   @Parameter(names = {"-e", "--execute-command"},
       description = "executes a command, and then exits")
   private String execCommand;
@@ -78,37 +59,6 @@ public class ShellOptionsJC {
       converter = FileConverter.class)
   private File execFileVerbose;
 
-  @Parameter(names = {"-z", "--zooKeeperInstance"},
-      description = "use a zookeeper instance with the given instance name and"
-          + " list of zoo hosts. Syntax: -z <zoo-instance-name> <zoo-hosts>. 
Where"
-          + " <zoo-hosts> is a comma separated list of zookeeper servers.",
-      arity = 2)
-  private List<String> zooKeeperInstance = new ArrayList<>();
-
-  @Parameter(names = {"--ssl"}, description = "use ssl to connect to accumulo")
-  private boolean useSsl = false;
-
-  @Parameter(names = "--sasl", description = "use SASL to connect to Accumulo 
(Kerberos)")
-  private boolean useSasl = false;
-
-  @Parameter(names = "--config-file",
-      description = "Read the given"
-          + " accumulo-client.properties file. If omitted, the following 
locations will be"
-          + " searched ~/.accumulo/accumulo-client.properties:"
-          + "$ACCUMULO_CONF_DIR/accumulo-client.properties:"
-          + "/etc/accumulo/accumulo-client.properties")
-  private String clientConfigFile = null;
-
-  @Parameter(names = {"-zi", "--zooKeeperInstanceName"},
-      description = "use a zookeeper instance with the given instance name. "
-          + "This parameter is used in conjunction with -zh.")
-  private String zooKeeperInstanceName;
-
-  @Parameter(names = {"-zh", "--zooKeeperHosts"},
-      description = "use a zookeeper instance with the given comma separated"
-          + " list of zookeeper servers. This parameter is used in conjunction 
with -zi.")
-  private String zooKeeperHosts;
-
   @Parameter(names = "--auth-timeout",
       description = "minutes the shell can be idle without re-entering a 
password",
       validateWith = PositiveInteger.class)
@@ -121,39 +71,10 @@ public class ShellOptionsJC {
   @Parameter(hidden = true)
   private List<String> unrecognizedOptions;
 
-  public String getUsername() throws Exception {
-    if (username == null) {
-      username = 
getClientProperties().getProperty(ClientProperty.AUTH_PRINCIPAL.getKey());
-      if (username == null || username.isEmpty()) {
-        if (ClientProperty.SASL_ENABLED.getBoolean(getClientProperties())) {
-          if (!UserGroupInformation.isSecurityEnabled()) {
-            throw new IllegalArgumentException(
-                "Kerberos security is not enabled. Run with --sasl or set 
'sasl.enabled' in"
-                    + " accumulo-client.properties");
-          }
-          UserGroupInformation ugi = UserGroupInformation.getCurrentUser();
-          username = ugi.getUserName();
-        } else {
-          throw new IllegalArgumentException("Username is not set. Run with 
'-u"
-              + " myuser' or set 'auth.principal' in 
accumulo-client.properties");
-        }
-      }
-    }
-    return username;
-  }
-
-  public String getPassword() {
-    return authenticationString;
-  }
-
   public boolean isTabCompletionDisabled() {
     return tabCompletionDisabled;
   }
 
-  public boolean isHelpEnabled() {
-    return helpEnabled;
-  }
-
   public String getExecCommand() {
     return execCommand;
   }
@@ -171,9 +92,6 @@ public class ShellOptionsJC {
   }
 
   public boolean isAuthTimeoutDisabled() {
-    if (useSasl) {
-      return true;
-    }
     return authTimeoutDisabled;
   }
 
@@ -183,8 +101,10 @@ public class ShellOptionsJC {
 
   @SuppressFBWarnings(value = "PATH_TRAVERSAL_IN",
       justification = "user-provided paths intentional")
-  public String getClientPropertiesFile() {
-    if (clientConfigFile == null) {
+  @Override
+  public String getClientConfigFile() {
+    String configFile = super.getClientConfigFile();
+    if (configFile == null) {
       List<String> searchPaths = new LinkedList<>();
       searchPaths.add(System.getProperty("user.home") + 
"/.accumulo/accumulo-client.properties");
       if (System.getenv("ACCUMULO_CONF_DIR") != null) {
@@ -194,43 +114,15 @@ public class ShellOptionsJC {
       for (String path : searchPaths) {
         Path file = Path.of(path);
         if (Files.isRegularFile(file) && Files.isReadable(file)) {
-          clientConfigFile = file.toAbsolutePath().toString();
+          String clientConfigFile = file.toAbsolutePath().toString();
           Shell.log.info("Loading configuration from {}", clientConfigFile);
-          break;
+          return clientConfigFile;
         }
       }
+      return null;
+    } else {
+      return configFile;
     }
-    return clientConfigFile;
-  }
-
-  public Properties getClientProperties() {
-    Properties props = new Properties();
-    if (getClientPropertiesFile() != null) {
-      props = ClientInfoImpl.toProperties(getClientPropertiesFile());
-    }
-    for (Map.Entry<String,String> entry : commandLineProperties.entrySet()) {
-      props.setProperty(entry.getKey(), entry.getValue());
-    }
-    if (useSsl) {
-      props.setProperty(ClientProperty.SSL_ENABLED.getKey(), "true");
-    }
-    if (useSasl) {
-      props.setProperty(ClientProperty.SASL_ENABLED.getKey(), "true");
-    }
-    if (!zooKeeperInstance.isEmpty()) {
-      String instanceName = zooKeeperInstance.get(0);
-      String hosts = zooKeeperInstance.get(1);
-      props.setProperty(ClientProperty.INSTANCE_ZOOKEEPERS.getKey(), hosts);
-      props.setProperty(ClientProperty.INSTANCE_NAME.getKey(), instanceName);
-    }
-    // If the user provided the hosts, set the ZK for tracing too
-    if (zooKeeperHosts != null && !zooKeeperHosts.isEmpty()) {
-      props.setProperty(ClientProperty.INSTANCE_ZOOKEEPERS.getKey(), 
zooKeeperHosts);
-    }
-    if (zooKeeperInstanceName != null && !zooKeeperInstanceName.isEmpty()) {
-      props.setProperty(ClientProperty.INSTANCE_NAME.getKey(), 
zooKeeperInstanceName);
-    }
-    return props;
   }
 
   public static class PositiveInteger implements IParameterValidator {
diff --git a/shell/src/test/java/org/apache/accumulo/shell/ShellConfigTest.java 
b/shell/src/test/java/org/apache/accumulo/shell/ShellConfigTest.java
index fc8e244873..14724cc0c7 100644
--- a/shell/src/test/java/org/apache/accumulo/shell/ShellConfigTest.java
+++ b/shell/src/test/java/org/apache/accumulo/shell/ShellConfigTest.java
@@ -18,7 +18,7 @@
  */
 package org.apache.accumulo.shell;
 
-import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.FileDescriptor;
@@ -96,14 +96,15 @@ public class ShellConfigTest {
   }
 
   @Test
-  public void testHelp() throws IOException {
-    assertFalse(shell.config(args("--help")));
+  public void testHelp() throws Exception {
+    shell.execute(args("--help"));
     assertTrue(output.get().startsWith("Usage"), "Did not print usage");
   }
 
   @Test
-  public void testBadArg() throws IOException {
-    assertFalse(shell.config(args("--bogus")));
+  public void testBadArg() throws Exception {
+    shell.execute(args("--bogus"));
+    assertNotEquals(0, shell.getExitCode());
     // JCommander versions after 1.60 will cause the Shell to detect the arg 
as Unrecognized option
     assertTrue(output.get().startsWith("ERROR"), "Did not print Error");
     assertTrue(output.get().contains("Usage"), "Did not print usage");
diff --git 
a/shell/src/test/java/org/apache/accumulo/shell/ShellOptionsJCTest.java 
b/shell/src/test/java/org/apache/accumulo/shell/ShellOptionsJCTest.java
deleted file mode 100644
index df91a2ebb7..0000000000
--- a/shell/src/test/java/org/apache/accumulo/shell/ShellOptionsJCTest.java
+++ /dev/null
@@ -1,64 +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
- *
- *   https://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.accumulo.shell;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import java.util.Properties;
-
-import org.apache.accumulo.core.conf.ClientProperty;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import com.beust.jcommander.JCommander;
-
-public class ShellOptionsJCTest {
-
-  ShellOptionsJC options;
-
-  @BeforeEach
-  public void setup() {
-    options = new ShellOptionsJC();
-  }
-
-  @Test
-  public void testSasl() throws Exception {
-    JCommander jc = new JCommander();
-
-    jc.setProgramName("accumulo shell");
-    jc.addObject(options);
-    jc.parse("--sasl");
-    Properties properties = options.getClientProperties();
-    assertEquals("true", 
properties.getProperty(ClientProperty.SASL_ENABLED.getKey()));
-  }
-
-  @Test
-  public void testTraceHosts() throws Exception {
-    // Set the zk hosts in the client conf directly for tracing
-    final String zk = "localhost:45454";
-    JCommander jc = new JCommander();
-
-    jc.setProgramName("accumulo shell");
-    jc.addObject(options);
-    jc.parse(new String[] {"-zh", zk});
-    Properties properties = options.getClientProperties();
-    assertEquals(zk, 
properties.getProperty(ClientProperty.INSTANCE_ZOOKEEPERS.getKey()));
-  }
-
-}
diff --git a/start/pom.xml b/start/pom.xml
index 0766d612a9..76782d1515 100644
--- a/start/pom.xml
+++ b/start/pom.xml
@@ -33,6 +33,11 @@
     
<spotbugs.omitVisitors>SharedVariableAtomicityDetector,ConstructorThrow</spotbugs.omitVisitors>
   </properties>
   <dependencies>
+    <dependency>
+      <groupId>com.google.auto.service</groupId>
+      <artifactId>auto-service</artifactId>
+      <optional>true</optional>
+    </dependency>
     <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
diff --git a/start/src/main/java/org/apache/accumulo/start/Main.java 
b/start/src/main/java/org/apache/accumulo/start/Main.java
index d45efd5e57..54a7d8fb53 100644
--- a/start/src/main/java/org/apache/accumulo/start/Main.java
+++ b/start/src/main/java/org/apache/accumulo/start/Main.java
@@ -21,17 +21,17 @@ package org.apache.accumulo.start;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
-import java.util.Arrays;
 import java.util.Collections;
-import java.util.EnumMap;
-import java.util.List;
 import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.ServiceLoader;
+import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeSet;
 
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
-import org.apache.accumulo.start.spi.KeywordExecutable.UsageGroup;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -39,7 +39,7 @@ public class Main {
 
   private static final Logger log = LoggerFactory.getLogger(Main.class);
   private static ClassLoader classLoader;
-  private static Map<UsageGroup,Map<String,KeywordExecutable>> servicesMap;
+  private static Map<CommandGroup,Map<String,KeywordExecutable>> servicesMap;
 
   public static void main(final String[] args) throws Exception {
     final ClassLoader loader = getClassLoader();
@@ -53,23 +53,48 @@ public class Main {
       return;
     }
 
-    if (args.length == 1) {
-      execMainClassName(args[0], new String[] {});
+    Set<CommandGroup> usageGroups = CommandGroups.getGroups();
+    Map<CommandGroup,Map<String,KeywordExecutable>> executables = 
getExecutables(loader);
+
+    // The commands in the CLIENT group may be specified without a group name. 
For example,
+    // instead of `accumulo client shell`, we support the user supplying 
`accumulo shell`.
+    // Check to see if the first arg is in the client group first.
+
+    CommandGroup clientGroup = CommandGroups.CLIENT;
+    String cmd = args[0];
+    int argOffset = 1;
+    KeywordExecutable ke = executables.get(clientGroup).get(cmd);
+
+    if (ke != null) {
+      execKeyword(ke, stripArgs(args, argOffset));
+    } else if (args.length == 1) {
+      // If only one arg, and not a client command, then it's a class with
+      // no args.
+      execMainClassName(cmd, stripArgs(args, argOffset));
     } else {
-      String arg1 = args[0];
-      String arg2 = args[1];
-      KeywordExecutable keywordExec = null;
+      // More than 1 arg, check to see if it's a command, otherwise exec class
+      String group = args[0];
+      cmd = args[1];
+      argOffset = 2;
+      CommandGroup ug = null;
       try {
-        UsageGroup group = UsageGroup.valueOf(arg1.toUpperCase());
-        keywordExec = getExecutables(loader).get(group).get(arg2);
-      } catch (IllegalArgumentException e) {}
-      if (keywordExec != null) {
-        execKeyword(keywordExec, stripArgs(args, 2));
+        ug = usageGroups.stream().filter(g -> 
g.title().equalsIgnoreCase(group)).findFirst()
+            .orElseThrow();
+        ke = executables.get(ug).get(cmd);
+      } catch (NoSuchElementException e) {
+        // args[0] is not a valid group
+        ke = null;
+      }
+
+      if (ke != null) {
+        execKeyword(ke, stripArgs(args, argOffset));
       } else {
-        execMainClassName(arg1, stripArgs(args, 1));
+        String clazz = args[0];
+        argOffset = 1;
+        execMainClassName(clazz, stripArgs(args, argOffset));
       }
-    }
 
+    }
   }
 
   public static synchronized ClassLoader getClassLoader() {
@@ -173,21 +198,19 @@ public class Main {
     System.out.println("    accumulo classpath");
     System.out.println("    accumulo jshell (<argument> ...)");
     System.out.println("    accumulo className (<argument> ...)");
-    System.out.println("    accumulo <group> <command> [--help] (<argument> 
...)\n\n");
-
-    Map<UsageGroup,Map<String,KeywordExecutable>> exectuables = 
getExecutables(getClassLoader());
-    List<UsageGroup> groups = Arrays.asList(UsageGroup.values());
-    Collections.sort(groups);
-    groups.forEach(g -> {
-      System.out.println("\n" + g.name() + " Group Commands:");
-      exectuables.get(g).values()
+    System.out.println("    accumulo <group>* <command> [--help] (<argument> 
...)");
+    System.out.println("    * group may be omitted for commands with 'CLIENT' 
group\n\n");
+
+    Map<CommandGroup,Map<String,KeywordExecutable>> executables = 
getExecutables(getClassLoader());
+    executables.entrySet().forEach(e -> {
+      System.out.println("\n" + e.getKey().title() + " Group Commands:");
+      e.getValue().values()
           .forEach(ke -> System.out.printf("  %-30s %s\n", ke.usage(), 
ke.description()));
     });
-
     System.out.println();
   }
 
-  public static synchronized Map<UsageGroup,Map<String,KeywordExecutable>>
+  public static synchronized Map<CommandGroup,Map<String,KeywordExecutable>>
       getExecutables(final ClassLoader cl) {
     if (servicesMap == null) {
       servicesMap = 
checkDuplicates(ServiceLoader.load(KeywordExecutable.class, cl));
@@ -195,7 +218,7 @@ public class Main {
     return servicesMap;
   }
 
-  private record BanKey(UsageGroup group, String keyword) implements 
Comparable<BanKey> {
+  private record BanKey(CommandGroup group, String keyword) implements 
Comparable<BanKey> {
     @Override
     public int compareTo(BanKey o) {
       int result = this.group.compareTo(o.group);
@@ -206,15 +229,13 @@ public class Main {
     }
   };
 
-  public static Map<UsageGroup,Map<String,KeywordExecutable>>
+  public static Map<CommandGroup,Map<String,KeywordExecutable>>
       checkDuplicates(final Iterable<? extends KeywordExecutable> services) {
     TreeSet<BanKey> banList = new TreeSet<>();
-    EnumMap<UsageGroup,Map<String,KeywordExecutable>> results = new 
EnumMap<>(UsageGroup.class);
-    for (UsageGroup ug : UsageGroup.values()) {
-      results.put(ug, new TreeMap<>());
-    }
+    Map<CommandGroup,Map<String,KeywordExecutable>> results = new TreeMap<>();
+    CommandGroups.getGroups().forEach(ug -> results.put(ug, new TreeMap<>()));
     for (KeywordExecutable service : services) {
-      UsageGroup group = service.usageGroup();
+      CommandGroup group = service.commandGroup();
       String keyword = service.keyword();
       BanKey bk = new BanKey(group, keyword);
       if (banList.contains(bk)) {
diff --git a/core/src/main/java/org/apache/accumulo/core/util/Help.java 
b/start/src/main/java/org/apache/accumulo/start/spi/CommandGroup.java
similarity index 60%
copy from core/src/main/java/org/apache/accumulo/core/util/Help.java
copy to start/src/main/java/org/apache/accumulo/start/spi/CommandGroup.java
index eabed9088d..d1f0313fe4 100644
--- a/core/src/main/java/org/apache/accumulo/core/util/Help.java
+++ b/start/src/main/java/org/apache/accumulo/start/spi/CommandGroup.java
@@ -16,32 +16,26 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.accumulo.core.util;
+package org.apache.accumulo.start.spi;
 
-import org.apache.accumulo.start.Main;
-import org.apache.accumulo.start.spi.KeywordExecutable;
+public interface CommandGroup extends Comparable<CommandGroup> {
 
-import com.google.auto.service.AutoService;
+  String key();
 
-@AutoService(KeywordExecutable.class)
-public class Help implements KeywordExecutable {
-  @Override
-  public String keyword() {
-    return "help";
-  }
+  String title();
 
-  @Override
-  public UsageGroup usageGroup() {
-    return UsageGroup.CORE;
-  }
+  String description();
 
   @Override
-  public String description() {
-    return "Prints usage";
+  default int compareTo(CommandGroup o) {
+    int result = this.key().compareTo(o.key());
+    if (result == 0) {
+      result = this.title().compareTo(o.title());
+      if (result == 0) {
+        result = this.description().compareTo(o.description());
+      }
+    }
+    return result;
   }
 
-  @Override
-  public void execute(final String[] args) {
-    Main.printUsage();
-  }
 }
diff --git 
a/start/src/main/java/org/apache/accumulo/start/spi/CommandGroups.java 
b/start/src/main/java/org/apache/accumulo/start/spi/CommandGroups.java
new file mode 100644
index 0000000000..e98d77ab5a
--- /dev/null
+++ b/start/src/main/java/org/apache/accumulo/start/spi/CommandGroups.java
@@ -0,0 +1,185 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.start.spi;
+
+import java.util.Objects;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.TreeSet;
+
+import com.google.auto.service.AutoService;
+
+public class CommandGroups {
+
+  public static final CommandGroup ADMIN = new AdminCommandGroup();
+  public static final CommandGroup CLIENT = new ClientCommandGroup();
+  public static final CommandGroup COMPACTION = new CompactionCommandGroup();
+  public static final CommandGroup CORE = new CoreCommandGroup();
+  public static final CommandGroup OTHER = new OtherCommandGroup();
+  public static final CommandGroup PROCESS = new ProcessCommandGroup();
+
+  private static Set<CommandGroup> groups = null;
+
+  public static synchronized Set<CommandGroup> getGroups() {
+    if (groups == null) {
+      Set<CommandGroup> ug = new TreeSet<>();
+      ServiceLoader.load(CommandGroup.class).forEach(ug::add);
+      groups = ug;
+    }
+    return groups;
+  }
+
+  public static abstract class BaseCommandGroup implements CommandGroup {
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(key(), title(), description());
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (obj == null) {
+        return false;
+      }
+      if (obj == this) {
+        return true;
+      }
+      if (obj instanceof CommandGroup) {
+        return this.compareTo((CommandGroup) obj) == 0;
+      }
+      return false;
+    }
+
+  }
+
+  @AutoService(CommandGroup.class)
+  public static class AdminCommandGroup extends BaseCommandGroup {
+
+    @Override
+    public String key() {
+      return "admin";
+    }
+
+    @Override
+    public String title() {
+      return "Admin";
+    }
+
+    @Override
+    public String description() {
+      return "Administrative commands.";
+    }
+  }
+
+  @AutoService(CommandGroup.class)
+  public static class ClientCommandGroup extends BaseCommandGroup {
+
+    @Override
+    public String key() {
+      return "";
+    }
+
+    @Override
+    public String title() {
+      return "Client";
+    }
+
+    @Override
+    public String description() {
+      return "Client commands, requires accumulo-client.properties only, group 
is optional in command.";
+    }
+  }
+
+  @AutoService(CommandGroup.class)
+  public static class CompactionCommandGroup extends BaseCommandGroup {
+
+    @Override
+    public String key() {
+      return "compaction";
+    }
+
+    @Override
+    public String title() {
+      return "Compaction";
+    }
+
+    @Override
+    public String description() {
+      return "Compaction related commands";
+    }
+  }
+
+  @AutoService(CommandGroup.class)
+  public static class CoreCommandGroup extends BaseCommandGroup {
+
+    @Override
+    public String key() {
+      return "core";
+    }
+
+    @Override
+    public String title() {
+      return "Core";
+    }
+
+    @Override
+    public String description() {
+      return "Core commands";
+    }
+  }
+
+  @AutoService(CommandGroup.class)
+  public static class ProcessCommandGroup extends BaseCommandGroup {
+
+    @Override
+    public String key() {
+      return "process";
+    }
+
+    @Override
+    public String title() {
+      return "Process";
+    }
+
+    @Override
+    public String description() {
+      return "Process related commands";
+    }
+  }
+
+  @AutoService(CommandGroup.class)
+  public static class OtherCommandGroup extends BaseCommandGroup {
+
+    @Override
+    public String key() {
+      return "other";
+    }
+
+    @Override
+    public String title() {
+      return "Other";
+    }
+
+    @Override
+    public String description() {
+      return "Other commands";
+    }
+  }
+
+}
diff --git 
a/start/src/main/java/org/apache/accumulo/start/spi/KeywordExecutable.java 
b/start/src/main/java/org/apache/accumulo/start/spi/KeywordExecutable.java
index 04e2c80169..dfc96addfb 100644
--- a/start/src/main/java/org/apache/accumulo/start/spi/KeywordExecutable.java
+++ b/start/src/main/java/org/apache/accumulo/start/spi/KeywordExecutable.java
@@ -45,7 +45,7 @@ import java.util.ServiceLoader;
 public interface KeywordExecutable {
 
   enum UsageGroup {
-    ADMIN, COMPACTION, CORE, PROCESS, OTHER
+    CORE, PROCESS, OTHER
   }
 
   /**
@@ -63,10 +63,16 @@ public interface KeywordExecutable {
   /**
    * @return Usage group for this command
    */
+  @Deprecated(since = "4.0.0")
   default UsageGroup usageGroup() {
     return UsageGroup.OTHER;
   }
 
+  /**
+   * @return CommandGroup for this command
+   */
+  CommandGroup commandGroup();
+
   /**
    * @return Description of service
    */
diff --git a/test/src/main/java/org/apache/accumulo/test/shell/MockShell.java 
b/test/src/main/java/org/apache/accumulo/test/shell/MockShell.java
index 965be08ddf..308e085406 100644
--- a/test/src/main/java/org/apache/accumulo/test/shell/MockShell.java
+++ b/test/src/main/java/org/apache/accumulo/test/shell/MockShell.java
@@ -28,6 +28,7 @@ import java.io.OutputStream;
 
 import org.apache.accumulo.core.clientImpl.ClientInfo;
 import org.apache.accumulo.shell.Shell;
+import org.apache.accumulo.shell.ShellOptionsJC;
 import org.jline.reader.LineReader;
 import org.jline.reader.LineReaderBuilder;
 import org.jline.terminal.Terminal;
@@ -35,33 +36,49 @@ import org.jline.terminal.impl.DumbTerminal;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.beust.jcommander.JCommander;
+
 public class MockShell {
+
   private static final Logger shellLog = 
LoggerFactory.getLogger(MockShell.class);
   private static final ErrorMessageCallback noop = new ErrorMessageCallback();
 
+  public static class TestShell extends Shell {
+
+    public TestShell(LineReader reader) {
+      super(reader);
+    }
+
+    @Override
+    public void execute(JCommander cl, ShellOptionsJC options) throws 
Exception {
+      config(cl, options);
+    }
+
+  }
+
   final TestOutputStream output;
   final StringInputStream input;
-  public Shell shell;
+  public TestShell shell;
   public LineReader reader;
   public Terminal terminal;
 
   MockShell(String user, String rootPass, String instanceName, String 
zookeepers, File configFile)
-      throws IOException {
+      throws Exception {
     ClientInfo info = ClientInfo.from(configFile.toPath());
     // start the shell
     output = new TestOutputStream();
     input = new StringInputStream();
     terminal = new DumbTerminal(input, output);
     reader = LineReaderBuilder.builder().terminal(terminal).build();
-    shell = new Shell(reader);
+    shell = new TestShell(reader);
     shell.setLogErrorsToConsole();
     if (info.saslEnabled()) {
       // Pull the kerberos principal out when we're using SASL
-      shell.config("-u", user, "-z", instanceName, zookeepers, "--config-file",
-          configFile.getAbsolutePath());
+      shell.execute(new String[] {"-u", user, "-z", instanceName, zookeepers, 
"--config-file",
+          configFile.getAbsolutePath()});
     } else {
-      shell.config("-u", user, "-p", rootPass, "-z", instanceName, zookeepers, 
"--config-file",
-          configFile.getAbsolutePath());
+      shell.execute(new String[] {"-u", user, "-p", rootPass, "-z", 
instanceName, zookeepers,
+          "--config-file", configFile.getAbsolutePath()});
     }
     exec("quit", true);
     shell.start();
diff --git 
a/test/src/main/java/org/apache/accumulo/test/shell/ShellAuthenticatorIT_SimpleSuite.java
 
b/test/src/main/java/org/apache/accumulo/test/shell/ShellAuthenticatorIT_SimpleSuite.java
index 18b8c1e5ff..7518cc6680 100644
--- 
a/test/src/main/java/org/apache/accumulo/test/shell/ShellAuthenticatorIT_SimpleSuite.java
+++ 
b/test/src/main/java/org/apache/accumulo/test/shell/ShellAuthenticatorIT_SimpleSuite.java
@@ -18,14 +18,13 @@
  */
 package org.apache.accumulo.test.shell;
 
-import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.IOException;
 import java.util.TimeZone;
 
 import org.apache.accumulo.harness.SharedMiniClusterBase;
-import org.apache.accumulo.shell.Shell;
+import org.apache.accumulo.test.shell.MockShell.TestShell;
 import org.apache.accumulo.test.shell.ShellIT.StringInputStream;
 import org.apache.accumulo.test.shell.ShellIT.TestOutputStream;
 import org.jline.reader.LineReader;
@@ -53,7 +52,7 @@ public class ShellAuthenticatorIT_SimpleSuite extends 
SharedMiniClusterBase {
 
   private StringInputStream input;
   private TestOutputStream output;
-  private Shell shell;
+  private TestShell shell;
   private LineReader reader;
   private Terminal terminal;
 
@@ -75,40 +74,45 @@ public class ShellAuthenticatorIT_SimpleSuite extends 
SharedMiniClusterBase {
   }
 
   @Test
-  public void testClientPropertiesFile() throws IOException {
-    shell = new Shell(reader);
+  public void testClientPropertiesFile() throws Exception {
+    shell = new TestShell(reader);
     shell.setLogErrorsToConsole();
-    assertTrue(shell.config("--config-file", 
getCluster().getClientPropsPath()));
+    shell.execute(new String[] {"-u", getAdminPrincipal(), "--password", 
getRootPassword(),
+        "--config-file", getCluster().getClientPropsPath()});
+    assertTrue(shell.getExitCode() == 0);
   }
 
   @Test
-  public void testClientProperties() throws IOException {
-    shell = new Shell(reader);
+  public void testClientProperties() throws Exception {
+    shell = new TestShell(reader);
     shell.setLogErrorsToConsole();
-    assertTrue(shell.config("-u", getAdminPrincipal(), "-p", 
getRootPassword(), "-zi",
-        getCluster().getInstanceName(), "-zh", getCluster().getZooKeepers()));
+    shell.execute(new String[] {"-u", getAdminPrincipal(), "--password", 
getRootPassword(),
+        "--config-file", getCluster().getClientPropsPath()});
+    assertTrue(shell.getExitCode() == 0);
   }
 
   @Test
-  public void testClientPropertiesBadPassword() throws IOException {
-    shell = new Shell(reader);
+  public void testClientPropertiesBadPassword() throws Exception {
+    shell = new TestShell(reader);
     shell.setLogErrorsToConsole();
-    assertFalse(shell.config("-u", getAdminPrincipal(), "-p", "BADPW", "-zi",
-        getCluster().getInstanceName(), "-zh", getCluster().getZooKeepers()));
+    shell.execute(new String[] {"-u", getAdminPrincipal(), "--password", 
"BADPW", "--config-file",
+        getCluster().getClientPropsPath()});
+    assertTrue(shell.getExitCode() != 0);
   }
 
   @Test
-  public void testAuthTimeoutPropertiesFile() throws IOException, 
InterruptedException {
+  public void testAuthTimeoutPropertiesFile() throws Exception {
     TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
     output = new TestOutputStream();
     input = new StringInputStream();
     terminal = new DumbTerminal(input, output);
     terminal.setSize(new Size(80, 24));
     reader = LineReaderBuilder.builder().terminal(terminal).build();
-    shell = new Shell(reader);
+    shell = new TestShell(reader);
     shell.setLogErrorsToConsole();
-    assertTrue(
-        shell.config("--auth-timeout", "1", "--config-file", 
getCluster().getClientPropsPath()));
+    shell.execute(new String[] {"-u", getAdminPrincipal(), "--password", 
getRootPassword(),
+        "--auth-timeout", "1", "--config-file", 
getCluster().getClientPropsPath()});
+    assertTrue(shell.getExitCode() == 0);
     Thread.sleep(90000);
     shell.execCommand("whoami", false, false);
     assertTrue(output.get().contains("root"));
diff --git a/test/src/main/java/org/apache/accumulo/test/shell/ShellIT.java 
b/test/src/main/java/org/apache/accumulo/test/shell/ShellIT.java
index 40f5ce2877..e7ba701190 100644
--- a/test/src/main/java/org/apache/accumulo/test/shell/ShellIT.java
+++ b/test/src/main/java/org/apache/accumulo/test/shell/ShellIT.java
@@ -28,7 +28,6 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.PrintWriter;
-import java.nio.file.Files;
 import java.nio.file.Path;
 import java.time.Duration;
 import java.util.ArrayList;
@@ -43,6 +42,7 @@ import org.apache.accumulo.core.data.TableId;
 import org.apache.accumulo.harness.SharedMiniClusterBase;
 import org.apache.accumulo.server.conf.TableConfiguration;
 import org.apache.accumulo.shell.Shell;
+import org.apache.accumulo.test.shell.MockShell.TestShell;
 import org.jline.reader.LineReader;
 import org.jline.reader.LineReaderBuilder;
 import org.jline.terminal.Size;
@@ -55,8 +55,6 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 @Tag(MINI_CLUSTER_ONLY)
 public class ShellIT extends SharedMiniClusterBase {
@@ -76,8 +74,6 @@ public class ShellIT extends SharedMiniClusterBase {
     SharedMiniClusterBase.stopMiniCluster();
   }
 
-  private static final Logger log = LoggerFactory.getLogger(ShellIT.class);
-
   public static class TestOutputStream extends OutputStream {
     StringBuilder sb = new StringBuilder();
 
@@ -117,7 +113,7 @@ public class ShellIT extends SharedMiniClusterBase {
   private StringInputStream input;
   private TestOutputStream output;
   private Shell shell;
-  private Path config;
+  private String config;
   private LineReader reader;
   private Terminal terminal;
 
@@ -165,27 +161,22 @@ public class ShellIT extends SharedMiniClusterBase {
   }
 
   @BeforeEach
-  public void setupShell() throws IOException {
+  public void setupShell() throws Exception {
     TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
     output = new TestOutputStream();
     input = new StringInputStream();
-    config = Files.createTempFile(null, null);
+    config = getCluster().getClientPropsPath();
     terminal = new DumbTerminal(input, output);
     terminal.setSize(new Size(80, 24));
     reader = LineReaderBuilder.builder().terminal(terminal).build();
-    shell = new Shell(reader);
+    shell = new TestShell(reader);
     shell.setLogErrorsToConsole();
-    shell.config("--config-file", config.toString(), "-u", "root", "-p", 
getRootPassword(), "-zi",
-        getCluster().getInstanceName(), "-zh", getCluster().getZooKeepers());
+    shell.execute(
+        new String[] {"--config-file", config.toString(), "-u", "root", "-p", 
getRootPassword()});
   }
 
   @AfterEach
   public void teardownShell() {
-    try {
-      Files.deleteIfExists(config);
-    } catch (IOException e) {
-      log.error("Unable to delete {}", config, e);
-    }
     shell.shutdown();
   }
 
@@ -627,11 +618,10 @@ public class ShellIT extends SharedMiniClusterBase {
   }
 
   @Test
-  public void execFileTest() throws IOException {
+  public void execFileTest() throws Exception {
     Shell.log.debug("Starting exec file test --------------------------");
-    shell.config("--config-file", config.toString(), "-u", "root", "-p", 
getRootPassword(), "-zi",
-        getCluster().getInstanceName(), "-zh", getCluster().getZooKeepers(), 
"-f",
-        "src/main/resources/org/apache/accumulo/test/shellit.shellit");
+    shell.execute(new String[] {"--config-file", config.toString(), "-u", 
"root", "-p",
+        getRootPassword(), "-f", 
"src/main/resources/org/apache/accumulo/test/shellit.shellit"});
     assertEquals(0, shell.start());
     assertGoodExit("Unknown command", false);
   }
diff --git 
a/test/src/main/java/org/apache/accumulo/test/start/CommandGroupsIT.java 
b/test/src/main/java/org/apache/accumulo/test/start/CommandGroupsIT.java
new file mode 100644
index 0000000000..d0813aa03d
--- /dev/null
+++ b/test/src/main/java/org/apache/accumulo/test/start/CommandGroupsIT.java
@@ -0,0 +1,46 @@
+/*
+ * 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
+ *
+ *   https://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.accumulo.test.start;
+
+import static org.apache.accumulo.harness.AccumuloITBase.SUNNY_DAY;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Set;
+
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+@Tag(SUNNY_DAY)
+public class CommandGroupsIT {
+
+  @Test
+  public void testCommandGroups() {
+    Set<CommandGroup> groups = CommandGroups.getGroups();
+    assertEquals(6, groups.size());
+    assertTrue(groups.contains(CommandGroups.ADMIN));
+    assertTrue(groups.contains(CommandGroups.CLIENT));
+    assertTrue(groups.contains(CommandGroups.COMPACTION));
+    assertTrue(groups.contains(CommandGroups.CORE));
+    assertTrue(groups.contains(CommandGroups.OTHER));
+    assertTrue(groups.contains(CommandGroups.PROCESS));
+  }
+}
diff --git 
a/test/src/main/java/org/apache/accumulo/test/start/KeywordStartIT.java 
b/test/src/main/java/org/apache/accumulo/test/start/KeywordStartIT.java
index 6fd9dcc3dd..e4a546dcab 100644
--- a/test/src/main/java/org/apache/accumulo/test/start/KeywordStartIT.java
+++ b/test/src/main/java/org/apache/accumulo/test/start/KeywordStartIT.java
@@ -85,8 +85,9 @@ import 
org.apache.accumulo.server.util.adminCommand.SystemCheck;
 import org.apache.accumulo.server.util.adminCommand.VerifyTabletAssignments;
 import org.apache.accumulo.shell.Shell;
 import org.apache.accumulo.start.Main;
+import org.apache.accumulo.start.spi.CommandGroup;
+import org.apache.accumulo.start.spi.CommandGroups;
 import org.apache.accumulo.start.spi.KeywordExecutable;
-import org.apache.accumulo.start.spi.KeywordExecutable.UsageGroup;
 import org.apache.accumulo.tserver.ScanServerExecutable;
 import org.apache.accumulo.tserver.TServerExecutable;
 import org.apache.accumulo.tserver.TabletServer;
@@ -110,7 +111,7 @@ public class KeywordStartIT {
    * There may be other ways to run annotation processors in your IDE, so this 
may not be necessary,
    * depending on your IDE and its configuration.
    */
-  private Map<UsageGroup,Map<String,KeywordExecutable>> 
getKeywordExecutables() {
+  private Map<CommandGroup,Map<String,KeywordExecutable>> 
getKeywordExecutables() {
     var all = Main.getExecutables(ClassLoader.getSystemClassLoader());
     assumeTrue(!all.isEmpty());
     return all;
@@ -131,15 +132,15 @@ public class KeywordStartIT {
     NoOp three = new NoOp("three");
     List<NoOp> services = Arrays.asList(one, three, two, two, three, three, 
anotherOne);
     assertEquals(7, services.size());
-    Map<UsageGroup,Map<String,KeywordExecutable>> results = 
Main.checkDuplicates(services);
-    assertTrue(results.get(UsageGroup.OTHER).containsKey(one.keyword()));
-    
assertTrue(results.get(UsageGroup.OTHER).containsKey(anotherOne.keyword()));
-    assertFalse(results.get(UsageGroup.OTHER).containsKey(two.keyword()));
-    assertFalse(results.get(UsageGroup.OTHER).containsKey(three.keyword()));
-    assertEquals(2, results.get(UsageGroup.OTHER).size());
+    Map<CommandGroup,Map<String,KeywordExecutable>> results = 
Main.checkDuplicates(services);
+    assertTrue(results.get(CommandGroups.OTHER).containsKey(one.keyword()));
+    
assertTrue(results.get(CommandGroups.OTHER).containsKey(anotherOne.keyword()));
+    assertFalse(results.get(CommandGroups.OTHER).containsKey(two.keyword()));
+    assertFalse(results.get(CommandGroups.OTHER).containsKey(three.keyword()));
+    assertEquals(2, results.get(CommandGroups.OTHER).size());
   }
 
-  private record CommandInfo(UsageGroup group, String keyword,
+  private record CommandInfo(CommandGroup group, String keyword,
       Class<? extends KeywordExecutable> clazz) implements 
Comparable<CommandInfo> {
 
     @Override
@@ -164,64 +165,67 @@ public class KeywordStartIT {
   public void testExpectedClasses() {
     
assumeTrue(Files.exists(Path.of(System.getProperty("user.dir")).resolve("src")));
     SortedSet<CommandInfo> expectSet = new TreeSet<>();
-    expectSet.add(new CommandInfo(UsageGroup.ADMIN, "change-secret", 
ChangeSecret.class));
-    expectSet.add(new CommandInfo(UsageGroup.ADMIN, "check", 
SystemCheck.class));
-    expectSet.add(
-        new CommandInfo(UsageGroup.OTHER, "check-compaction-config", 
CheckCompactionConfig.class));
-    expectSet.add(new CommandInfo(UsageGroup.OTHER, 
"check-accumulo-properties",
+    expectSet.add(new CommandInfo(CommandGroups.ADMIN, "change-secret", 
ChangeSecret.class));
+    expectSet.add(new CommandInfo(CommandGroups.ADMIN, "check", 
SystemCheck.class));
+    expectSet.add(new CommandInfo(CommandGroups.OTHER, 
"check-compaction-config",
+        CheckCompactionConfig.class));
+    expectSet.add(new CommandInfo(CommandGroups.OTHER, 
"check-accumulo-properties",
         CheckAccumuloProperties.class));
-    expectSet.add(new CommandInfo(UsageGroup.PROCESS, "compactor", 
CompactorExecutable.class));
-    expectSet.add(new CommandInfo(UsageGroup.OTHER, "create-empty", 
CreateEmpty.class));
-    expectSet.add(new CommandInfo(UsageGroup.OTHER, "create-token", 
CreateToken.class));
-    expectSet.add(new CommandInfo(UsageGroup.ADMIN, "delete-instance", 
DeleteZooInstance.class));
-    expectSet.add(new CommandInfo(UsageGroup.ADMIN, "dump-config", 
DumpConfig.class));
-    expectSet.add(new CommandInfo(UsageGroup.OTHER, "dump-zoo", 
DumpZookeeper.class));
-    expectSet.add(new CommandInfo(UsageGroup.ADMIN, "fate", Fate.class));
-    expectSet.add(new CommandInfo(UsageGroup.PROCESS, "gc", 
GCExecutable.class));
-    expectSet.add(new CommandInfo(UsageGroup.OTHER, "generate-splits", 
GenerateSplits.class));
-    expectSet.add(new CommandInfo(UsageGroup.CORE, "help", Help.class));
-    expectSet.add(new CommandInfo(UsageGroup.CORE, "info", Info.class));
-    expectSet.add(new CommandInfo(UsageGroup.CORE, "init", Initialize.class));
-    expectSet.add(new CommandInfo(UsageGroup.ADMIN, "list-instances", 
ListInstances.class));
-    expectSet.add(new CommandInfo(UsageGroup.ADMIN, "list-volumes", 
ListVolumesUsed.class));
-    expectSet.add(new CommandInfo(UsageGroup.ADMIN, "locks", Locks.class));
-    expectSet.add(new CommandInfo(UsageGroup.OTHER, "login-info", 
LoginProperties.class));
-    expectSet.add(new CommandInfo(UsageGroup.PROCESS, "manager", 
ManagerExecutable.class));
-    expectSet.add(new CommandInfo(UsageGroup.PROCESS, "minicluster", 
MiniClusterExecutable.class));
-    expectSet.add(new CommandInfo(UsageGroup.PROCESS, "monitor", 
MonitorExecutable.class));
-    expectSet.add(new CommandInfo(UsageGroup.ADMIN, "ping", PingServer.class));
-    expectSet.add(new CommandInfo(UsageGroup.ADMIN, "restore-zookeeper", 
RestoreZookeeper.class));
-    expectSet.add(new CommandInfo(UsageGroup.OTHER, "rfile-info", 
PrintInfo.class));
-    expectSet.add(new CommandInfo(UsageGroup.ADMIN, "service-status", 
ServiceStatus.class));
-    expectSet.add(new CommandInfo(UsageGroup.CORE, "shell", Shell.class));
-    expectSet.add(new CommandInfo(UsageGroup.OTHER, "split-large", 
SplitLarge.class));
-    expectSet.add(new CommandInfo(UsageGroup.PROCESS, "sserver", 
ScanServerExecutable.class));
-    expectSet.add(new CommandInfo(UsageGroup.ADMIN, "stop-all", 
StopAll.class));
-    expectSet.add(new CommandInfo(UsageGroup.ADMIN, "stop-manager", 
StopManager.class));
-    expectSet.add(new CommandInfo(UsageGroup.ADMIN, "stop-servers", 
StopServers.class));
-    expectSet.add(new CommandInfo(UsageGroup.PROCESS, "tserver", 
TServerExecutable.class));
-    expectSet.add(new CommandInfo(UsageGroup.OTHER, "upgrade", 
UpgradeUtil.class));
-    expectSet.add(new CommandInfo(UsageGroup.ADMIN, 
"verify-tablet-assignments",
+    expectSet.add(new CommandInfo(CommandGroups.PROCESS, "compactor", 
CompactorExecutable.class));
+    expectSet.add(new CommandInfo(CommandGroups.OTHER, "create-empty", 
CreateEmpty.class));
+    expectSet.add(new CommandInfo(CommandGroups.OTHER, "create-token", 
CreateToken.class));
+    expectSet.add(new CommandInfo(CommandGroups.ADMIN, "delete-instance", 
DeleteZooInstance.class));
+    expectSet.add(new CommandInfo(CommandGroups.ADMIN, "dump-config", 
DumpConfig.class));
+    expectSet.add(new CommandInfo(CommandGroups.OTHER, "dump-zoo", 
DumpZookeeper.class));
+    expectSet.add(new CommandInfo(CommandGroups.ADMIN, "fate", Fate.class));
+    expectSet.add(new CommandInfo(CommandGroups.PROCESS, "gc", 
GCExecutable.class));
+    expectSet.add(new CommandInfo(CommandGroups.OTHER, "generate-splits", 
GenerateSplits.class));
+    expectSet.add(new CommandInfo(CommandGroups.CLIENT, "help", Help.class));
+    expectSet.add(new CommandInfo(CommandGroups.CORE, "info", Info.class));
+    expectSet.add(new CommandInfo(CommandGroups.CORE, "init", 
Initialize.class));
+    expectSet.add(new CommandInfo(CommandGroups.ADMIN, "list-instances", 
ListInstances.class));
+    expectSet.add(new CommandInfo(CommandGroups.ADMIN, "list-volumes", 
ListVolumesUsed.class));
+    expectSet.add(new CommandInfo(CommandGroups.ADMIN, "locks", Locks.class));
+    expectSet.add(new CommandInfo(CommandGroups.OTHER, "login-info", 
LoginProperties.class));
+    expectSet.add(new CommandInfo(CommandGroups.PROCESS, "manager", 
ManagerExecutable.class));
+    expectSet
+        .add(new CommandInfo(CommandGroups.PROCESS, "minicluster", 
MiniClusterExecutable.class));
+    expectSet.add(new CommandInfo(CommandGroups.PROCESS, "monitor", 
MonitorExecutable.class));
+    expectSet.add(new CommandInfo(CommandGroups.ADMIN, "ping", 
PingServer.class));
+    expectSet
+        .add(new CommandInfo(CommandGroups.ADMIN, "restore-zookeeper", 
RestoreZookeeper.class));
+    expectSet.add(new CommandInfo(CommandGroups.OTHER, "rfile-info", 
PrintInfo.class));
+    expectSet.add(new CommandInfo(CommandGroups.ADMIN, "service-status", 
ServiceStatus.class));
+    expectSet.add(new CommandInfo(CommandGroups.CLIENT, "shell", Shell.class));
+    expectSet.add(new CommandInfo(CommandGroups.OTHER, "split-large", 
SplitLarge.class));
+    expectSet.add(new CommandInfo(CommandGroups.PROCESS, "sserver", 
ScanServerExecutable.class));
+    expectSet.add(new CommandInfo(CommandGroups.ADMIN, "stop-all", 
StopAll.class));
+    expectSet.add(new CommandInfo(CommandGroups.ADMIN, "stop-manager", 
StopManager.class));
+    expectSet.add(new CommandInfo(CommandGroups.ADMIN, "stop-servers", 
StopServers.class));
+    expectSet.add(new CommandInfo(CommandGroups.PROCESS, "tserver", 
TServerExecutable.class));
+    expectSet.add(new CommandInfo(CommandGroups.OTHER, "upgrade", 
UpgradeUtil.class));
+    expectSet.add(new CommandInfo(CommandGroups.ADMIN, 
"verify-tablet-assignments",
         VerifyTabletAssignments.class));
-    expectSet.add(new CommandInfo(UsageGroup.CORE, "version", Version.class));
-    expectSet.add(new CommandInfo(UsageGroup.OTHER, "wal-info", 
LogReader.class));
-    expectSet.add(new CommandInfo(UsageGroup.OTHER, "zoo-info-viewer", 
ZooInfoViewer.class));
-    expectSet.add(new CommandInfo(UsageGroup.OTHER, "zoo-prop-editor", 
ZooPropEditor.class));
-    expectSet.add(new CommandInfo(UsageGroup.OTHER, "zoo-zap", ZooZap.class));
-    expectSet.add(new CommandInfo(UsageGroup.PROCESS, "zookeeper", 
ZooKeeperMain.class));
-    expectSet.add(new CommandInfo(UsageGroup.COMPACTION, "cancel", 
CancelCompaction.class));
-    expectSet.add(new CommandInfo(UsageGroup.PROCESS, "list-compactors", 
ListCompactors.class));
-    expectSet.add(new CommandInfo(UsageGroup.COMPACTION, "list", 
ListCompactions.class));
-    expectSet.add(new CommandInfo(UsageGroup.COMPACTION, "find", 
FindCompactionTmpFiles.class));
+    expectSet.add(new CommandInfo(CommandGroups.CLIENT, "version", 
Version.class));
+    expectSet.add(new CommandInfo(CommandGroups.OTHER, "wal-info", 
LogReader.class));
+    expectSet.add(new CommandInfo(CommandGroups.OTHER, "zoo-info-viewer", 
ZooInfoViewer.class));
+    expectSet.add(new CommandInfo(CommandGroups.OTHER, "zoo-prop-editor", 
ZooPropEditor.class));
+    expectSet.add(new CommandInfo(CommandGroups.OTHER, "zoo-zap", 
ZooZap.class));
+    expectSet.add(new CommandInfo(CommandGroups.PROCESS, "zookeeper", 
ZooKeeperMain.class));
+    expectSet.add(new CommandInfo(CommandGroups.COMPACTION, "cancel", 
CancelCompaction.class));
+    expectSet.add(new CommandInfo(CommandGroups.PROCESS, "list-compactors", 
ListCompactors.class));
+    expectSet.add(new CommandInfo(CommandGroups.COMPACTION, "list", 
ListCompactions.class));
+    expectSet.add(new CommandInfo(CommandGroups.COMPACTION, "find", 
FindCompactionTmpFiles.class));
+    System.out.println("Expected: " + expectSet);
 
-    Map<UsageGroup,Map<String,KeywordExecutable>> actualExecutables =
-        new TreeMap<>(getKeywordExecutables());
+    Map<CommandGroup,Map<String,KeywordExecutable>> actualExecutables = 
getKeywordExecutables();
     SortedSet<CommandInfo> actualSet = new TreeSet<>();
     actualExecutables.entrySet().forEach((e) -> {
       e.getValue().entrySet().forEach((e2) -> {
         actualSet.add(new CommandInfo(e.getKey(), e2.getKey(), 
e2.getValue().getClass()));
       });
     });
+    System.out.println("Actuals: " + actualSet);
 
     Iterator<CommandInfo> expectIter = expectSet.iterator();
     Iterator<CommandInfo> actualIter = actualSet.iterator();
@@ -231,9 +235,11 @@ public class KeywordStartIT {
     while (expectIter.hasNext() && actualIter.hasNext()) {
       expected = expectIter.next();
       actual = actualIter.next();
-      assertEquals(expected.group(), actual.group());
-      assertEquals(expected.keyword(), actual.keyword());
-      assertEquals(expected.clazz(), actual.clazz());
+      assertEquals(expected.clazz(), actual.clazz(), "Class is not expected");
+      assertEquals(expected.group(), actual.group(),
+          "Usage group for class is not expected: " + expected.clazz());
+      assertEquals(expected.keyword(), actual.keyword(),
+          "Keyword for class is not expected: " + expected.clazz());
     }
     boolean moreExpected = expectIter.hasNext();
     if (moreExpected) {
@@ -292,7 +298,7 @@ public class KeywordStartIT {
         c -> assertTrue(hasMain(c), "Class " + c.getName() + " is missing a 
main method!"));
 
     // build a list of all classed that implement KeywordExecutable
-    Map<UsageGroup,Map<String,KeywordExecutable>> actualExecutables =
+    Map<CommandGroup,Map<String,KeywordExecutable>> actualExecutables =
         new TreeMap<>(getKeywordExecutables());
     Set<Class<? extends KeywordExecutable>> actualSet = new HashSet<>();
     actualExecutables.entrySet().forEach((e) -> {
@@ -340,6 +346,11 @@ public class KeywordStartIT {
       return kw;
     }
 
+    @Override
+    public CommandGroup commandGroup() {
+      return CommandGroups.OTHER;
+    }
+
     @Override
     public void execute(String[] args) {}
 

Reply via email to