You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2023/12/23 07:59:07 UTC

(camel) branch main updated: CAMEL-20270: Introduce JBang plugins (#12583)

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

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


The following commit(s) were added to refs/heads/main by this push:
     new d5c0ea0ee10 CAMEL-20270: Introduce JBang plugins (#12583)
d5c0ea0ee10 is described below

commit d5c0ea0ee10b243c34ad5d12e4b718718992a88a
Author: Christoph Deppisch <cd...@redhat.com>
AuthorDate: Sat Dec 23 08:59:01 2023 +0100

    CAMEL-20270: Introduce JBang plugins (#12583)
    
    - Introduce plugin mechanism to modularize Camel JBang
    - Add new plugin sub-commands (add, get, delete) to manage plugins
    - Plugin configuration is stored to user home directory
    - Load plugin commands on demand
    - Create 1st plugin for Camel K and move commands into separate Maven module camel-jbang-plugin-k
---
 dsl/camel-jbang/camel-jbang-core/pom.xml           |  20 --
 .../dsl/jbang/core/commands/CamelJBangMain.java    |  23 ++-
 .../dsl/jbang/core/commands/plugin/PluginAdd.java  | 112 +++++++++++
 .../PluginBaseCommand.java}                        |  32 +--
 .../KubeCommand.java => plugin/PluginCommand.java} |  16 +-
 .../KubeCommand.java => plugin/PluginDelete.java}  |  31 ++-
 .../dsl/jbang/core/commands/plugin/PluginGet.java  | 103 ++++++++++
 .../CamelJBangPlugin.java}                         |  20 +-
 .../k/TraitProfile.java => common/Plugin.java}     |  18 +-
 .../camel/dsl/jbang/core/common/PluginHelper.java  | 222 +++++++++++++++++++++
 .../camel/dsl/jbang/core/common/PluginType.java    |  57 ++++++
 .../jbang/core/commands/plugin/PluginAddTest.java  | 111 +++++++++++
 .../core/commands/plugin/PluginDeleteTest.java     |  63 ++++++
 .../jbang/core/commands/plugin/PluginGetTest.java  | 133 ++++++++++++
 dsl/camel-jbang/camel-jbang-plugin-k/pom.xml       |  77 +++++++
 .../camel/camel-jbang-plugin/camel-jbang-plugin-k  |   2 +
 .../jbang/core/commands/k/CompressionHelper.java   |   0
 .../jbang/core/commands/k/IntegrationDelete.java   |   0
 .../dsl/jbang/core/commands/k/IntegrationGet.java  |   0
 .../dsl/jbang/core/commands/k/IntegrationLogs.java |   0
 .../dsl/jbang/core/commands/k/IntegrationRun.java  |   0
 .../dsl/jbang/core/commands/k/KubeBaseCommand.java |   0
 .../dsl/jbang/core/commands/k/KubeCommand.java     |   2 +-
 .../dsl/jbang/core/commands/k/KubePlugin.java}     |  26 +--
 .../jbang/core/commands/k/KubernetesHelper.java    |   0
 .../dsl/jbang/core/commands/k/SourceScheme.java    |   0
 .../dsl/jbang/core/commands/k/TraitHelper.java     |   0
 .../dsl/jbang/core/commands/k/TraitProfile.java    |   0
 .../dsl/jbang/core/commands/StringPrinter.java     |  78 ++++++++
 .../core/commands/k/IntegrationDeleteTest.java     |   0
 .../jbang/core/commands/k/IntegrationGetTest.java  |   0
 .../jbang/core/commands/k/IntegrationLogsTest.java |   0
 .../jbang/core/commands/k/IntegrationRunTest.java  |   0
 .../dsl/jbang/core/commands/k/KubeBaseTest.java    |   6 +
 .../jbang/core/commands/k/KubeCommandMainTest.java |  12 +-
 .../dsl/jbang/core/commands/k/integration.yaml     |   0
 .../src/test/resources/pod.yaml                    |   0
 .../src/test/resources/route.yaml                  |   0
 dsl/camel-jbang/pom.xml                            |   1 +
 39 files changed, 1072 insertions(+), 93 deletions(-)

diff --git a/dsl/camel-jbang/camel-jbang-core/pom.xml b/dsl/camel-jbang/camel-jbang-core/pom.xml
index be32224ebb1..415ade86ff1 100644
--- a/dsl/camel-jbang/camel-jbang-core/pom.xml
+++ b/dsl/camel-jbang/camel-jbang-core/pom.xml
@@ -87,20 +87,6 @@
             <version>${ascii-table-version}</version>
         </dependency>
 
-        <!-- kubernetes -->
-        <dependency>
-            <groupId>io.fabric8</groupId>
-            <artifactId>kubernetes-client</artifactId>
-            <version>${kubernetes-client-version}</version>
-        </dependency>
-
-        <!-- camel k-->
-        <dependency>
-            <groupId>org.apache.camel.k</groupId>
-            <artifactId>camel-k-crds</artifactId>
-            <version>${camel-k-version}</version>
-        </dependency>
-
         <!-- jolokia -->
         <dependency>
             <groupId>org.jolokia</groupId>
@@ -174,12 +160,6 @@
             <artifactId>camel-test-junit5</artifactId>
             <scope>test</scope>
         </dependency>
-        <dependency>
-            <groupId>io.fabric8</groupId>
-            <artifactId>kubernetes-server-mock</artifactId>
-            <version>${kubernetes-client-version}</version>
-            <scope>test</scope>
-        </dependency>
 
     </dependencies>
 
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
index 5933943ce37..f66f17b4ebe 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
@@ -33,17 +33,17 @@ import org.apache.camel.dsl.jbang.core.commands.config.ConfigGet;
 import org.apache.camel.dsl.jbang.core.commands.config.ConfigList;
 import org.apache.camel.dsl.jbang.core.commands.config.ConfigSet;
 import org.apache.camel.dsl.jbang.core.commands.config.ConfigUnset;
-import org.apache.camel.dsl.jbang.core.commands.k.IntegrationDelete;
-import org.apache.camel.dsl.jbang.core.commands.k.IntegrationGet;
-import org.apache.camel.dsl.jbang.core.commands.k.IntegrationLogs;
-import org.apache.camel.dsl.jbang.core.commands.k.IntegrationRun;
-import org.apache.camel.dsl.jbang.core.commands.k.KubeCommand;
+import org.apache.camel.dsl.jbang.core.commands.plugin.PluginAdd;
+import org.apache.camel.dsl.jbang.core.commands.plugin.PluginCommand;
+import org.apache.camel.dsl.jbang.core.commands.plugin.PluginDelete;
+import org.apache.camel.dsl.jbang.core.commands.plugin.PluginGet;
 import org.apache.camel.dsl.jbang.core.commands.process.*;
 import org.apache.camel.dsl.jbang.core.commands.version.VersionCommand;
 import org.apache.camel.dsl.jbang.core.commands.version.VersionGet;
 import org.apache.camel.dsl.jbang.core.commands.version.VersionList;
 import org.apache.camel.dsl.jbang.core.commands.version.VersionSet;
 import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
