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

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

commit eeee823a8e647aeb560288b6d7698cc4ef4ef9d5
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu May 21 21:06:13 2026 +0200

    CAMEL-23491: Add known 3rd party plugin catalog to camel-jbang
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 .../modules/ROOT/pages/camel-jbang.adoc            | 29 +++++++++++++--
 .../dsl/jbang/core/commands/plugin/PluginAdd.java  | 33 +++++++++++++++--
 .../dsl/jbang/core/commands/plugin/PluginGet.java  | 40 +++++++++++++++++++--
 .../camel/dsl/jbang/core/common/PluginHelper.java  | 32 +++++++++++++++++
 .../camel/dsl/jbang/core/common/PluginType.java    | 22 +++++++-----
 .../src/main/resources/known-plugins.json          | 20 +++++++++++
 .../jbang/core/commands/plugin/PluginGetTest.java  | 41 ++++++++++++----------
 7 files changed, 183 insertions(+), 34 deletions(-)

diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
index d95a9d8160a2..02972e2360ef 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
@@ -1251,11 +1251,20 @@ camel plugin get --all
 ----
 Supported plugins:
 
- NAME        COMMAND     DEPENDENCY                                      
DESCRIPTION
- kubernetes  kubernetes  org.apache.camel:camel-jbang-plugin-kubernetes  Run 
Camel applications on Kubernetes
- generate    generate    org.apache.camel:camel-jbang-plugin-generate    
Generate code such as DTOs
+ NAME        COMMAND     VENDOR  DEPENDENCY                                    
  DESCRIPTION
+ kubernetes  kubernetes  ASF     
org.apache.camel:camel-jbang-plugin-kubernetes  Run Camel applications on 
Kubernetes
+ generate    generate    ASF     org.apache.camel:camel-jbang-plugin-generate  
  Generate code such as DTOs
+ ...
+
+Known 3rd party plugins:
+
+ NAME        COMMAND  VENDOR     DEPENDENCY                                    
    DESCRIPTION
+ forage      forage   Community  io.kaoto.forage:camel-jbang-plugin-forage     
    Utilities for working with Forage components
+ camel-kit   kit      Community  io.github.luigidemasi:camel-kit-jbang-plugin  
    Design Apache Camel Integrations with AI
 ----
 
+The `VENDOR` column indicates whether the plugin is from `ASF` (Apache 
Software Foundation) or `Community` (3rd party).
+
 In case you want to enable a plugin and its functionality, you can add it as 
follows:
 
 [source,bash]
@@ -1272,6 +1281,20 @@ camel plugin add generate
 
 This adds the plugin, and all subcommands are now available for execution.
 
+Known 3rd party plugins can be installed by name as well. For example:
+
+[source,bash]
+----
+camel plugin add forage
+----
+
+To install a specific version of a 3rd party plugin, use the `--version` 
option:
+
+[source,bash]
+----
+camel plugin add forage --version=1.2.3
+----
+
 You can list the currently installed plugins with:
 
 [source,bash]
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
index a1ad1998f9ad..82c1c0b15de8 100644
--- 
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
@@ -21,6 +21,7 @@ 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.PluginHelper;
 import org.apache.camel.dsl.jbang.core.common.PluginType;
 import org.apache.camel.util.json.JsonObject;
 import picocli.CommandLine;
@@ -89,6 +90,30 @@ public class PluginAdd extends PluginBaseCommand {
             if (firstVersion == null) {
                 firstVersion = camelPlugin.get().getFirstVersion();
             }
+        } else {
+            Optional<JsonObject> known = PluginHelper.findKnownPlugin(name);
+            if (known.isPresent()) {
+                JsonObject kp = known.get();
+                if (command == null) {
+                    command = kp.getString("command");
+                }
+                if (description == null) {
+                    description = kp.getString("description");
+                }
+                if (firstVersion == null) {
+                    firstVersion = kp.getString("firstVersion");
+                }
+                if (gav == null) {
+                    groupId = kp.getStringOrDefault("groupId", groupId);
+                    artifactId = kp.getString("artifactId");
+                }
+                if (repositories == null) {
+                    String knownRepos = kp.getString("repos");
+                    if (knownRepos != null) {
+                        repositories = knownRepos;
+                    }
+                }
+            }
         }
 
         if (command == null) {
@@ -111,8 +136,12 @@ public class PluginAdd extends PluginBaseCommand {
 
         if (gav == null && (groupId != null && artifactId != null)) {
             if (version == null) {
-                CamelCatalog catalog = new DefaultCamelCatalog();
-                version = catalog.getCatalogVersion();
+                if ("org.apache.camel".equals(groupId)) {
+                    CamelCatalog catalog = new DefaultCamelCatalog();
+                    version = catalog.getCatalogVersion();
+                } else {
+                    version = "LATEST";
+                }
             }
 
             gav = "%s:%s:%s".formatted(groupId, artifactId, version);
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
index 9e17ae314ec7..0395a5f5f720 100644
--- 
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
@@ -25,6 +25,7 @@ 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.PluginHelper;
 import org.apache.camel.dsl.jbang.core.common.PluginType;
 import org.apache.camel.util.json.JsonObject;
 import picocli.CommandLine;
@@ -59,7 +60,8 @@ public class PluginGet extends PluginBaseCommand {
                     = details.getStringOrDefault("description", "Plugin %s 
called with command %s".formatted(name, command));
             String repos = details.getString("repos");
 
-            rows.add(new Row(name, command, dependency, description, repos));
+            String vendor = resolveVendor(name);
+            rows.add(new Row(name, command, dependency, description, repos, 
vendor));
         });
 
         printRows(rows);
@@ -71,7 +73,7 @@ public class PluginGet extends PluginBaseCommand {
                     String dependency = 
"org.apache.camel:camel-jbang-plugin-%s".formatted(camelPlugin.getCommand());
                     rows.add(new Row(
                             camelPlugin.getName(), camelPlugin.getCommand(), 
dependency,
-                            camelPlugin.getDescription(), 
camelPlugin.getRepos()));
+                            camelPlugin.getDescription(), 
camelPlugin.getRepos(), camelPlugin.getVendor()));
                 }
             }
 