+import org.apache.camel.dsl.jbang.core.common.PluginHelper;
 import org.apache.camel.dsl.jbang.core.common.Printer;
 import picocli.CommandLine;
 import picocli.CommandLine.Command;
@@ -129,16 +129,17 @@ public class CamelJBangMain implements Callable<Integer> {
                         .addSubcommand("get", new CommandLine(new ConfigGet(main)))
                         .addSubcommand("unset", new CommandLine(new ConfigUnset(main)))
                         .addSubcommand("set", new CommandLine(new ConfigSet(main))))
-                .addSubcommand("k", new CommandLine(new KubeCommand(main))
-                        .addSubcommand("get", new CommandLine(new IntegrationGet(main)))
-                        .addSubcommand("run", new CommandLine(new IntegrationRun(main)))
-                        .addSubcommand("delete", new CommandLine(new IntegrationDelete(main)))
-                        .addSubcommand("logs", new CommandLine(new IntegrationLogs(main))))
+                .addSubcommand("plugin", new CommandLine(new PluginCommand(main))
+                        .addSubcommand("get", new CommandLine(new PluginGet(main)))
+                        .addSubcommand("add", new CommandLine(new PluginAdd(main)))
+                        .addSubcommand("delete", new CommandLine(new PluginDelete(main))))
                 .addSubcommand("version", new CommandLine(new VersionCommand(main))
                         .addSubcommand("get", new CommandLine(new VersionGet(main)))
                         .addSubcommand("set", new CommandLine(new VersionSet(main)))
                         .addSubcommand("list", new CommandLine(new VersionList(main))));
 
+        PluginHelper.addPlugins(commandLine, main);
+
         commandLine.getCommandSpec().versionProvider(() -> {
             CamelCatalog catalog = new DefaultCamelCatalog();
             String v = catalog.getCatalogVersion();
@@ -156,7 +157,7 @@ public class CamelJBangMain implements Callable<Integer> {
      *
      * @param exitCode
      */
-    protected void quit(int exitCode) {
+    public void quit(int exitCode) {
         System.exit(exitCode);
     }
 
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginAdd.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginAdd.java
new file mode 100644
index 00000000000..50a67124a00
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginAdd.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.plugin;
+
+import java.util.Optional;
+
+import org.apache.camel.catalog.CamelCatalog;
+import org.apache.camel.catalog.DefaultCamelCatalog;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.PluginType;
+import org.apache.camel.util.json.JsonObject;
+import picocli.CommandLine;
+
+@CommandLine.Command(name = "add",
+                     description = "Add new plugin.")
+public class PluginAdd extends PluginBaseCommand {
+
+    @CommandLine.Parameters(description = "The Camel plugin to add.",
+                            paramLabel = "<name>")
+    String name;
+
+    @CommandLine.Option(names = { "--command", "-c" },
+                        description = "The command that the plugin uses.")
+    String command;
+
+    @CommandLine.Option(names = { "--description", "-d" },
+                        description = "A short description of the plugin.")
+    String description;
+
+    @CommandLine.Option(names = { "--artifactId", "-a" },
+                        description = "Maven artifactId.")
+    String artifactId;
+
+    @CommandLine.Option(names = { "--groupId", "-g" },
+                        defaultValue = "org.apache.camel",
+                        description = "Maven groupId.")
+    String groupId = "org.apache.camel";
+
+    @CommandLine.Option(names = { "--version", "-v" },
+                        defaultValue = "${camel-version}",
+                        description = "Maven artifact version.")
+    String version;
+
+    @CommandLine.Option(names = { "--gav" },
+                        description = "Maven group and artifact coordinates.")
+    String gav;
+
+    public PluginAdd(CamelJBangMain main) {
+        super(main);
+    }
+
+    @Override
+    public Integer doCall() throws Exception {
+        JsonObject pluginConfig = loadConfig();
+        JsonObject plugins = pluginConfig.getMap("plugins");
+
+        Optional<PluginType> camelPlugin = PluginType.findByName(name);
+        if (camelPlugin.isPresent()) {
+            if (command == null) {
+                command = camelPlugin.get().getCommand();
+            }
+
+            if (description == null) {
+                description = camelPlugin.get().getDescription();
+            }
+        }
+
+        if (command == null) {
+            // use plugin name as command
+            command = name;
+        }
+
+        JsonObject plugin = new JsonObject();
+        plugin.put("name", name);
+        plugin.put("command", command);
+        plugin.put("description",
+                description != null ? description : "Plugin %s called with command %s".formatted(name, command));
+
+        if (gav == null && (groupId != null && artifactId != null)) {
+            if (version == null) {
+                CamelCatalog catalog = new DefaultCamelCatalog();
+                version = catalog.getCatalogVersion();
+            }
+
+            gav = "%s:%s:%s".formatted(groupId, artifactId, version);
+        }
+
+        if (gav != null) {
+            plugin.put("dependency", gav);
+        }
+
+        plugins.put(name, plugin);
+
+        saveConfig(pluginConfig);
+        return 0;
+    }
+
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommand.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginBaseCommand.java
similarity index 53%
copy from dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommand.java
copy to dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginBaseCommand.java
index 8bc5105ceaa..da4ed6c635b 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommand.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginBaseCommand.java
@@ -14,27 +14,29 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.dsl.jbang.core.commands.k;
 
-import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
-import picocli.CommandLine;
+package org.apache.camel.dsl.jbang.core.commands.plugin;
 
-@CommandLine.Command(name = "k",
-                     description = "Manage Camel integrations on Kubernetes (use config --help to see sub commands)")
-public class KubeCommand extends KubeBaseCommand {
+import org.apache.camel.dsl.jbang.core.commands.CamelCommand;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.PluginHelper;
+import org.apache.camel.util.json.JsonObject;
 
-    public static final String OPERATOR_ID_LABEL = "camel.apache.org/operator.id";
-    public static final String INTEGRATION_LABEL = "camel.apache.org/integration";
-    public static final String INTEGRATION_CONTAINER_NAME = "integration";
+/**
+ * Base command supports Kubernetes client related options such as namespace or custom kube config option. Automatically
+ * applies the options to the Kubernetes client instance that is being used to run commands.
+ */
+abstract class PluginBaseCommand extends CamelCommand {
 
-    public KubeCommand(CamelJBangMain main) {
+    public PluginBaseCommand(CamelJBangMain main) {
         super(main);
     }
 
-    @Override
-    public Integer doCall() throws Exception {
-        // defaults to list integrations deployed on Kubernetes
-        new CommandLine(new IntegrationGet(getMain())).execute();
-        return 0;
+    JsonObject loadConfig() {
+        return PluginHelper.getOrCreatePluginConfig();
+    }
+
+    void saveConfig(JsonObject plugins) {
+        PluginHelper.savePluginConfig(plugins);
     }
 }
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommand.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginCommand.java
similarity index 63%
copy from dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommand.java
copy to dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginCommand.java
index 8bc5105ceaa..dd8c7451fdc 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommand.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginCommand.java
@@ -14,27 +14,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.dsl.jbang.core.commands.k;
+package org.apache.camel.dsl.jbang.core.commands.plugin;
 
 import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
 import picocli.CommandLine;
 
-@CommandLine.Command(name = "k",
-                     description = "Manage Camel integrations on Kubernetes (use config --help to see sub commands)")
-public class KubeCommand extends KubeBaseCommand {
+@CommandLine.Command(name = "plugin",
+                     description = "Manage plugins that add sub-commands to this CLI.")
+public class PluginCommand extends PluginBaseCommand {
 
-    public static final String OPERATOR_ID_LABEL = "camel.apache.org/operator.id";
-    public static final String INTEGRATION_LABEL = "camel.apache.org/integration";
-    public static final String INTEGRATION_CONTAINER_NAME = "integration";
-
-    public KubeCommand(CamelJBangMain main) {
+    public PluginCommand(CamelJBangMain main) {
         super(main);
     }
 
     @Override
     public Integer doCall() throws Exception {
         // defaults to list integrations deployed on Kubernetes
-        new CommandLine(new IntegrationGet(getMain())).execute();
+        new CommandLine(new PluginGet(getMain())).execute();
         return 0;
     }
 }
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommand.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginDelete.java
similarity index 54%
copy from dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommand.java
copy to dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginDelete.java
index 8bc5105ceaa..f4172bb8f02 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommand.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginDelete.java
@@ -14,27 +14,38 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.dsl.jbang.core.commands.k;
+package org.apache.camel.dsl.jbang.core.commands.plugin;
 
 import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.util.json.JsonObject;
 import picocli.CommandLine;
 
-@CommandLine.Command(name = "k",
-                     description = "Manage Camel integrations on Kubernetes (use config --help to see sub commands)")
-public class KubeCommand extends KubeBaseCommand {
+@CommandLine.Command(name = "delete",
+                     description = "Removes a plugin.")
+public class PluginDelete extends PluginBaseCommand {
 
-    public static final String OPERATOR_ID_LABEL = "camel.apache.org/operator.id";
-    public static final String INTEGRATION_LABEL = "camel.apache.org/integration";
-    public static final String INTEGRATION_CONTAINER_NAME = "integration";
+    @CommandLine.Parameters(description = "The Camel plugin to remove.",
+                            paramLabel = "<plugin>")
+    String name;
 
-    public KubeCommand(CamelJBangMain main) {
+    public PluginDelete(CamelJBangMain main) {
         super(main);
     }
 
     @Override
     public Integer doCall() throws Exception {
-        // defaults to list integrations deployed on Kubernetes
-        new CommandLine(new IntegrationGet(getMain())).execute();
+        JsonObject pluginConfig = loadConfig();
+        JsonObject plugins = pluginConfig.getMap("plugins");
+
+        Object plugin = plugins.remove(name);
+        if (plugin != null) {
+            printer().printf("Plugin %s removed%n", name);
+            saveConfig(pluginConfig);
+        } else {
+            printer().printf("Plugin %s not found in configuration%n", name);
+        }
+
         return 0;
     }
+
 }
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGet.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGet.java
new file mode 100644
index 00000000000..8769323b56f
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGet.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.plugin;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.github.freva.asciitable.AsciiTable;
+import com.github.freva.asciitable.Column;
+import com.github.freva.asciitable.HorizontalAlign;
+import com.github.freva.asciitable.OverflowBehaviour;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.PluginType;
+import org.apache.camel.util.json.JsonObject;
+import picocli.CommandLine;
+
+@CommandLine.Command(name = "get",
+                     description = "Display available plugins.")
+public class PluginGet extends PluginBaseCommand {
+
+    @CommandLine.Option(names = { "--all", "-a" }, defaultValue = "false", description = "Display all available plugins.")
+    public boolean all;
+
+    public PluginGet(CamelJBangMain main) {
+        super(main);
+    }
+
+    @Override
+    public Integer doCall() throws Exception {
+        List<Row> rows = new ArrayList<>();
+
+        JsonObject plugins = loadConfig().getMap("plugins");
+        plugins.forEach((key, value) -> {
+            JsonObject details = (JsonObject) value;
+
+            String name = details.getStringOrDefault("name", key);
+            String command = details.getStringOrDefault("command", name);
+            String dependency = details.getStringOrDefault("dependency",
+                    "org.apache.camel:camel-jbang-plugin-%s".formatted(command));
+            String description
+                    = details.getStringOrDefault("description", "Plugin %s called with command %s".formatted(name, command));
+
+            rows.add(new Row(name, command, dependency, description));
+        });
+
+        printRows(rows);
+
+        if (all) {
+            rows.clear();
+            for (PluginType camelPlugin : PluginType.values()) {
+                if (plugins.get(camelPlugin.getName()) == null) {
+                    String dependency = "org.apache.camel:camel-jbang-plugin-%s".formatted(camelPlugin.getCommand());
+                    rows.add(new Row(
+                            camelPlugin.getName(), camelPlugin.getCommand(), dependency,
+                            camelPlugin.getDescription()));
+                }
+            }
+
+            if (!rows.isEmpty()) {
+                printer().println();
+                printer().println("Supported plugins:");
+                printer().println();
+
+                printRows(rows);
+            }
+        }
+
+        return 0;
+    }
+
+    private void printRows(List<Row> rows) {
+        if (!rows.isEmpty()) {
+            printer().println(AsciiTable.getTable(AsciiTable.NO_BORDERS, rows, Arrays.asList(
+                    new Column().header("NAME").headerAlign(HorizontalAlign.LEFT).dataAlign(HorizontalAlign.LEFT)
+                            .with(r -> r.name),
+                    new Column().header("COMMAND").headerAlign(HorizontalAlign.LEFT).dataAlign(HorizontalAlign.LEFT)
+                            .with(r -> r.command),
+                    new Column().header("DEPENDENCY").headerAlign(HorizontalAlign.LEFT).dataAlign(HorizontalAlign.LEFT)
+                            .with(r -> r.dependency),
+                    new Column().header("DESCRIPTION").headerAlign(HorizontalAlign.LEFT).dataAlign(HorizontalAlign.LEFT)
+                            .maxWidth(50, OverflowBehaviour.ELLIPSIS_RIGHT)
+                            .with(r -> r.description))));
+        }
+    }
+
+    private record Row(String name, String command, String dependency, String description) {
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/TraitProfile.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CamelJBangPlugin.java
similarity index 62%
copy from dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/TraitProfile.java
copy to dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CamelJBangPlugin.java
index 4d5e1664b12..95f824b7106 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/TraitProfile.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CamelJBangPlugin.java
@@ -15,11 +15,21 @@
  * limitations under the License.
  */
 
-package org.apache.camel.dsl.jbang.core.commands.k;
+package org.apache.camel.dsl.jbang.core.common;
 
-public enum TraitProfile {
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 
-    OPENSHIFT,
-    KUBERNETES,
-    KNATIVE
+import org.apache.camel.spi.annotations.ServiceFactory;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Target({ ElementType.TYPE })
+@ServiceFactory("camel-jbang-plugin")
+public @interface CamelJBangPlugin {
+
+    String value();
 }
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/TraitProfile.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/Plugin.java
similarity index 64%
copy from dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/TraitProfile.java
copy to dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/Plugin.java
index 4d5e1664b12..3337d7c18f9 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/TraitProfile.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/Plugin.java
@@ -15,11 +15,19 @@
  * limitations under the License.
  */
 
-package org.apache.camel.dsl.jbang.core.commands.k;
+package org.apache.camel.dsl.jbang.core.common;
 
-public enum TraitProfile {
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import picocli.CommandLine;
 
-    OPENSHIFT,
-    KUBERNETES,
-    KNATIVE
+@FunctionalInterface
+public interface Plugin {
+
+    /**
+     * Customize given command line adding sub-commands in particular.
+     *
+     * @param commandLine the command line to adjust.
+     * @param main        the current JBang main.
+     */
+    void customize(CommandLine commandLine, CamelJBangMain main);
 }
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginHelper.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginHelper.java
new file mode 100644
index 00000000000..3b5514ca5d5
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginHelper.java
@@ -0,0 +1,222 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.camel.dsl.jbang.core.common;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.catalog.CamelCatalog;
+import org.apache.camel.catalog.DefaultCamelCatalog;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.impl.engine.DefaultClassResolver;
+import org.apache.camel.impl.engine.DefaultFactoryFinder;
+import org.apache.camel.spi.FactoryFinder;
+import org.apache.camel.util.IOHelper;
+import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
+import picocli.CommandLine;
+
+/**
+ * Helper for command line plugins that add sub-commands and functionality when enabled by the user. Loads and saves
+ * plugin configuration from/to the user home directory.
+ */
+public final class PluginHelper {
+
+    public static final String PLUGIN_CONFIG = ".camel-jbang-plugins.json";
+
+    private static final FactoryFinder FACTORY_FINDER
+            = new DefaultFactoryFinder(new DefaultClassResolver(), FactoryFinder.DEFAULT_PATH + "camel-jbang-plugin/");
+
+    private PluginHelper() {
+        // prevent instantiation of utility class
+    }
+
+    /**
+     * Loads the plugin Json configuration from the user home and goes through all configured plugins adding the plugin
+     * commands to the current command line. Tries to resolve each plugin from the classpath with the factory finder
+     * pattern. If present the plugin is called to customize the command line to add all sub-commands of the plugin.
+     *
+     * @param commandLine the command line to add commands to
+     * @param main        the current Camel JBang main
+     */
+    public static void addPlugins(CommandLine commandLine, CamelJBangMain main) {
+        JsonObject config = getPluginConfig();
+
+        if (config != null) {
+            JsonObject plugins = config.getMap("plugins");
+            for (String pluginKey : plugins.keySet()) {
+                JsonObject properties = plugins.getMap(pluginKey);
+
+                String name = properties.getOrDefault("name", pluginKey).toString();
+                String command = properties.getOrDefault("command", name).toString();
+
+                Optional<Plugin> plugin = FACTORY_FINDER.newInstance("camel-jbang-plugin-" + command, Plugin.class);
+                if (plugin.isPresent()) {
+                    plugin.get().customize(commandLine, main);
+                } else {
+                    String description = properties.getOrDefault("description", "").toString();
+                    String dependency = properties.getOrDefault("dependency",
+                            "org.apache.camel:camel-jbang-plugin-%s:${camel-version}".formatted(command)).toString();
+                    createSubCommand(commandLine, name, command, dependency, description, main);
+                }
+            }
+        }
+    }
+
+    /**
+     * Create sub-command as a placeholder for calling a plugin. When the command gets executed the plugin is added to
+     * the classpath and a new JBang process is spawned with the same arguments. The factory finder mechanism will be
+     * able to resolve the actual plugin from the classpath so the real plugin command is run.
+     *
+     * @param commandLine to receive the new command
+     * @param name        the plugin name
+     * @param command     the plugin command
+     * @param dependency  the Maven dependency for the plugin
+     * @param description optional description of the plugin command
+     * @param main        current Camel JBang main
+     */
+    private static void createSubCommand(
+            CommandLine commandLine, String name, String command,
+            String dependency, String description, CamelJBangMain main) {
+        commandLine.addSubcommand(command, CommandLine.Model.CommandSpec.wrapWithoutInspection(
+                (Runnable) () -> {
+                    List<String> args = commandLine.getParseResult().originalArgs();
+                    if (args.contains("--help") || args.contains("--h")) {
+                        main.getOut().printf("Loading plugin %s for command %s%n", name, command);
+                    }
+
+                    String gav = dependency;
+                    if (gav.endsWith(":${camel-version}")) {
+                        gav = gav.substring(0, gav.length() - "${camel-version}".length()) + getCamelVersion(args);
+                    }
+
+                    // need to use jbang command to call plugin
+                    List<String> jbangArgs = new ArrayList<>();
+                    jbangArgs.add("jbang");
+                    // Add plugin dependency, so it is present on the classpath for the new JBang process
+                    jbangArgs.add("--deps=" + gav);
+
+                    jbangArgs.add("camel");
+                    jbangArgs.addAll(args);
+
+                    try {
+                        ProcessBuilder pb = new ProcessBuilder();
+                        pb.command(jbangArgs);
+
+                        pb.inheritIO(); // run in foreground (with IO so logs are visible)
+                        Process p = pb.start();
+
+                        // wait for that process to exit as we run in foreground
+                        int exitCode = p.waitFor();
+                        main.quit(exitCode);
+                    } catch (IOException | InterruptedException e) {
+                        main.getOut().printf("Unable to spawn JBang process - %s%n", e.getMessage());
+                        main.quit(1);
+                    }
+                })
+                .usageMessage(new CommandLine.Model.UsageMessageSpec().description(description))
+                .addUnmatchedArgsBinding(CommandLine.Model.UnmatchedArgsBinding
+                        .forStringArrayConsumer(new CommandLine.Model.ISetter() {
+                            @Override
+                            public <T> T set(T value) throws Exception {
+                                return value;
+                            }
+                        })));
+    }
+
+    private static String getCamelVersion(List<String> args) {
+        Optional<String> version = args.stream()
+                .filter(arg -> arg.startsWith("--camel.version="))
+                .map(arg -> arg.substring("camel.version=".length()))
+                .findFirst();
+
+        if (version.isPresent()) {
+            return version.get();
+        }
+
+        CamelCatalog catalog = new DefaultCamelCatalog();
+        return catalog.getCatalogVersion();
+    }
+
+    public static JsonObject getOrCreatePluginConfig() {
+        return Optional.ofNullable(getPluginConfig()).orElseGet(PluginHelper::createPluginConfig);
+    }
+
+    private static JsonObject getPluginConfig() {
+        try {
+            File f = new File(CommandLineHelper.getHomeDir(), PLUGIN_CONFIG);
+            if (f.exists()) {
+                try (FileInputStream fis = new FileInputStream(f)) {
+                    String text = IOHelper.loadText(fis);
+                    return (JsonObject) Jsoner.deserialize(text);
+                }
+            }
+        } catch (Exception e) {
+            // ignore
+        }
+
+        return null;
+    }
+
+    public static JsonObject createPluginConfig() {
+        File f = new File(CommandLineHelper.getHomeDir(), PLUGIN_CONFIG);
+        JsonObject config = Jsoner.deserialize("{ \"plugins\": {} }", new JsonObject());
+        try {
+            Files.writeString(f.toPath(), config.toJson(),
+                    StandardOpenOption.CREATE,
+                    StandardOpenOption.WRITE,
+                    StandardOpenOption.TRUNCATE_EXISTING);
+        } catch (IOException e) {
+            throw new RuntimeCamelException("Failed to create plugin configuration", e);
+        }
+
+        return config;
+    }
+
+    public static void savePluginConfig(JsonObject plugins) {
+        File f = new File(CommandLineHelper.getHomeDir(), PLUGIN_CONFIG);
+        try {
+            Files.writeString(f.toPath(), plugins.toJson(),
+                    StandardOpenOption.CREATE,
+                    StandardOpenOption.WRITE,
+                    StandardOpenOption.TRUNCATE_EXISTING);
+        } catch (IOException e) {
+            throw new RuntimeCamelException("Failed to save plugin configuration", e);
+        }
+    }
+
+    public static void enable(PluginType pluginType) {
+        JsonObject pluginConfig = PluginHelper.getOrCreatePluginConfig();
+        JsonObject plugins = pluginConfig.getMap("plugins");
+
+        JsonObject kubePlugin = new JsonObject();
+        kubePlugin.put("name", pluginType.getName());
+        kubePlugin.put("command", pluginType.getCommand());
+        kubePlugin.put("description", pluginType.getDescription());
+        plugins.put(pluginType.getName(), kubePlugin);
+
+        PluginHelper.savePluginConfig(pluginConfig);
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginType.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginType.java
new file mode 100644
index 00000000000..c4389f28cc1
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginType.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.camel.dsl.jbang.core.common;
+
+import java.util.Arrays;
+import java.util.Optional;
+
+/**
+ * Known plugins in the Camel project.
+ */
+public enum PluginType {
+
+    CAMEL_K("camel-k", "k", "Manage Camel integrations on Kubernetes");
+
+    private final String name;
+    private final String command;
+    private final String description;
+
+    PluginType(String name, String command, String description) {
+        this.name = name;
+        this.command = command;
+        this.description = description;
+    }
+
+    public static Optional<PluginType> findByName(String name) {
+        return Arrays.stream(values())
+                .filter(p -> p.name.equalsIgnoreCase(name))
+                .findFirst();
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getCommand() {
+        return command;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginAddTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginAddTest.java
new file mode 100644
index 00000000000..db43bc984e8
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginAddTest.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.camel.dsl.jbang.core.commands.plugin;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelCommandBaseTest;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
+import org.apache.camel.dsl.jbang.core.common.PluginHelper;
+import org.apache.camel.dsl.jbang.core.common.PluginType;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class PluginAddTest extends CamelCommandBaseTest {
+
+    @BeforeEach
+    public void setup() {
+        super.setup();
+
+        CommandLineHelper.useHomeDir("target");
+        PluginHelper.createPluginConfig();
+    }
+
+    @Test
+    public void shouldAddDefaultPlugin() throws Exception {
+        PluginAdd command = new PluginAdd(new CamelJBangMain().withPrinter(printer));
+        command.name = "camel-k";
+        command.doCall();
+
+        Assertions.assertEquals("", printer.getOutput());
+
+        Assertions.assertEquals("{\"plugins\":{\"camel-k\":{\"name\":\"camel-k\",\"command\":\"k\",\"description\":\"%s\"}}}"
+                .formatted(PluginType.CAMEL_K.getDescription()), PluginHelper.getOrCreatePluginConfig().toJson());
+    }
+
+    @Test
+    public void shouldAddPlugin() throws Exception {
+        PluginAdd command = new PluginAdd(new CamelJBangMain().withPrinter(printer));
+        command.name = "foo-plugin";
+        command.command = "foo";
+        command.description = "Some plugin";
+        command.doCall();
+
+        Assertions.assertEquals("", printer.getOutput());
+
+        Assertions.assertEquals("{\"plugins\":{\"foo-plugin\":{\"name\":\"foo-plugin\",\"command\":\"foo\"," +
+                                "\"description\":\"Some plugin\"}}}",
+                PluginHelper.getOrCreatePluginConfig().toJson());
+    }
+
+    @Test
+    public void shouldGenerateProperties() throws Exception {
+        PluginAdd command = new PluginAdd(new CamelJBangMain().withPrinter(printer));
+        command.name = "foo";
+        command.doCall();
+
+        Assertions.assertEquals("", printer.getOutput());
+
+        Assertions.assertEquals("{\"plugins\":{\"foo\":{\"name\":\"foo\",\"command\":\"foo\"," +
+                                "\"description\":\"Plugin foo called with command foo\"}}}",
+                PluginHelper.getOrCreatePluginConfig().toJson());
+    }
+
+    @Test
+    public void shouldUseMavenGAV() throws Exception {
+        PluginAdd command = new PluginAdd(new CamelJBangMain().withPrinter(printer));
+        command.name = "foo-plugin";
+        command.command = "foo";
+        command.gav = "org.apache.camel:foo-plugin:1.0.0";
+        command.doCall();
+
+        Assertions.assertEquals("", printer.getOutput());
+
+        Assertions.assertEquals("{\"plugins\":{\"foo-plugin\":{\"name\":\"foo-plugin\",\"command\":\"foo\"," +
+                                "\"description\":\"Plugin foo-plugin called with command foo\",\"dependency\":\"org.apache.camel:foo-plugin:1.0.0\"}}}",
+                PluginHelper.getOrCreatePluginConfig().toJson());
+    }
+
+    @Test
+    public void shouldUseArtifactIdAndVersion() throws Exception {
+        PluginAdd command = new PluginAdd(new CamelJBangMain().withPrinter(printer));
+        command.name = "foo-plugin";
+        command.command = "foo";
+        command.groupId = "org.foo";
+        command.artifactId = "foo-bar";
+        command.version = "1.0.0";
+        command.doCall();
+
+        Assertions.assertEquals("", printer.getOutput());
+
+        Assertions.assertEquals("{\"plugins\":{\"foo-plugin\":{\"name\":\"foo-plugin\",\"command\":\"foo\"," +
+                                "\"description\":\"Plugin foo-plugin called with command foo\",\"dependency\":\"org.foo:foo-bar:1.0.0\"}}}",
+                PluginHelper.getOrCreatePluginConfig().toJson());
+    }
+
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginDeleteTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginDeleteTest.java
new file mode 100644
index 00000000000..e9377543f2b
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginDeleteTest.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.camel.dsl.jbang.core.commands.plugin;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelCommandBaseTest;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
+import org.apache.camel.dsl.jbang.core.common.PluginHelper;
+import org.apache.camel.dsl.jbang.core.common.PluginType;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class PluginDeleteTest extends CamelCommandBaseTest {
+
+    @BeforeEach
+    public void setup() {
+        super.setup();
+
+        CommandLineHelper.useHomeDir("target");
+        PluginHelper.createPluginConfig();
+    }
+
+    @Test
+    public void shouldDeletePlugin() throws Exception {
+        PluginHelper.enable(PluginType.CAMEL_K);
+
+        PluginDelete command = new PluginDelete(new CamelJBangMain().withPrinter(printer));
+        command.name = "camel-k";
+        command.doCall();
+
+        Assertions.assertEquals("Plugin camel-k removed", printer.getOutput());
+
+        Assertions.assertEquals("{\"plugins\":{}}", PluginHelper.getOrCreatePluginConfig().toJson());
+    }
+
+    @Test
+    public void shouldHandleUnknownPlugin() throws Exception {
+        PluginDelete command = new PluginDelete(new CamelJBangMain().withPrinter(printer));
+        command.name = "foo";
+        command.doCall();
+
+        Assertions.assertEquals("Plugin foo not found in configuration", printer.getOutput());
+
+        Assertions.assertEquals("{\"plugins\":{}}", PluginHelper.getOrCreatePluginConfig().toJson());
+    }
+
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGetTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGetTest.java
new file mode 100644
index 00000000000..38377fa9f4f
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGetTest.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.camel.dsl.jbang.core.commands.plugin;
+
+import java.util.List;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelCommandBaseTest;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
+import org.apache.camel.dsl.jbang.core.common.PluginHelper;
+import org.apache.camel.dsl.jbang.core.common.PluginType;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class PluginGetTest extends CamelCommandBaseTest {
+
+    @BeforeEach
+    public void setup() {
+        super.setup();
+
+        CommandLineHelper.useHomeDir("target");
+        PluginHelper.createPluginConfig();
+    }
+
+    @Test
+    public void shouldGetEmptyPlugins() throws Exception {
+        PluginGet command = new PluginGet(new CamelJBangMain().withPrinter(printer));
+        command.doCall();
+
+        Assertions.assertEquals("", printer.getOutput());
+    }
+
+    @Test
+    public void shouldGetPlugin() throws Exception {
+        PluginHelper.enable(PluginType.CAMEL_K);
+
+        PluginGet command = new PluginGet(new CamelJBangMain().withPrinter(printer));
+        command.doCall();
+
+        List<String> output = printer.getLines();
+        Assertions.assertEquals(2, output.size());
+        Assertions.assertEquals("NAME     COMMAND  DEPENDENCY                             DESCRIPTION", output.get(0));
+        Assertions.assertEquals(
+                "camel-k  k        org.apache.camel:camel-jbang-plugin-k  %s".formatted(PluginType.CAMEL_K.getDescription()),
+                output.get(1));
+    }
+
+    @Test
+    public void shouldGetDefaultPlugins() throws Exception {
+        PluginGet command = new PluginGet(new CamelJBangMain().withPrinter(printer));
+        command.all = true;
+        command.doCall();
+
+        List<String> output = printer.getLines();
+        Assertions.assertEquals(4, output.size());
+        Assertions.assertEquals("Supported plugins:", output.get(0));
+        Assertions.assertEquals("NAME     COMMAND  DEPENDENCY                             DESCRIPTION", output.get(2));
+        Assertions.assertEquals(
+                "camel-k  k        org.apache.camel:camel-jbang-plugin-k  %s".formatted(PluginType.CAMEL_K.getDescription()),
+                output.get(3));
+    }
+
+    @Test
+    public void shouldGenerateDependencyAndDescription() throws Exception {
+        JsonObject pluginConfig = PluginHelper.getOrCreatePluginConfig();
+        JsonObject plugins = pluginConfig.getMap("plugins");
+
+        JsonObject fooPlugin = new JsonObject();
+        fooPlugin.put("name", "foo");
+        fooPlugin.put("command", "foo");
+        plugins.put("foo-plugin", fooPlugin);
+
+        PluginHelper.savePluginConfig(pluginConfig);
+
+        PluginGet command = new PluginGet(new CamelJBangMain().withPrinter(printer));
+        command.doCall();
+
+        List<String> output = printer.getLines();
+        Assertions.assertEquals(2, output.size());
+        Assertions.assertEquals("NAME  COMMAND  DEPENDENCY                               DESCRIPTION", output.get(0));
+        Assertions.assertEquals("foo   foo      org.apache.camel:camel-jbang-plugin-foo  Plugin foo called with command foo",
+                output.get(1));
+    }
+
+    @Test
+    public void shouldGetAllPlugins() throws Exception {
+        JsonObject pluginConfig = PluginHelper.getOrCreatePluginConfig();
+        JsonObject plugins = pluginConfig.getMap("plugins");
+
+        JsonObject fooPlugin = new JsonObject();
+        fooPlugin.put("name", "foo-plugin");
+        fooPlugin.put("command", "foo");
+        fooPlugin.put("dependency", "org.apache.camel:foo-plugin:1.0.0");
+        plugins.put("foo-plugin", fooPlugin);
+
+        PluginHelper.savePluginConfig(pluginConfig);
+
+        PluginGet command = new PluginGet(new CamelJBangMain().withPrinter(printer));
+        command.all = true;
+        command.doCall();
+
+        List<String> output = printer.getLines();
+        Assertions.assertEquals(7, output.size());
+        Assertions.assertEquals("NAME        COMMAND  DEPENDENCY                         DESCRIPTION", output.get(0));
+        Assertions.assertEquals(
+                "foo-plugin  foo      org.apache.camel:foo-plugin:1.0.0  Plugin foo-plugin called with command foo",
+                output.get(1));
+
+        Assertions.assertEquals("Supported plugins:", output.get(3));
+        Assertions.assertEquals("NAME     COMMAND  DEPENDENCY                             DESCRIPTION", output.get(5));
+        Assertions.assertEquals(
+                "camel-k  k        org.apache.camel:camel-jbang-plugin-k  %s".formatted(PluginType.CAMEL_K.getDescription()),
+                output.get(6));
+    }
+
+}
diff --git a/dsl/camel-jbang/camel-jbang-plugin-k/pom.xml b/dsl/camel-jbang/camel-jbang-plugin-k/pom.xml
new file mode 100644
index 00000000000..21301efb514
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-plugin-k/pom.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.camel</groupId>
+        <artifactId>camel-jbang-parent</artifactId>
+        <version>4.4.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>camel-jbang-plugin-k</artifactId>
+
+    <name>Camel :: JBang :: Plugin :: Camel K</name>
+    <description>Camel JBang Camel K Plugin</description>
+
+    <properties>
+        <firstVersion>4.4.0</firstVersion>
+        <label>jbang</label>
+        <supportLevel>Preview</supportLevel>
+        <camel-prepare-component>false</camel-prepare-component>
+    </properties>
+
+    <dependencies>
+        <dependency>
+          <groupId>org.apache.camel</groupId>
+          <artifactId>camel-jbang-core</artifactId>
+        </dependency>
+
+        <!-- kubernetes -->
+        <dependency>
+            <groupId>io.fabric8</groupId>
+            <artifactId>kubernetes-client</artifactId>
+            <version>${kubernetes-client-version}</version>
+        </dependency>
+
+        <!-- camel k-->
+        <dependency>
+            <groupId>org.apache.camel.k</groupId>
+            <artifactId>camel-k-crds</artifactId>
+            <version>${camel-k-version}</version>
+        </dependency>
+
+        <!-- test dependencies -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-test-junit5</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.fabric8</groupId>
+            <artifactId>kubernetes-server-mock</artifactId>
+            <version>${kubernetes-client-version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/dsl/camel-jbang/camel-jbang-plugin-k/src/generated/resources/META-INF/services/org/apache/camel/camel-jbang-plugin/camel-jbang-plugin-k b/dsl/camel-jbang/camel-jbang-plugin-k/src/generated/resources/META-INF/services/org/apache/camel/camel-jbang-plugin/camel-jbang-plugin-k
new file mode 100644
index 00000000000..5a3ee5c3e1b
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-plugin-k/src/generated/resources/META-INF/services/org/apache/camel/camel-jbang-plugin/camel-jbang-plugin-k
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.dsl.jbang.core.commands.k.KubePlugin
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/CompressionHelper.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/CompressionHelper.java
similarity index 100%
rename from dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/CompressionHelper.java
rename to dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/CompressionHelper.java
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationDelete.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationDelete.java
similarity index 100%
rename from dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationDelete.java
rename to dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationDelete.java
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationGet.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationGet.java
similarity index 100%
rename from dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationGet.java
rename to dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationGet.java
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationLogs.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationLogs.java
similarity index 100%
rename from dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationLogs.java
rename to dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationLogs.java
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRun.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRun.java
similarity index 100%
rename from dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRun.java
rename to dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRun.java
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeBaseCommand.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeBaseCommand.java
similarity index 100%
rename from dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeBaseCommand.java
rename to dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeBaseCommand.java
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommand.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommand.java
similarity index 96%
copy from dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommand.java
copy to dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommand.java
index 8bc5105ceaa..4bfc5202f74 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommand.java
+++ b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommand.java
@@ -20,7 +20,7 @@ import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
 import picocli.CommandLine;
 
 @CommandLine.Command(name = "k",
-                     description = "Manage Camel integrations on Kubernetes (use config --help to see sub commands)")
+                     description = "Manage Camel integrations on Kubernetes (use k --help to see sub commands)")
 public class KubeCommand extends KubeBaseCommand {
 
     public static final String OPERATOR_ID_LABEL = "camel.apache.org/operator.id";
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommand.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubePlugin.java
similarity index 57%
rename from dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommand.java
rename to dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubePlugin.java
index 8bc5105ceaa..b344ee6e4e7 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommand.java
+++ b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubePlugin.java
@@ -14,27 +14,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.apache.camel.dsl.jbang.core.commands.k;
 
 import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.CamelJBangPlugin;
+import org.apache.camel.dsl.jbang.core.common.Plugin;
 import picocli.CommandLine;
 
-@CommandLine.Command(name = "k",
-                     description = "Manage Camel integrations on Kubernetes (use config --help to see sub commands)")
-public class KubeCommand extends KubeBaseCommand {
-
-    public static final String OPERATOR_ID_LABEL = "camel.apache.org/operator.id";
-    public static final String INTEGRATION_LABEL = "camel.apache.org/integration";
-    public static final String INTEGRATION_CONTAINER_NAME = "integration";
-
-    public KubeCommand(CamelJBangMain main) {
-        super(main);
-    }
+@CamelJBangPlugin("camel-jbang-plugin-k")
+public class KubePlugin implements Plugin {
 
     @Override
-    public Integer doCall() throws Exception {
-        // defaults to list integrations deployed on Kubernetes
-        new CommandLine(new IntegrationGet(getMain())).execute();
-        return 0;
+    public void customize(CommandLine commandLine, CamelJBangMain main) {
+        commandLine.addSubcommand("k", new picocli.CommandLine(new KubeCommand(main))
+                .addSubcommand("get", new picocli.CommandLine(new IntegrationGet(main)))
+                .addSubcommand("run", new picocli.CommandLine(new IntegrationRun(main)))
+                .addSubcommand("delete", new picocli.CommandLine(new IntegrationDelete(main)))
+                .addSubcommand("logs", new picocli.CommandLine(new IntegrationLogs(main))));
     }
 }
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubernetesHelper.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubernetesHelper.java
similarity index 100%
rename from dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubernetesHelper.java
rename to dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubernetesHelper.java
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/SourceScheme.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/SourceScheme.java
similarity index 100%
rename from dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/SourceScheme.java
rename to dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/SourceScheme.java
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/TraitHelper.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/TraitHelper.java
similarity index 100%
rename from dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/TraitHelper.java
rename to dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/TraitHelper.java
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/TraitProfile.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/TraitProfile.java
similarity index 100%
rename from dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/TraitProfile.java
rename to dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/TraitProfile.java
diff --git a/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/StringPrinter.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/StringPrinter.java
new file mode 100644
index 00000000000..0c44f197089
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/StringPrinter.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.camel.dsl.jbang.core.commands;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.camel.dsl.jbang.core.common.Printer;
+
+public class StringPrinter implements Printer {
+
+    private final StringWriter writer = new StringWriter();
+
+    @Override
+    public void println() {
+        writer.write(System.lineSeparator());
+    }
+
+    @Override
+    public void println(String line) {
+        printf("%s%n", line);
+    }
+
+    @Override
+    public void print(String output) {
+        writer.write(output);
+    }
+
+    @Override
+    public void printf(String format, Object... args) {
+        writer.write(format.formatted(args));
+    }
+
+    /**
+     * Provides access to the cached output.
+     *
+     * @return
+     */
+    public String getOutput() {
+        return writer.toString().trim();
+    }
+
+    /**
+     * Provides access to all lines of the cached output.
+     *
+     * @return
+     * @throws IOException
+     */
+    public List<String> getLines() throws IOException {
+        BufferedReader buf = new BufferedReader(new StringReader(getOutput()));
+        List<String> lines = new ArrayList<>();
+        String line;
+        while ((line = buf.readLine()) != null) {
+            lines.add(line.trim());
+        }
+
+        return lines;
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationDeleteTest.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationDeleteTest.java
similarity index 100%
rename from dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationDeleteTest.java
rename to dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationDeleteTest.java
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationGetTest.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationGetTest.java
similarity index 100%
rename from dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationGetTest.java
rename to dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationGetTest.java
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationLogsTest.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationLogsTest.java
similarity index 100%
rename from dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationLogsTest.java
rename to dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationLogsTest.java
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRunTest.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRunTest.java
similarity index 100%
rename from dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRunTest.java
rename to dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRunTest.java
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/KubeBaseTest.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/KubeBaseTest.java
similarity index 91%
rename from dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/KubeBaseTest.java
rename to dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/KubeBaseTest.java
index 86856f6a31c..ab3dde9278f 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/KubeBaseTest.java
+++ b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/KubeBaseTest.java
@@ -26,6 +26,9 @@ import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer;
 import io.fabric8.mockwebserver.Context;
 import okhttp3.mockwebserver.MockWebServer;
 import org.apache.camel.dsl.jbang.core.commands.StringPrinter;
+import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
+import org.apache.camel.dsl.jbang.core.common.PluginHelper;
+import org.apache.camel.dsl.jbang.core.common.PluginType;
 import org.apache.camel.util.IOHelper;
 import org.apache.camel.v1.Integration;
 import org.apache.camel.v1.IntegrationSpec;
@@ -52,6 +55,9 @@ public class KubeBaseTest {
                 new HashMap<>(), new KubernetesCrudDispatcher(), false);
 
         kubernetesClient = k8sServer.createClient();
+
+        CommandLineHelper.useHomeDir("target");
+        PluginHelper.enable(PluginType.CAMEL_K);
     }
 
     @BeforeEach
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommandMainTest.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommandMainTest.java
similarity index 89%
rename from dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommandMainTest.java
rename to dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommandMainTest.java
index 8333dc0b278..7cf10191c45 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommandMainTest.java
+++ b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/KubeCommandMainTest.java
@@ -24,6 +24,9 @@ import java.util.List;
 import io.fabric8.kubernetes.api.model.Pod;
 import io.fabric8.kubernetes.api.model.PodBuilder;
 import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.impl.engine.DefaultClassResolver;
+import org.apache.camel.impl.engine.DefaultFactoryFinder;
+import org.apache.camel.spi.FactoryFinder;
 import org.apache.camel.v1.Integration;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
@@ -105,10 +108,17 @@ class KubeCommandMainTest extends KubeBaseTest {
                   traits: {}""", printer.getOutput());
     }
 
+    @Test
+    public void shouldResolvePlugin() {
+        FactoryFinder factoryFinder
+                = new DefaultFactoryFinder(new DefaultClassResolver(), FactoryFinder.DEFAULT_PATH + "camel-jbang-plugin/");
+        Assertions.assertTrue(factoryFinder.newInstance("camel-jbang-plugin-k").isPresent());
+    }
+
     private CamelJBangMain createMain() {
         return new CamelJBangMain() {
             @Override
-            protected void quit(int exitCode) {
+            public void quit(int exitCode) {
                 if (exitCode != 0) {
                     Assertions.fail("Main finished with exit code %d".formatted(exitCode));
                 }
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/resources/org/apache/camel/dsl/jbang/core/commands/k/integration.yaml b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/resources/org/apache/camel/dsl/jbang/core/commands/k/integration.yaml
similarity index 100%
rename from dsl/camel-jbang/camel-jbang-core/src/test/resources/org/apache/camel/dsl/jbang/core/commands/k/integration.yaml
rename to dsl/camel-jbang/camel-jbang-plugin-k/src/test/resources/org/apache/camel/dsl/jbang/core/commands/k/integration.yaml
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/resources/pod.yaml b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/resources/pod.yaml
similarity index 100%
rename from dsl/camel-jbang/camel-jbang-core/src/test/resources/pod.yaml
rename to dsl/camel-jbang/camel-jbang-plugin-k/src/test/resources/pod.yaml
diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/resources/route.yaml b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/resources/route.yaml
similarity index 100%
rename from dsl/camel-jbang/camel-jbang-core/src/test/resources/route.yaml
rename to dsl/camel-jbang/camel-jbang-plugin-k/src/test/resources/route.yaml
diff --git a/dsl/camel-jbang/pom.xml b/dsl/camel-jbang/pom.xml
index fe7c8a66317..1399ce4e14f 100644
--- a/dsl/camel-jbang/pom.xml
+++ b/dsl/camel-jbang/pom.xml
@@ -37,5 +37,6 @@
         <module>camel-jbang-console</module>
         <module>camel-jbang-core</module>
         <module>camel-jbang-main</module>
+        <module>camel-jbang-plugin-k</module>
     </modules>
 </project>