@@ -82,11 +84,41 @@ public class PluginGet extends PluginBaseCommand {
 
                 printRows(rows);
             }
+
+            rows.clear();
+            List<JsonObject> knownPlugins = PluginHelper.loadKnownPlugins();
+            for (JsonObject kp : knownPlugins) {
+                String kpName = kp.getString("name");
+                if (plugins.get(kpName) == null && 
PluginType.findByName(kpName).isEmpty()) {
+                    String dep = kp.getString("groupId") != null && 
kp.getString("artifactId") != null
+                            ? "%s:%s".formatted(kp.getString("groupId"), 
kp.getString("artifactId"))
+                            : kp.getStringOrDefault("dependency", "");
+                    rows.add(new Row(
+                            kpName, kp.getString("command"), dep,
+                            kp.getString("description"), kp.getString("repos"),
+                            kp.getStringOrDefault("vendor", "Community")));
+                }
+            }
+
+            if (!rows.isEmpty()) {
+                printer().println();
+                printer().println("Known 3rd party plugins:");
+                printer().println();
+
+                printRows(rows);
+            }
         }
 
         return 0;
     }
 
+    private String resolveVendor(String name) {
+        return PluginType.findByName(name)
+                .map(PluginType::getVendor)
+                .or(() -> PluginHelper.findKnownPlugin(name).map(kp -> 
kp.getStringOrDefault("vendor", "Community")))
+                .orElse("");
+    }
+
     private void printRows(List<Row> rows) {
         if (!rows.isEmpty()) {
             printer().println(AsciiTable.getTable(AsciiTable.NO_BORDERS, rows, 
Arrays.asList(
@@ -94,6 +126,8 @@ public class PluginGet extends PluginBaseCommand {
                             .with(r -> r.name),
                     new 
Column().header("COMMAND").headerAlign(HorizontalAlign.LEFT).dataAlign(HorizontalAlign.LEFT)
                             .with(r -> r.command),
+                    new 
Column().header("VENDOR").headerAlign(HorizontalAlign.LEFT).dataAlign(HorizontalAlign.LEFT)
+                            .with(r -> r.vendor != null ? r.vendor : ""),
                     new 
Column().header("DEPENDENCY").headerAlign(HorizontalAlign.LEFT).dataAlign(HorizontalAlign.LEFT)
                             .with(r -> r.dependency),
                     new 
Column().visible(repos).header("REPOSITORY").headerAlign(HorizontalAlign.LEFT)
@@ -105,6 +139,6 @@ public class PluginGet extends PluginBaseCommand {
         }
     }
 
-    private record Row(String name, String command, String dependency, String 
description, String repos) {
+    private record Row(String name, String command, String dependency, String 
description, String repos, String vendor) {
     }
 }
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
index 36b01f5e3eda..9a8c9c60c71f 100644
--- 
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
@@ -50,6 +50,7 @@ import org.apache.camel.spi.FactoryFinder;
 import org.apache.camel.support.ObjectHelper;
 import org.apache.camel.tooling.maven.MavenGav;
 import org.apache.camel.util.IOHelper;
+import org.apache.camel.util.json.JsonArray;
 import org.apache.camel.util.json.JsonObject;
 import org.apache.camel.util.json.Jsoner;
 import picocli.CommandLine;
@@ -575,6 +576,37 @@ public final class PluginHelper {
         }
     }
 
+    /**
+     * Loads the list of known 3rd party plugins from the classpath resource 
known-plugins.json.
+     */
+    public static List<JsonObject> loadKnownPlugins() {
+        try (InputStream is = 
PluginHelper.class.getClassLoader().getResourceAsStream("known-plugins.json")) {
+            if (is != null) {
+                String text = new String(is.readAllBytes(), 
java.nio.charset.StandardCharsets.UTF_8);
+                JsonArray arr = (JsonArray) Jsoner.deserialize(text);
+                List<JsonObject> result = new ArrayList<>(arr.size());
+                for (Object o : arr) {
+                    if (o instanceof JsonObject jo) {
+                        result.add(jo);
+                    }
+                }
+                return result;
+            }
+        } catch (Exception e) {
+            // ignore
+        }
+        return List.of();
+    }
+
+    /**
+     * Finds a known 3rd party plugin by name (case-insensitive).
+     */
+    public static Optional<JsonObject> findKnownPlugin(String name) {
+        return loadKnownPlugins().stream()
+                .filter(p -> name.equalsIgnoreCase(p.getString("name")))
+                .findFirst();
+    }
+
     public static void enable(PluginType pluginType) {
         JsonObject pluginConfig = PluginHelper.getOrCreatePluginConfig();
         JsonObject plugins = pluginConfig.getMap("plugins");
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
index 0e3eb1d5df4c..32ab80632d44 100644
--- 
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
@@ -24,26 +24,28 @@ import java.util.Optional;
  */
 public enum PluginType {
 
-    KUBERNETES("kubernetes", "kubernetes", "Run Camel applications on 
Kubernetes", "4.8.0", null),
-    GENERATE("generate", "generate", "Generate code such as DTOs", "4.8.0", 
null),
-    EDIT("edit", "edit", "Edit Camel files with suggestions", "4.12.0", null),
-    TEST("test", "test", "Manage tests for Camel applications", "4.14.0", 
null),
-    ROUTE_PARSER("route-parser", "route-parser", "Parses Java route and dumps 
route structure", "4.17.0", null),
-    VALIDATE("validate", "validate", "Validate Camel routes", "4.18.0", null),
-    TUI("tui", "tui", "Camel Dashboard", "4.20.0", null);
+    KUBERNETES("kubernetes", "kubernetes", "Run Camel applications on 
Kubernetes", "4.8.0", null, "ASF"),
+    GENERATE("generate", "generate", "Generate code such as DTOs", "4.8.0", 
null, "ASF"),
+    EDIT("edit", "edit", "Edit Camel files with suggestions", "4.12.0", null, 
"ASF"),
+    TEST("test", "test", "Manage tests for Camel applications", "4.14.0", 
null, "ASF"),
+    ROUTE_PARSER("route-parser", "route-parser", "Parses Java route and dumps 
route structure", "4.17.0", null, "ASF"),
+    VALIDATE("validate", "validate", "Validate Camel routes", "4.18.0", null, 
"ASF"),
+    TUI("tui", "tui", "Camel Dashboard", "4.20.0", null, "ASF");
 
     private final String name;
     private final String command;
     private final String description;
     private final String firstVersion;
     private final String repos;
+    private final String vendor;
 
-    PluginType(String name, String command, String description, String 
firstVersion, String repos) {
+    PluginType(String name, String command, String description, String 
firstVersion, String repos, String vendor) {
         this.name = name;
         this.command = command;
         this.description = description;
         this.firstVersion = firstVersion;
         this.repos = repos;
+        this.vendor = vendor;
     }
 
     public static Optional<PluginType> findByName(String name) {
@@ -71,4 +73,8 @@ public enum PluginType {
     public String getRepos() {
         return repos;
     }
+
+    public String getVendor() {
+        return vendor;
+    }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/known-plugins.json 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/known-plugins.json
new file mode 100644
index 000000000000..9b1909b2434e
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/known-plugins.json
@@ -0,0 +1,20 @@
+[
+  {
+    "name": "forage",
+    "command": "forage",
+    "description": "Utilities for working with Forage components",
+    "firstVersion": "4.10.0",
+    "groupId": "io.kaoto.forage",
+    "artifactId": "camel-jbang-plugin-forage",
+    "vendor": "Community"
+  },
+  {
+    "name": "camel-kit",
+    "command": "kit",
+    "description": "Design Apache Camel Integrations with AI",
+    "firstVersion": "4.10.0",
+    "groupId": "io.github.luigidemasi",
+    "artifactId": "camel-kit-jbang-plugin",
+    "vendor": "Community"
+  }
+]
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
index 9fef8e54f0bb..ba950afb31bb 100644
--- 
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
@@ -55,10 +55,10 @@ class PluginGetTest extends CamelCommandBaseTestSupport {
 
         List<String> output = printer.getLines();
         Assertions.assertEquals(2, output.size());
-        Assertions.assertEquals("NAME        COMMAND     DEPENDENCY            
                          DESCRIPTION",
+        Assertions.assertEquals("NAME        COMMAND     VENDOR  DEPENDENCY    
                                  DESCRIPTION",
                 output.get(0));
         Assertions.assertEquals(
-                "kubernetes  kubernetes  
org.apache.camel:camel-jbang-plugin-kubernetes  %s"
+                "kubernetes  kubernetes  ASF     
org.apache.camel:camel-jbang-plugin-kubernetes  %s"
                         .formatted(PluginType.KUBERNETES.getDescription()),
                 output.get(1));
     }
@@ -70,12 +70,13 @@ class PluginGetTest extends CamelCommandBaseTestSupport {
         command.doCall();
 
         List<String> output = printer.getLines();
-        Assertions.assertEquals(10, output.size());
+        Assertions.assertTrue(output.size() >= 10);
         Assertions.assertEquals("Supported plugins:", output.get(0));
-        Assertions.assertEquals("NAME          COMMAND       DEPENDENCY        
                                DESCRIPTION",
+        Assertions.assertEquals(
+                "NAME          COMMAND       VENDOR  DEPENDENCY                
                        DESCRIPTION",
                 output.get(2));
         Assertions.assertEquals(
-                "kubernetes    kubernetes    
org.apache.camel:camel-jbang-plugin-kubernetes    %s"
+                "kubernetes    kubernetes    ASF     
org.apache.camel:camel-jbang-plugin-kubernetes    %s"
                         .formatted(PluginType.KUBERNETES.getDescription()),
                 output.get(3));
     }
@@ -97,8 +98,9 @@ class PluginGetTest extends CamelCommandBaseTestSupport {
 
         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",
+        Assertions.assertEquals("NAME  COMMAND  VENDOR  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));
     }
 
@@ -120,43 +122,46 @@ class PluginGetTest extends CamelCommandBaseTestSupport {
         command.doCall();
 
         List<String> output = printer.getLines();
-        Assertions.assertEquals(13, output.size());
-        Assertions.assertEquals("NAME        COMMAND  DEPENDENCY               
          DESCRIPTION", output.get(0));
+        Assertions.assertTrue(output.size() >= 13);
+        Assertions.assertEquals("NAME        COMMAND  VENDOR  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",
+                "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",
+        Assertions.assertEquals(
+                "NAME          COMMAND       VENDOR  DEPENDENCY                
                        DESCRIPTION",
                 output.get(5));
         Assertions.assertEquals(
-                "kubernetes    kubernetes    
org.apache.camel:camel-jbang-plugin-kubernetes    %s"
+                "kubernetes    kubernetes    ASF     
org.apache.camel:camel-jbang-plugin-kubernetes    %s"
                         .formatted(PluginType.KUBERNETES.getDescription()),
                 output.get(6));
         Assertions.assertEquals(
-                "generate      generate      
org.apache.camel:camel-jbang-plugin-generate      %s"
+                "generate      generate      ASF     
org.apache.camel:camel-jbang-plugin-generate      %s"
                         .formatted(PluginType.GENERATE.getDescription()),
                 output.get(7));
         Assertions.assertEquals(
-                "edit          edit          
org.apache.camel:camel-jbang-plugin-edit          %s"
+                "edit          edit          ASF     
org.apache.camel:camel-jbang-plugin-edit          %s"
                         .formatted(PluginType.EDIT.getDescription()),
                 output.get(8));
         Assertions.assertEquals(
-                "test          test          
org.apache.camel:camel-jbang-plugin-test          %s"
+                "test          test          ASF     
org.apache.camel:camel-jbang-plugin-test          %s"
                         .formatted(PluginType.TEST.getDescription()),
                 output.get(9));
         Assertions.assertEquals(
-                "route-parser  route-parser  
org.apache.camel:camel-jbang-plugin-route-parser  %s"
+                "route-parser  route-parser  ASF     
org.apache.camel:camel-jbang-plugin-route-parser  %s"
                         .formatted(PluginType.ROUTE_PARSER.getDescription()),
                 output.get(10));
         Assertions.assertEquals(
-                "validate      validate      
org.apache.camel:camel-jbang-plugin-validate      %s"
+                "validate      validate      ASF     
org.apache.camel:camel-jbang-plugin-validate      %s"
                         .formatted(PluginType.VALIDATE.getDescription()),
                 output.get(11));
         Assertions.assertEquals(
-                "tui           tui           
org.apache.camel:camel-jbang-plugin-tui           %s"
+                "tui           tui           ASF     
org.apache.camel:camel-jbang-plugin-tui           %s"
                         .formatted(PluginType.TUI.getDescription()),
                 output.get(12));
+
+        Assertions.assertEquals("Known 3rd party plugins:", output.get(14));
     }
 
 }

Reply via email to