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 de7124e5aa5a chore: reorganize bundled jbang examples with category 
sub-grouping (#24492)
de7124e5aa5a is described below

commit de7124e5aa5a25b3db5998e5ece4aee6b9ea7a83
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Jul 7 16:21:01 2026 +0200

    chore: reorganize bundled jbang examples with category sub-grouping (#24492)
    
    * CAMEL-23928: camel-langchain4j-agent - Add AiServices builder customizer 
and tool-calling options to AgentConfiguration
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
    
    * CAMEL-23928: Fix FQCN usage in AgentConfigurationTest
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
    
    * chore: reorganize bundled jbang examples into categorized folders
    
    Organize bundled examples into topic-based subfolders matching
    the companion reorganization of apache/camel-jbang-examples:
    
    - beginner/ - timer-log, cron-log, rest-api, routes, tui-hello-world, 
camel-1-tribute
    - eip/ - circuit-breaker, content-based-router (new), splitter (new), 
aggregator (new)
    - database/ - sql
    - language/ - groovy
    - observability/ - memory-leak, message-size, route-topology
    - transformation/ - xslt
    
    Three new EIP examples added:
    - content-based-router: choice/when routing by message content
    - splitter: split batch into individual messages
    - aggregator: collect messages into batches
    
    Catalog JSON updated with new paths for all 29 examples.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
    
    * chore: add category sub-grouping to example listing in CLI and TUI
    
    Add category-based organization to the example browser in both CLI
    (camel run --example) and TUI. Examples are now sub-grouped by category
    within each difficulty level. The TUI uses a folder-based navigation
    model with ".." to go back. Fix tests for the new categorized names.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
    
    ---------
    
    Signed-off-by: Claus Ibsen <[email protected]>
    Co-authored-by: Claude Opus 4.6 <[email protected]>
---
 .../langchain4j/agent/api/AbstractAgent.java       |  22 +
 .../langchain4j/agent/api/AgentConfiguration.java  | 139 +++++
 .../agent/api/AgentConfigurationTest.java          | 101 ++++
 .../apache/camel/dsl/jbang/core/commands/Run.java  |  23 +-
 .../camel/dsl/jbang/core/common/ExampleHelper.java |  21 +
 .../{ => beginner}/camel-1-tribute/README.md       |   0
 .../camel-1-tribute/application.properties         |   0
 .../camel-1-tribute/jms-to-file.camel.yaml         |   0
 .../examples/{ => beginner}/cron-log/README.md     |   0
 .../{ => beginner}/cron-log/application.properties |   0
 .../{ => beginner}/cron-log/cron-log.camel.yaml    |   0
 .../examples/{ => beginner}/rest-api/README.md     |   0
 .../{ => beginner}/rest-api/application.properties |   0
 .../{ => beginner}/rest-api/rest-api.camel.yaml    |   0
 .../examples/{ => beginner}/routes/Greeter.java    |   0
 .../examples/{ => beginner}/routes/README.md       |   0
 .../{ => beginner}/routes/application.properties   |   0
 .../examples/{ => beginner}/routes/beans.yaml      |   0
 .../{ => beginner}/routes/routes.camel.yaml        |   0
 .../examples/{ => beginner}/timer-log/README.md    |   0
 .../timer-log/application.properties               |   0
 .../{ => beginner}/timer-log/timer-log.camel.yaml  |   0
 .../{ => beginner}/tui-hello-world/README.md       |   0
 .../tui-hello-world/tui-hello-world.yaml           |   0
 .../examples/camel-jbang-example-catalog.json      | 638 +++++++++++----------
 .../examples/{ => database}/sql/README.md          |   0
 .../{ => database}/sql/application.properties      |   0
 .../examples/{ => database}/sql/sql.camel.yaml     |   0
 .../resources/examples/eip/aggregator/README.md    |  47 ++
 .../examples/eip/aggregator/aggregator.camel.yaml  |  37 ++
 .../examples/{ => eip}/circuit-breaker/README.md   |   0
 .../{ => eip}/circuit-breaker/route.camel.yaml     |   0
 .../examples/eip/content-based-router/README.md    |  48 ++
 .../content-based-router.camel.yaml                |  45 ++
 .../main/resources/examples/eip/splitter/README.md |  47 ++
 .../examples/eip/splitter/splitter.camel.yaml      |  22 +
 .../examples/{ => language}/groovy/README.md       |   0
 .../{ => language}/groovy/application.properties   |   0
 .../{ => language}/groovy/groovy.camel.yaml        |   0
 .../memory-leak/MemoryLeak.java                    |   0
 .../{ => observability}/memory-leak/README.md      |   0
 .../{ => observability}/message-size/README.md     |   0
 .../message-size/message-size.camel.yaml           |   0
 .../message-size/orders.camel.yaml                 |   0
 .../{ => observability}/route-topology/README.md   |   0
 .../route-topology/application.properties          |   0
 .../route-topology/route-topology.camel.yaml       |   0
 .../examples/{ => transformation}/xslt/README.md   |   0
 .../{ => transformation}/xslt/consumer.camel.yaml  |   0
 .../{ => transformation}/xslt/input/account.xml    |   0
 .../{ => transformation}/xslt/stylesheet.xsl       |   0
 .../camel/dsl/jbang/core/commands/RunTest.java     |   2 +-
 .../dsl/jbang/core/common/ExampleHelperTest.java   |  36 +-
 .../core/commands/tui/ExampleBrowserPopup.java     | 341 +++++++----
 54 files changed, 1129 insertions(+), 440 deletions(-)

diff --git 
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AbstractAgent.java
 
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AbstractAgent.java
index 0cccc29792b8..bc837d6c2c42 100644
--- 
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AbstractAgent.java
+++ 
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AbstractAgent.java
@@ -150,5 +150,27 @@ public abstract class AbstractAgent<S> implements Agent {
         if (responseFormat != null) {
             builder.chatRequestTransformer(chatRequest -> 
chatRequest.toBuilder().responseFormat(responseFormat).build());
         }
+
+        // Tool calling options
+        if (configuration.getMaxToolCallingRoundTrips() > 0) {
+            
builder.maxToolCallingRoundTrips(configuration.getMaxToolCallingRoundTrips());
+        }
+        if (configuration.getHallucinatedToolNameStrategy() != null) {
+            
builder.hallucinatedToolNameStrategy(configuration.getHallucinatedToolNameStrategy());
+        }
+        if (configuration.getToolExecutionErrorHandler() != null) {
+            
builder.toolExecutionErrorHandler(configuration.getToolExecutionErrorHandler());
+        }
+        if (configuration.getToolArgumentsErrorHandler() != null) {
+            
builder.toolArgumentsErrorHandler(configuration.getToolArgumentsErrorHandler());
+        }
+        if (configuration.getCompensateOnToolErrors() != null) {
+            
builder.compensateOnToolErrors(configuration.getCompensateOnToolErrors());
+        }
+
+        // Custom AiServices builder customizer (escape hatch for any builder 
option not directly exposed)
+        if (configuration.getAiServicesCustomizer() != null) {
+            configuration.getAiServicesCustomizer().accept(builder);
+        }
     }
 }
diff --git 
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentConfiguration.java
 
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentConfiguration.java
index 65ea85617afb..c813d7454ae5 100644
--- 
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentConfiguration.java
+++ 
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/AgentConfiguration.java
@@ -21,13 +21,20 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.function.BiPredicate;
+import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
+import dev.langchain4j.agent.tool.ToolExecutionRequest;
 import dev.langchain4j.agent.tool.ToolSpecification;
+import dev.langchain4j.data.message.ToolExecutionResultMessage;
 import dev.langchain4j.mcp.client.McpClient;
 import dev.langchain4j.memory.chat.ChatMemoryProvider;
 import dev.langchain4j.model.chat.ChatModel;
 import dev.langchain4j.rag.RetrievalAugmentor;
+import dev.langchain4j.service.AiServices;
+import dev.langchain4j.service.tool.ToolArgumentsErrorHandler;
+import dev.langchain4j.service.tool.ToolExecutionErrorHandler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -67,6 +74,12 @@ public class AgentConfiguration {
     private List<Object> customTools; // Custom LangChain4j tools
     private List<McpClient> mcpClients; // MCP clients for external tool 
integration
     private BiPredicate<McpClient, ToolSpecification> mcpToolProviderFilter; 
// Filter for MCP tools
+    private int maxToolCallingRoundTrips; // Max number of tool-calling round 
trips (0 = not set)
+    private Function<ToolExecutionRequest, ToolExecutionResultMessage> 
hallucinatedToolNameStrategy;
+    private ToolExecutionErrorHandler toolExecutionErrorHandler;
+    private ToolArgumentsErrorHandler toolArgumentsErrorHandler;
+    private Boolean compensateOnToolErrors;
+    private Consumer<AiServices<?>> aiServicesCustomizer;
 
     /**
      * Gets the configured chat model.
@@ -346,6 +359,132 @@ public class AgentConfiguration {
         return this;
     }
 
+    /**
+     * Gets the maximum number of tool-calling round trips allowed.
+     *
+     * @return the maximum round trips, or 0 if not configured
+     */
+    public int getMaxToolCallingRoundTrips() {
+        return maxToolCallingRoundTrips;
+    }
+
+    /**
+     * Sets the maximum number of tool-calling round trips the LLM can make 
per request. This limits how many times the
+     * model can invoke tools before a final response must be produced.
+     *
+     * @param  maxToolCallingRoundTrips the maximum number of round trips 
(must be positive)
+     * @return                          this configuration instance for method 
chaining
+     */
+    public AgentConfiguration withMaxToolCallingRoundTrips(int 
maxToolCallingRoundTrips) {
+        this.maxToolCallingRoundTrips = maxToolCallingRoundTrips;
+        return this;
+    }
+
+    /**
+     * Gets the strategy for handling hallucinated (non-existent) tool names.
+     *
+     * @return the hallucinated tool name strategy, or {@code null} if not 
configured
+     */
+    public Function<ToolExecutionRequest, ToolExecutionResultMessage> 
getHallucinatedToolNameStrategy() {
+        return hallucinatedToolNameStrategy;
+    }
+
+    /**
+     * Sets the strategy for handling cases where the LLM hallucinates a tool 
name that does not exist. The function
+     * receives the invalid tool execution request and returns a result 
message to send back to the LLM.
+     *
+     * @param  hallucinatedToolNameStrategy the strategy function for handling 
hallucinated tool names
+     * @return                              this configuration instance for 
method chaining
+     */
+    public AgentConfiguration withHallucinatedToolNameStrategy(
+            Function<ToolExecutionRequest, ToolExecutionResultMessage> 
hallucinatedToolNameStrategy) {
+        this.hallucinatedToolNameStrategy = hallucinatedToolNameStrategy;
+        return this;
+    }
+
+    /**
+     * Gets the error handler for tool execution failures.
+     *
+     * @return the tool execution error handler, or {@code null} if not 
configured
+     */
+    public ToolExecutionErrorHandler getToolExecutionErrorHandler() {
+        return toolExecutionErrorHandler;
+    }
+
+    /**
+     * Sets the error handler invoked when a tool execution throws an 
exception.
+     *
+     * @param  toolExecutionErrorHandler the handler for tool execution errors
+     * @return                           this configuration instance for 
method chaining
+     */
+    public AgentConfiguration 
withToolExecutionErrorHandler(ToolExecutionErrorHandler 
toolExecutionErrorHandler) {
+        this.toolExecutionErrorHandler = toolExecutionErrorHandler;
+        return this;
+    }
+
+    /**
+     * Gets the error handler for tool argument parsing failures.
+     *
+     * @return the tool arguments error handler, or {@code null} if not 
configured
+     */
+    public ToolArgumentsErrorHandler getToolArgumentsErrorHandler() {
+        return toolArgumentsErrorHandler;
+    }
+
+    /**
+     * Sets the error handler invoked when tool arguments cannot be parsed or 
validated.
+     *
+     * @param  toolArgumentsErrorHandler the handler for tool argument errors
+     * @return                           this configuration instance for 
method chaining
+     */
+    public AgentConfiguration 
withToolArgumentsErrorHandler(ToolArgumentsErrorHandler 
toolArgumentsErrorHandler) {
+        this.toolArgumentsErrorHandler = toolArgumentsErrorHandler;
+        return this;
+    }
+
+    /**
+     * Gets whether tool error compensation is enabled.
+     *
+     * @return {@code true} if enabled, {@code false} if disabled, or {@code 
null} if not configured
+     */
+    public Boolean getCompensateOnToolErrors() {
+        return compensateOnToolErrors;
+    }
+
+    /**
+     * Sets whether the agent should attempt to compensate when tool execution 
errors occur by sending the error back to
+     * the LLM for recovery.
+     *
+     * @param  compensateOnToolErrors {@code true} to enable compensation, 
{@code false} to disable
+     * @return                        this configuration instance for method 
chaining
+     */
+    public AgentConfiguration withCompensateOnToolErrors(Boolean 
compensateOnToolErrors) {
+        this.compensateOnToolErrors = compensateOnToolErrors;
+        return this;
+    }
+
+    /**
+     * Gets the custom AiServices builder customizer.
+     *
+     * @return the customizer, or {@code null} if not configured
+     */
+    public Consumer<AiServices<?>> getAiServicesCustomizer() {
+        return aiServicesCustomizer;
+    }
+
+    /**
+     * Sets a customizer callback that is invoked on the LangChain4j {@link 
AiServices} builder after all standard
+     * configuration has been applied but before {@code build()} is called. 
This provides an escape hatch for
+     * configuring any AiServices builder option that is not directly exposed 
on this configuration class.
+     *
+     * @param  aiServicesCustomizer the customizer to apply to the AiServices 
builder
+     * @return                      this configuration instance for method 
chaining
+     */
+    public AgentConfiguration withAiServicesCustomizer(Consumer<AiServices<?>> 
aiServicesCustomizer) {
+        this.aiServicesCustomizer = aiServicesCustomizer;
+        return this;
+    }
+
     /**
      * Loads a guardrail class by its fully qualified name using reflection.
      *
diff --git 
a/components/camel-ai/camel-langchain4j-agent-api/src/test/java/org/apache/camel/component/langchain4j/agent/api/AgentConfigurationTest.java
 
b/components/camel-ai/camel-langchain4j-agent-api/src/test/java/org/apache/camel/component/langchain4j/agent/api/AgentConfigurationTest.java
index 12a682d07ad0..297ac1ccc71e 100644
--- 
a/components/camel-ai/camel-langchain4j-agent-api/src/test/java/org/apache/camel/component/langchain4j/agent/api/AgentConfigurationTest.java
+++ 
b/components/camel-ai/camel-langchain4j-agent-api/src/test/java/org/apache/camel/component/langchain4j/agent/api/AgentConfigurationTest.java
@@ -18,11 +18,19 @@ package org.apache.camel.component.langchain4j.agent.api;
 
 import java.io.Serializable;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Function;
 
+import dev.langchain4j.agent.tool.ToolExecutionRequest;
+import dev.langchain4j.data.message.ToolExecutionResultMessage;
+import dev.langchain4j.service.tool.ToolArgumentsErrorHandler;
+import dev.langchain4j.service.tool.ToolExecutionErrorHandler;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class AgentConfigurationTest {
@@ -249,4 +257,97 @@ public class AgentConfigurationTest {
         assertNotNull(config.getOutputGuardrailClasses());
         assertTrue(config.getOutputGuardrailClasses().isEmpty());
     }
+
+    // Tests for tool-calling options
+
+    @Test
+    public void testMaxToolCallingRoundTrips() {
+        AgentConfiguration config = new AgentConfiguration();
+        assertEquals(0, config.getMaxToolCallingRoundTrips());
+
+        AgentConfiguration result = config.withMaxToolCallingRoundTrips(10);
+
+        assertSame(config, result);
+        assertEquals(10, config.getMaxToolCallingRoundTrips());
+    }
+
+    @Test
+    public void testHallucinatedToolNameStrategy() {
+        AgentConfiguration config = new AgentConfiguration();
+        assertNull(config.getHallucinatedToolNameStrategy());
+
+        Function<ToolExecutionRequest, ToolExecutionResultMessage> strategy
+                = request -> ToolExecutionResultMessage.from(request, "Unknown 
tool: " + request.name());
+        AgentConfiguration result = 
config.withHallucinatedToolNameStrategy(strategy);
+
+        assertSame(config, result);
+        assertSame(strategy, config.getHallucinatedToolNameStrategy());
+    }
+
+    @Test
+    public void testToolExecutionErrorHandler() {
+        AgentConfiguration config = new AgentConfiguration();
+        assertNull(config.getToolExecutionErrorHandler());
+
+        ToolExecutionErrorHandler handler = (error, context) -> null;
+        AgentConfiguration result = 
config.withToolExecutionErrorHandler(handler);
+
+        assertSame(config, result);
+        assertSame(handler, config.getToolExecutionErrorHandler());
+    }
+
+    @Test
+    public void testToolArgumentsErrorHandler() {
+        AgentConfiguration config = new AgentConfiguration();
+        assertNull(config.getToolArgumentsErrorHandler());
+
+        ToolArgumentsErrorHandler handler = (error, context) -> null;
+        AgentConfiguration result = 
config.withToolArgumentsErrorHandler(handler);
+
+        assertSame(config, result);
+        assertSame(handler, config.getToolArgumentsErrorHandler());
+    }
+
+    @Test
+    public void testCompensateOnToolErrors() {
+        AgentConfiguration config = new AgentConfiguration();
+        assertNull(config.getCompensateOnToolErrors());
+
+        AgentConfiguration result = config.withCompensateOnToolErrors(true);
+
+        assertSame(config, result);
+        assertTrue(config.getCompensateOnToolErrors());
+    }
+
+    @Test
+    public void testAiServicesCustomizer() {
+        AgentConfiguration config = new AgentConfiguration();
+        assertNull(config.getAiServicesCustomizer());
+
+        AtomicBoolean invoked = new AtomicBoolean(false);
+        AgentConfiguration result = config.withAiServicesCustomizer(builder -> 
invoked.set(true));
+
+        assertSame(config, result);
+        assertNotNull(config.getAiServicesCustomizer());
+    }
+
+    @Test
+    public void testFluentChaining() {
+        ToolExecutionErrorHandler execHandler = (error, context) -> null;
+        ToolArgumentsErrorHandler argsHandler = (error, context) -> null;
+
+        AgentConfiguration config = new AgentConfiguration()
+                .withMaxToolCallingRoundTrips(5)
+                .withToolExecutionErrorHandler(execHandler)
+                .withToolArgumentsErrorHandler(argsHandler)
+                .withCompensateOnToolErrors(true)
+                .withAiServicesCustomizer(builder -> {
+                });
+
+        assertEquals(5, config.getMaxToolCallingRoundTrips());
+        assertSame(execHandler, config.getToolExecutionErrorHandler());
+        assertSame(argsHandler, config.getToolArgumentsErrorHandler());
+        assertTrue(config.getCompensateOnToolErrors());
+        assertNotNull(config.getAiServicesCustomizer());
+    }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
index 99603ac96d44..d8ecbe8a8243 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
@@ -411,13 +411,26 @@ public class Run extends CamelCommand {
             if (entries.isEmpty()) {
                 continue;
             }
-            entries.sort(Comparator.comparing(e -> e.getString("name")));
+            String levelName = group.getKey();
+            entries.sort(Comparator.comparing((JsonObject e) -> {
+                String cat = ExampleHelper.getCategory(e);
+                return cat.equals(levelName) ? "" : cat;
+            }).thenComparing(e -> e.getString("name")));
             printer().println();
-            printer().println(group.getKey().substring(0, 1).toUpperCase() + 
group.getKey().substring(1) + ":");
-            printer().printf("       %-30s %s%n", "NAME", "DESCRIPTION");
-            printer().printf("       %-30s %s%n", "----", "-----------");
+            String levelLabel = levelName.substring(0, 1).toUpperCase() + 
levelName.substring(1) + ":";
+            printer().println(levelLabel);
+            printer().println("=".repeat(levelLabel.length()));
+            String currentCategory = null;
             for (JsonObject entry : entries) {
-                String eName = entry.getString("name");
+                String category = ExampleHelper.getCategory(entry);
+                if (!category.equals(currentCategory)) {
+                    currentCategory = category;
+                    if (!category.equals(levelName)) {
+                        printer().println();
+                        printer().println("  " + 
ExampleHelper.formatCategory(category) + ":");
+                    }
+                }
+                String eName = ExampleHelper.getShortName(entry);
                 String desc = entry.getString("description");
                 StringBuilder icons = new StringBuilder();
                 if (ExampleHelper.isBundled(entry)) {
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ExampleHelper.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ExampleHelper.java
index f7674f50268f..d1821c946c2f 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ExampleHelper.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ExampleHelper.java
@@ -122,6 +122,27 @@ public final class ExampleHelper {
         return false;
     }
 
+    public static String getCategory(JsonObject entry) {
+        String name = entry.getString("name");
+        int slash = name != null ? name.indexOf('/') : -1;
+        return slash > 0 ? name.substring(0, slash) : "";
+    }
+
+    public static String formatCategory(String category) {
+        return switch (category) {
+            case "ai" -> "AI";
+            case "eip" -> "EIP";
+            case "rest" -> "REST";
+            default -> category.substring(0, 1).toUpperCase() + 
category.substring(1);
+        };
+    }
+
+    public static String getShortName(JsonObject entry) {
+        String name = entry.getString("name");
+        int slash = name != null ? name.indexOf('/') : -1;
+        return slash > 0 ? name.substring(slash + 1) : name != null ? name : 
"";
+    }
+
     public static boolean isBundled(JsonObject entry) {
         Boolean bundled = entry.getBoolean("bundled");
         return bundled != null && bundled;
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-1-tribute/README.md
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/camel-1-tribute/README.md
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-1-tribute/README.md
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/camel-1-tribute/README.md
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-1-tribute/application.properties
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/camel-1-tribute/application.properties
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-1-tribute/application.properties
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/camel-1-tribute/application.properties
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-1-tribute/jms-to-file.camel.yaml
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/camel-1-tribute/jms-to-file.camel.yaml
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-1-tribute/jms-to-file.camel.yaml
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/camel-1-tribute/jms-to-file.camel.yaml
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/README.md
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/cron-log/README.md
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/README.md
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/cron-log/README.md
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/application.properties
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/cron-log/application.properties
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/application.properties
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/cron-log/application.properties
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/cron-log.camel.yaml
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/cron-log/cron-log.camel.yaml
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/cron-log.camel.yaml
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/cron-log/cron-log.camel.yaml
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/README.md
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/rest-api/README.md
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/README.md
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/rest-api/README.md
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/application.properties
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/rest-api/application.properties
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/application.properties
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/rest-api/application.properties
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/rest-api.camel.yaml
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/rest-api/rest-api.camel.yaml
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/rest-api.camel.yaml
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/rest-api/rest-api.camel.yaml
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/Greeter.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/routes/Greeter.java
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/Greeter.java
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/routes/Greeter.java
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/README.md 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/routes/README.md
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/README.md
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/routes/README.md
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/application.properties
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/routes/application.properties
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/application.properties
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/routes/application.properties
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/beans.yaml
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/routes/beans.yaml
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/beans.yaml
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/routes/beans.yaml
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/routes.camel.yaml
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/routes/routes.camel.yaml
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/routes.camel.yaml
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/routes/routes.camel.yaml
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/README.md
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/timer-log/README.md
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/README.md
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/timer-log/README.md
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/application.properties
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/timer-log/application.properties
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/application.properties
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/timer-log/application.properties
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/timer-log.camel.yaml
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/timer-log/timer-log.camel.yaml
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/timer-log.camel.yaml
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/timer-log/timer-log.camel.yaml
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/tui-hello-world/README.md
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/tui-hello-world/README.md
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/tui-hello-world/README.md
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/tui-hello-world/README.md
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/tui-hello-world/tui-hello-world.yaml
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/tui-hello-world/tui-hello-world.yaml
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/tui-hello-world/tui-hello-world.yaml
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/beginner/tui-hello-world/tui-hello-world.yaml
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json
index a065576cc70f..bd1affbb688b 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json
@@ -1,13 +1,14 @@
 [
     {
-        "name": "artemis",
-        "title": "Apache ActiveMQ Artemis",
-        "description": "Setup connection factory to a remote Apache ActiveMQ 
Artemis messaging broker",
-        "level": "intermediate",
+        "name": "ai/docling-langchain4j-rag",
+        "title": "Document Analysis with Docling and LangChain4j RAG",
+        "description": "RAG workflow combining Docling document conversion 
with LangChain4j and Ollama",
+        "level": "advanced",
         "tags": [
-            "messaging",
-            "jms",
-            "artemis"
+            "ai",
+            "rag",
+            "langchain4j",
+            "docling"
         ],
         "bundled": false,
         "requiresDocker": true,
@@ -15,59 +16,112 @@
         "files": [
             "README.md",
             "application.properties",
-            "consumer.camel.yaml",
-            "producer.camel.yaml"
+            "docling-langchain4j-rag.yaml",
+            "sample.md"
         ],
         "infraServices": [
-            "artemis"
+            "docling",
+            "ollama"
         ]
     },
     {
-        "name": "aws/aws-s3-event-based",
-        "title": "AWS S3 CDC",
-        "description": "Consume S3 events using EventBridge and SQS for change 
data capture",
+        "name": "ai/financial-doc-analyzer",
+        "title": "Financial Document Analyzer",
+        "description": "Automated financial document analysis with Docling, 
LangChain4j, and market data",
         "level": "advanced",
         "tags": [
-            "aws",
-            "s3",
-            "sqs",
-            "eventbridge",
-            "cloud"
+            "ai",
+            "langchain4j",
+            "docling",
+            "finance"
+        ],
+        "bundled": false,
+        "requiresDocker": true,
+        "hasCitrusTests": false,
+        "files": [
+            "README.md",
+            "application.properties",
+            "examples/banking-sector-brief.pdf",
+            "examples/magnificent-seven-update.pdf",
+            "examples/semiconductor-sector-analysis.pdf",
+            "examples/tesla-q3-2024.pdf",
+            "financial-doc-analyzer.yaml"
+        ],
+        "infraServices": [
+            "docling",
+            "ollama"
+        ]
+    },
+    {
+        "name": "ai/openai-pii-redaction",
+        "title": "OpenAI PII Redaction",
+        "description": "Redact personal identifiable information from text 
using OpenAI-compatible LLMs",
+        "level": "advanced",
+        "tags": [
+            "ai",
+            "openai",
+            "privacy",
+            "security"
         ],
         "bundled": false,
         "requiresDocker": false,
-        "hasCitrusTests": true,
+        "hasCitrusTests": false,
         "files": [
             "README.md",
             "application.properties",
-            "aws-s3-cdc-log.camel.yaml",
-            "example-file.txt",
-            "policy-queue.json",
-            "terraform/main.tf"
+            "pii-redaction.camel.yaml",
+            "pii.schema.json"
         ]
     },
     {
-        "name": "aws/aws-sqs",
-        "title": "AWS SQS Sink",
-        "description": "Push messages to AWS SQS queue via HTTP service",
+        "name": "ai/smart-log-analyzer",
+        "title": "Smart Log Analyzer",
+        "description": "Intelligent observability correlating OpenTelemetry 
logs and traces with LLM analysis",
         "level": "advanced",
         "tags": [
-            "aws",
-            "sqs",
-            "http",
-            "cloud"
+            "ai",
+            "observability",
+            "opentelemetry",
+            "logging"
         ],
         "bundled": false,
         "requiresDocker": false,
         "hasCitrusTests": true,
         "files": [
             "README.md",
-            "application.properties",
-            "http-to-aws-sqs.camel.yaml"
+            "analyzer/application-dev.properties",
+            "analyzer/error-analyzer.camel.yaml",
+            "containers/caches/infinispan-events-config.json",
+            "containers/caches/infinispan-events-to-process-config.json",
+            "containers/otel-collector-config.yaml",
+            "correlator/application-dev.properties",
+            "correlator/correlated-log-schema.json",
+            "correlator/correlated-trace-schema.json",
+            "correlator/infinispan.camel.yaml",
+            "correlator/kafka-ca-cert.pem",
+            "correlator/kaoto-datamapper-4a94acc3.xsl",
+            "correlator/kaoto-datamapper-8f5bb2dd.xsl",
+            "correlator/logs-mapper.camel.yaml",
+            "correlator/otel-log-record-schema.json",
+            "correlator/otel-logs-schema.json",
+            "correlator/otel-span-schema.json",
+            "correlator/otel-traces-schema.json",
+            "correlator/traces-mapper.camel.yaml",
+            "first-iteration/analyzer.camel.yaml",
+            "first-iteration/application.properties",
+            "first-iteration/load-generator.camel.yaml",
+            "log-generator/agent.properties",
+            "log-generator/application-dev.properties",
+            "log-generator/log-generator.camel.yaml",
+            "log-generator/opentelemetry-javaagent.jar",
+            "ui-console/application-dev.properties",
+            "ui-console/index.html",
+            "ui-console/jms-file-storage.camel.yaml",
+            "ui-console/rest-api.camel.yaml"
         ]
     },
     {
-        "name": "camel-1-tribute",
+        "name": "beginner/camel-1-tribute",
         "title": "Camel 1.0 Tribute",
         "description": "A tribute to the very first Apache Camel example from 
2007 - JMS to File",
         "level": "beginner",
@@ -91,31 +145,73 @@
         ]
     },
     {
-        "name": "circuit-breaker",
-        "title": "Circuit Breaker",
-        "description": "Use the circuit breaker EIP for fault tolerance",
+        "name": "beginner/cron-log",
+        "title": "Cron Log",
+        "description": "Scheduled task that logs every 5 seconds",
         "level": "beginner",
         "tags": [
-            "eip",
-            "resilience"
+            "beginner",
+            "timer",
+            "cron",
+            "log"
         ],
         "bundled": true,
         "requiresDocker": false,
         "hasCitrusTests": false,
         "files": [
             "README.md",
-            "route.camel.yaml"
+            "application.properties",
+            "cron-log.camel.yaml"
         ]
     },
     {
-        "name": "cron-log",
-        "title": "Cron Log",
-        "description": "Scheduled task that logs every 5 seconds",
+        "name": "beginner/rest-api",
+        "title": "REST API",
+        "description": "REST API with hello endpoints",
+        "level": "beginner",
+        "tags": [
+            "beginner",
+            "rest",
+            "http"
+        ],
+        "bundled": true,
+        "requiresDocker": false,
+        "hasCitrusTests": false,
+        "files": [
+            "README.md",
+            "application.properties",
+            "rest-api.camel.yaml"
+        ]
+    },
+    {
+        "name": "beginner/routes",
+        "title": "Routes",
+        "description": "Define routes in YAML with Java beans",
+        "level": "beginner",
+        "tags": [
+            "beginner",
+            "yaml",
+            "bean"
+        ],
+        "bundled": true,
+        "requiresDocker": false,
+        "hasCitrusTests": false,
+        "files": [
+            "Greeter.java",
+            "README.md",
+            "application.properties",
+            "beans.yaml",
+            "routes.camel.yaml"
+        ]
+    },
+    {
+        "name": "beginner/timer-log",
+        "title": "Timer Log",
+        "description": "Simple timer that logs a hello message every second",
         "level": "beginner",
         "tags": [
             "beginner",
             "timer",
-            "cron",
             "log"
         ],
         "bundled": true,
@@ -124,88 +220,167 @@
         "files": [
             "README.md",
             "application.properties",
-            "cron-log.camel.yaml"
+            "timer-log.camel.yaml"
         ]
     },
     {
-        "name": "docling-langchain4j-rag",
-        "title": "Document Analysis with Docling and LangChain4j RAG",
-        "description": "RAG workflow combining Docling document conversion 
with LangChain4j and Ollama",
+        "name": "beginner/tui-hello-world",
+        "title": "TUI Hello World",
+        "description": "Say hello via TUI Send Message (F2) or CLI",
+        "level": "beginner",
+        "tags": [
+            "beginner",
+            "direct",
+            "send",
+            "tui"
+        ],
+        "bundled": true,
+        "requiresDocker": false,
+        "hasCitrusTests": false,
+        "files": [
+            "README.md",
+            "tui-hello-world.yaml"
+        ]
+    },
+    {
+        "name": "cloud/aws-s3-event-based",
+        "title": "AWS S3 CDC",
+        "description": "Consume S3 events using EventBridge and SQS for change 
data capture",
         "level": "advanced",
         "tags": [
-            "ai",
-            "rag",
-            "langchain4j",
-            "docling"
+            "aws",
+            "s3",
+            "sqs",
+            "eventbridge",
+            "cloud"
         ],
         "bundled": false,
-        "requiresDocker": true,
-        "hasCitrusTests": false,
+        "requiresDocker": false,
+        "hasCitrusTests": true,
         "files": [
             "README.md",
             "application.properties",
-            "docling-langchain4j-rag.yaml",
-            "sample.md"
-        ],
-        "infraServices": [
-            "docling",
-            "ollama"
+            "aws-s3-cdc-log.camel.yaml",
+            "example-file.txt",
+            "policy-queue.json",
+            "terraform/main.tf"
         ]
     },
     {
-        "name": "financial-doc-analyzer",
-        "title": "Financial Document Analyzer",
-        "description": "Automated financial document analysis with Docling, 
LangChain4j, and market data",
+        "name": "cloud/aws-sqs",
+        "title": "AWS SQS Sink",
+        "description": "Push messages to AWS SQS queue via HTTP service",
         "level": "advanced",
         "tags": [
-            "ai",
-            "langchain4j",
-            "docling",
-            "finance"
+            "aws",
+            "sqs",
+            "http",
+            "cloud"
         ],
         "bundled": false,
-        "requiresDocker": true,
-        "hasCitrusTests": false,
+        "requiresDocker": false,
+        "hasCitrusTests": true,
         "files": [
             "README.md",
             "application.properties",
-            "examples/banking-sector-brief.pdf",
-            "examples/magnificent-seven-update.pdf",
-            "examples/semiconductor-sector-analysis.pdf",
-            "examples/tesla-q3-2024.pdf",
-            "financial-doc-analyzer.yaml"
-        ],
-        "infraServices": [
-            "docling",
-            "ollama"
+            "http-to-aws-sqs.camel.yaml"
         ]
     },
     {
-        "name": "ftp",
-        "title": "ActiveMQ to FTP",
-        "description": "Integrate ActiveMQ messaging with an FTP server",
+        "name": "database/sql",
+        "title": "SQL Database",
+        "description": "Use a SQL database with Camel and Postgres",
         "level": "intermediate",
         "tags": [
-            "messaging",
-            "ftp",
-            "activemq"
+            "database",
+            "sql",
+            "postgres"
         ],
-        "bundled": false,
+        "bundled": true,
         "requiresDocker": true,
-        "hasCitrusTests": true,
+        "hasCitrusTests": false,
         "files": [
             "README.md",
             "application.properties",
-            "ftp.camel.yaml",
-            "jbang.properties"
+            "sql.camel.yaml"
         ],
         "infraServices": [
-            "artemis",
-            "ftp"
+            "postgres"
+        ]
+    },
+    {
+        "name": "eip/aggregator",
+        "title": "Aggregator",
+        "description": "Collect individual messages into a batch using the 
Aggregator EIP",
+        "level": "beginner",
+        "tags": [
+            "eip",
+            "aggregator",
+            "batch"
+        ],
+        "bundled": true,
+        "requiresDocker": false,
+        "hasCitrusTests": false,
+        "files": [
+            "README.md",
+            "aggregator.camel.yaml"
+        ]
+    },
+    {
+        "name": "eip/circuit-breaker",
+        "title": "Circuit Breaker",
+        "description": "Use the circuit breaker EIP for fault tolerance",
+        "level": "beginner",
+        "tags": [
+            "eip",
+            "resilience"
+        ],
+        "bundled": true,
+        "requiresDocker": false,
+        "hasCitrusTests": false,
+        "files": [
+            "README.md",
+            "route.camel.yaml"
+        ]
+    },
+    {
+        "name": "eip/content-based-router",
+        "title": "Content Based Router",
+        "description": "Route messages to different destinations based on 
message content using the Choice EIP",
+        "level": "beginner",
+        "tags": [
+            "eip",
+            "choice",
+            "routing"
+        ],
+        "bundled": true,
+        "requiresDocker": false,
+        "hasCitrusTests": false,
+        "files": [
+            "README.md",
+            "content-based-router.camel.yaml"
+        ]
+    },
+    {
+        "name": "eip/splitter",
+        "title": "Splitter",
+        "description": "Split a batch of items into individual messages for 
processing",
+        "level": "beginner",
+        "tags": [
+            "eip",
+            "splitter",
+            "batch"
+        ],
+        "bundled": true,
+        "requiresDocker": false,
+        "hasCitrusTests": false,
+        "files": [
+            "README.md",
+            "splitter.camel.yaml"
         ]
     },
     {
-        "name": "groovy",
+        "name": "language/groovy",
         "title": "Groovy",
         "description": "Use Groovy with extra dependencies and content-based 
routing",
         "level": "beginner",
@@ -224,15 +399,14 @@
         ]
     },
     {
-        "name": "keycloak-introspection-rest",
-        "title": "Keycloak Token Introspection REST API",
-        "description": "Secure REST APIs with Keycloak OAuth 2.0 token 
introspection",
-        "level": "advanced",
+        "name": "messaging/artemis",
+        "title": "Apache ActiveMQ Artemis",
+        "description": "Setup connection factory to a remote Apache ActiveMQ 
Artemis messaging broker",
+        "level": "intermediate",
         "tags": [
-            "security",
-            "keycloak",
-            "rest",
-            "oauth"
+            "messaging",
+            "jms",
+            "artemis"
         ],
         "bundled": false,
         "requiresDocker": true,
@@ -240,37 +414,63 @@
         "files": [
             "README.md",
             "application.properties",
-            "rest-api.camel.yaml"
+            "consumer.camel.yaml",
+            "producer.camel.yaml"
         ],
         "infraServices": [
-            "keycloak"
+            "artemis"
         ]
     },
     {
-        "name": "keycloak-security-rest",
-        "title": "Keycloak Security REST API",
-        "description": "Secure REST APIs with Keycloak authentication and 
authorization",
-        "level": "advanced",
+        "name": "messaging/ftp",
+        "title": "ActiveMQ to FTP",
+        "description": "Integrate ActiveMQ messaging with an FTP server",
+        "level": "intermediate",
         "tags": [
-            "security",
-            "keycloak",
-            "rest",
-            "oauth"
+            "messaging",
+            "ftp",
+            "activemq"
         ],
         "bundled": false,
         "requiresDocker": true,
-        "hasCitrusTests": false,
+        "hasCitrusTests": true,
         "files": [
             "README.md",
             "application.properties",
-            "rest-api.camel.yaml"
+            "ftp.camel.yaml",
+            "jbang.properties"
         ],
         "infraServices": [
-            "keycloak"
+            "artemis",
+            "ftp"
+        ]
+    },
+    {
+        "name": "messaging/mqtt",
+        "title": "MQTT",
+        "description": "Receive MQTT events from an external MQTT broker",
+        "level": "intermediate",
+        "tags": [
+            "messaging",
+            "mqtt",
+            "iot"
+        ],
+        "bundled": false,
+        "requiresDocker": true,
+        "hasCitrusTests": true,
+        "files": [
+            "README.md",
+            "application.properties",
+            "infra/mosquitto.conf",
+            "mqtt.camel.yaml",
+            "start.sh"
+        ],
+        "infraServices": [
+            "mosquitto"
         ]
     },
     {
-        "name": "memory-leak",
+        "name": "observability/memory-leak",
         "title": "Memory Leak",
         "description": "Simulates a memory leak for testing JFR Old Object 
Sample diagnostics",
         "level": "beginner",
@@ -289,7 +489,7 @@
         ]
     },
     {
-        "name": "message-size",
+        "name": "observability/message-size",
         "title": "Message Size",
         "description": "Track message body and header sizes per endpoint",
         "level": "beginner",
@@ -307,52 +507,30 @@
         ]
     },
     {
-        "name": "mqtt",
-        "title": "MQTT",
-        "description": "Receive MQTT events from an external MQTT broker",
+        "name": "observability/route-topology",
+        "title": "Route Topology",
+        "description": "Demonstrates inter-route topology with triggers, 
shared routes, and external systems",
         "level": "intermediate",
         "tags": [
-            "messaging",
-            "mqtt",
-            "iot"
+            "topology",
+            "direct",
+            "kafka",
+            "timer"
         ],
-        "bundled": false,
+        "bundled": true,
         "requiresDocker": true,
-        "hasCitrusTests": true,
+        "hasCitrusTests": false,
         "files": [
             "README.md",
             "application.properties",
-            "infra/mosquitto.conf",
-            "mqtt.camel.yaml",
-            "start.sh"
+            "route-topology.camel.yaml"
         ],
         "infraServices": [
-            "mosquitto"
-        ]
-    },
-    {
-        "name": "openai/pii-redaction",
-        "title": "OpenAI PII Redaction",
-        "description": "Redact personal identifiable information from text 
using OpenAI-compatible LLMs",
-        "level": "advanced",
-        "tags": [
-            "ai",
-            "openai",
-            "privacy",
-            "security"
-        ],
-        "bundled": false,
-        "requiresDocker": false,
-        "hasCitrusTests": false,
-        "files": [
-            "README.md",
-            "application.properties",
-            "pii-redaction.camel.yaml",
-            "pii.schema.json"
+            "kafka"
         ]
     },
     {
-        "name": "openapi/client",
+        "name": "rest/openapi-client",
         "title": "OpenAPI Client",
         "description": "REST client generated from an OpenAPI specification",
         "level": "intermediate",
@@ -373,7 +551,7 @@
         ]
     },
     {
-        "name": "openapi/server",
+        "name": "rest/openapi-server",
         "title": "OpenAPI Server",
         "description": "REST service implemented from an OpenAPI 
specification",
         "level": "intermediate",
@@ -394,177 +572,53 @@
         ]
     },
     {
-        "name": "rest-api",
-        "title": "REST API",
-        "description": "REST API with hello endpoints",
-        "level": "beginner",
+        "name": "security/keycloak-introspection-rest",
+        "title": "Keycloak Token Introspection REST API",
+        "description": "Secure REST APIs with Keycloak OAuth 2.0 token 
introspection",
+        "level": "advanced",
         "tags": [
-            "beginner",
+            "security",
+            "keycloak",
             "rest",
-            "http"
-        ],
-        "bundled": true,
-        "requiresDocker": false,
-        "hasCitrusTests": false,
-        "files": [
-            "README.md",
-            "application.properties",
-            "rest-api.camel.yaml"
-        ]
-    },
-    {
-        "name": "route-topology",
-        "title": "Route Topology",
-        "description": "Demonstrates inter-route topology with triggers, 
shared routes, and external systems",
-        "level": "intermediate",
-        "tags": [
-            "topology",
-            "direct",
-            "kafka",
-            "timer"
+            "oauth"
         ],
-        "bundled": true,
+        "bundled": false,
         "requiresDocker": true,
         "hasCitrusTests": false,
         "files": [
             "README.md",
             "application.properties",
-            "route-topology.camel.yaml"
+            "rest-api.camel.yaml"
         ],
         "infraServices": [
-            "kafka"
-        ]
-    },
-    {
-        "name": "routes",
-        "title": "Routes",
-        "description": "Define routes in YAML with Java beans",
-        "level": "beginner",
-        "tags": [
-            "beginner",
-            "yaml",
-            "bean"
-        ],
-        "bundled": true,
-        "requiresDocker": false,
-        "hasCitrusTests": false,
-        "files": [
-            "Greeter.java",
-            "README.md",
-            "application.properties",
-            "beans.yaml",
-            "routes.camel.yaml"
+            "keycloak"
         ]
     },
     {
-        "name": "smart-log-analyzer",
-        "title": "Smart Log Analyzer",
-        "description": "Intelligent observability correlating OpenTelemetry 
logs and traces with LLM analysis",
+        "name": "security/keycloak-security-rest",
+        "title": "Keycloak Security REST API",
+        "description": "Secure REST APIs with Keycloak authentication and 
authorization",
         "level": "advanced",
         "tags": [
-            "ai",
-            "observability",
-            "opentelemetry",
-            "logging"
+            "security",
+            "keycloak",
+            "rest",
+            "oauth"
         ],
         "bundled": false,
-        "requiresDocker": false,
-        "hasCitrusTests": true,
-        "files": [
-            "README.md",
-            "analyzer/application-dev.properties",
-            "analyzer/error-analyzer.camel.yaml",
-            "containers/caches/infinispan-events-config.json",
-            "containers/caches/infinispan-events-to-process-config.json",
-            "containers/otel-collector-config.yaml",
-            "correlator/application-dev.properties",
-            "correlator/correlated-log-schema.json",
-            "correlator/correlated-trace-schema.json",
-            "correlator/infinispan.camel.yaml",
-            "correlator/kafka-ca-cert.pem",
-            "correlator/kaoto-datamapper-4a94acc3.xsl",
-            "correlator/kaoto-datamapper-8f5bb2dd.xsl",
-            "correlator/logs-mapper.camel.yaml",
-            "correlator/otel-log-record-schema.json",
-            "correlator/otel-logs-schema.json",
-            "correlator/otel-span-schema.json",
-            "correlator/otel-traces-schema.json",
-            "correlator/traces-mapper.camel.yaml",
-            "first-iteration/analyzer.camel.yaml",
-            "first-iteration/application.properties",
-            "first-iteration/load-generator.camel.yaml",
-            "log-generator/agent.properties",
-            "log-generator/application-dev.properties",
-            "log-generator/log-generator.camel.yaml",
-            "log-generator/opentelemetry-javaagent.jar",
-            "ui-console/application-dev.properties",
-            "ui-console/index.html",
-            "ui-console/jms-file-storage.camel.yaml",
-            "ui-console/rest-api.camel.yaml"
-        ]
-    },
-    {
-        "name": "sql",
-        "title": "SQL Database",
-        "description": "Use a SQL database with Camel and Postgres",
-        "level": "intermediate",
-        "tags": [
-            "database",
-            "sql",
-            "postgres"
-        ],
-        "bundled": true,
         "requiresDocker": true,
         "hasCitrusTests": false,
         "files": [
             "README.md",
             "application.properties",
-            "sql.camel.yaml"
+            "rest-api.camel.yaml"
         ],
         "infraServices": [
-            "postgres"
-        ]
-    },
-    {
-        "name": "timer-log",
-        "title": "Timer Log",
-        "description": "Simple timer that logs a hello message every second",
-        "level": "beginner",
-        "tags": [
-            "beginner",
-            "timer",
-            "log"
-        ],
-        "bundled": true,
-        "requiresDocker": false,
-        "hasCitrusTests": false,
-        "files": [
-            "README.md",
-            "application.properties",
-            "timer-log.camel.yaml"
-        ]
-    },
-    {
-        "name": "tui-hello-world",
-        "title": "TUI Hello World",
-        "description": "Say hello via TUI Send Message (F2) or CLI",
-        "level": "beginner",
-        "tags": [
-            "beginner",
-            "direct",
-            "send",
-            "tui"
-        ],
-        "bundled": true,
-        "requiresDocker": false,
-        "hasCitrusTests": false,
-        "files": [
-            "README.md",
-            "tui-hello-world.yaml"
+            "keycloak"
         ]
     },
     {
-        "name": "xslt",
+        "name": "transformation/xslt",
         "title": "XSLT Transformation",
         "description": "Basic XML transformation using XSLT style sheets",
         "level": "beginner",
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/sql/README.md 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/database/sql/README.md
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/sql/README.md
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/database/sql/README.md
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/sql/application.properties
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/database/sql/application.properties
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/sql/application.properties
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/database/sql/application.properties
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/sql/sql.camel.yaml
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/database/sql/sql.camel.yaml
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/sql/sql.camel.yaml
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/database/sql/sql.camel.yaml
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/eip/aggregator/README.md
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/eip/aggregator/README.md
new file mode 100644
index 000000000000..2f66737101fe
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/eip/aggregator/README.md
@@ -0,0 +1,47 @@
+## Aggregator
+
+This example demonstrates the Aggregator EIP.
+
+Individual order messages are collected into batches of 5 using the Aggregator 
EIP.
+The `StringAggregationStrategy` joins message bodies with a comma delimiter.
+
+A timer generates one order per second. The aggregator collects 5 orders before
+releasing the batch, which is then logged as a single combined message.
+
+### Install JBang
+
+First install JBang according to https://www.jbang.dev
+
+When JBang is installed then you should be able to run from a shell:
+
+```sh
+$ jbang --version
+```
+
+This will output the version of JBang.
+
+To run this example you can either install Camel on JBang via:
+
+```sh
+$ jbang app install camel@apache/camel
+```
+
+Which allows to run Camel CLI with `camel` as shown below.
+
+### How to run
+
+You can run this example using:
+
+```sh
+$ camel run *
+```
+
+### Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/eip/aggregator/aggregator.camel.yaml
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/eip/aggregator/aggregator.camel.yaml
new file mode 100644
index 000000000000..45a750c03a9b
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/eip/aggregator/aggregator.camel.yaml
@@ -0,0 +1,37 @@
+- beans:
+    - name: myAggregationStrategy
+      type: 
"#class:org.apache.camel.processor.aggregate.StringAggregationStrategy"
+      properties:
+        delimiter: ", "
+
+- route:
+    id: order-generator
+    from:
+      uri: timer
+      parameters:
+        timerName: orders
+        period: 1000
+      steps:
+        - setBody:
+            expression:
+              simple:
+                expression: "Order-${exchangeProperty.CamelTimerCounter}"
+        - log:
+            message: "New: ${body}"
+        - to:
+            uri: direct:aggregate
+
+- route:
+    id: order-aggregator
+    from:
+      uri: direct:aggregate
+      steps:
+        - aggregate:
+            aggregationStrategy: "#bean:myAggregationStrategy"
+            correlationExpression:
+              constant:
+                expression: "true"
+            completionSize: 5
+            steps:
+              - log:
+                  message: "Batch of 5: ${body}"
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/circuit-breaker/README.md
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/eip/circuit-breaker/README.md
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/circuit-breaker/README.md
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/eip/circuit-breaker/README.md
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/circuit-breaker/route.camel.yaml
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/eip/circuit-breaker/route.camel.yaml
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/circuit-breaker/route.camel.yaml
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/eip/circuit-breaker/route.camel.yaml
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/eip/content-based-router/README.md
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/eip/content-based-router/README.md
new file mode 100644
index 000000000000..d030d8dde2ad
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/eip/content-based-router/README.md
@@ -0,0 +1,48 @@
+## Content Based Router
+
+This example demonstrates the Content Based Router EIP.
+
+A simulated temperature sensor generates random readings between 0 and 40.
+The route uses the `choice` EIP to classify each reading:
+
+- **Hot** (>= 30): logged as an alert
+- **Normal** (15-29): logged as normal
+- **Cold** (< 15): logged as an alert
+
+### Install JBang
+
+First install JBang according to https://www.jbang.dev
+
+When JBang is installed then you should be able to run from a shell:
+
+```sh
+$ jbang --version
+```
+
+This will output the version of JBang.
+
+To run this example you can either install Camel on JBang via:
+
+```sh
+$ jbang app install camel@apache/camel
+```
+
+Which allows to run Camel CLI with `camel` as shown below.
+
+### How to run
+
+You can run this example using:
+
+```sh
+$ camel run *
+```
+
+### Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/eip/content-based-router/content-based-router.camel.yaml
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/eip/content-based-router/content-based-router.camel.yaml
new file mode 100644
index 000000000000..698c2b1c39dc
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/eip/content-based-router/content-based-router.camel.yaml
@@ -0,0 +1,45 @@
+- route:
+    id: sensor
+    from:
+      uri: timer
+      parameters:
+        timerName: sensor
+        period: 1000
+      steps:
+        - setBody:
+            expression:
+              simple:
+                expression: "${random(0,40)}"
+        - convertBodyTo:
+            type: int
+        - choice:
+            when:
+              - simple:
+                  expression: "${body} >= 30"
+                steps:
+                  - setHeader:
+                      name: level
+                      expression:
+                        constant:
+                          expression: hot
+                  - log:
+                      message: "Hot alert: ${body} C"
+              - simple:
+                  expression: "${body} >= 15"
+                steps:
+                  - setHeader:
+                      name: level
+                      expression:
+                        constant:
+                          expression: normal
+                  - log:
+                      message: "Normal: ${body} C"
+            otherwise:
+              steps:
+                - setHeader:
+                    name: level
+                    expression:
+                      constant:
+                        expression: cold
+                - log:
+                    message: "Cold alert: ${body} C"
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/eip/splitter/README.md
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/eip/splitter/README.md
new file mode 100644
index 000000000000..ea673ccfe3aa
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/eip/splitter/README.md
@@ -0,0 +1,47 @@
+## Splitter
+
+This example demonstrates the Splitter EIP.
+
+A batch of comma-separated product names is split into individual messages.
+Each item is logged with its split index.
+
+The splitter processes each part of the message independently, which is useful
+for breaking down bulk data into individual records for downstream processing.
+
+### Install JBang
+
+First install JBang according to https://www.jbang.dev
+
+When JBang is installed then you should be able to run from a shell:
+
+```sh
+$ jbang --version
+```
+
+This will output the version of JBang.
+
+To run this example you can either install Camel on JBang via:
+
+```sh
+$ jbang app install camel@apache/camel
+```
+
+Which allows to run Camel CLI with `camel` as shown below.
+
+### How to run
+
+You can run this example using:
+
+```sh
+$ camel run *
+```
+
+### Help and contributions
+
+If you hit any problem using Camel or have some feedback, then please
+[let us know](https://camel.apache.org/community/support/).
+
+We also love contributors, so
+[get involved](https://camel.apache.org/community/contributing/) :-)
+
+The Camel riders!
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/eip/splitter/splitter.camel.yaml
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/eip/splitter/splitter.camel.yaml
new file mode 100644
index 000000000000..b87b5b4b4d56
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/eip/splitter/splitter.camel.yaml
@@ -0,0 +1,22 @@
+- route:
+    id: order-splitter
+    from:
+      uri: timer
+      parameters:
+        timerName: orders
+        period: 5000
+        repeatCount: 3
+      steps:
+        - setBody:
+            expression:
+              constant:
+                expression: "Laptop,Phone,Tablet,Monitor,Keyboard"
+        - log:
+            message: "Received batch: ${body}"
+        - split:
+            expression:
+              tokenize:
+                token: ","
+            steps:
+              - log:
+                  message: "Processing item ${headers.CamelSplitIndex}: 
${body}"
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/README.md 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/language/groovy/README.md
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/README.md
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/language/groovy/README.md
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/application.properties
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/language/groovy/application.properties
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/application.properties
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/language/groovy/application.properties
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/groovy.camel.yaml
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/language/groovy/groovy.camel.yaml
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/groovy.camel.yaml
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/language/groovy/groovy.camel.yaml
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/memory-leak/MemoryLeak.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/observability/memory-leak/MemoryLeak.java
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/memory-leak/MemoryLeak.java
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/observability/memory-leak/MemoryLeak.java
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/memory-leak/README.md
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/observability/memory-leak/README.md
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/memory-leak/README.md
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/observability/memory-leak/README.md
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/message-size/README.md
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/observability/message-size/README.md
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/message-size/README.md
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/observability/message-size/README.md
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/message-size/message-size.camel.yaml
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/observability/message-size/message-size.camel.yaml
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/message-size/message-size.camel.yaml
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/observability/message-size/message-size.camel.yaml
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/message-size/orders.camel.yaml
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/observability/message-size/orders.camel.yaml
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/message-size/orders.camel.yaml
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/observability/message-size/orders.camel.yaml
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route-topology/README.md
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/observability/route-topology/README.md
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route-topology/README.md
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/observability/route-topology/README.md
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route-topology/application.properties
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/observability/route-topology/application.properties
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route-topology/application.properties
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/observability/route-topology/application.properties
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route-topology/route-topology.camel.yaml
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/observability/route-topology/route-topology.camel.yaml
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route-topology/route-topology.camel.yaml
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/observability/route-topology/route-topology.camel.yaml
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/README.md 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transformation/xslt/README.md
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/README.md
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transformation/xslt/README.md
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/consumer.camel.yaml
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transformation/xslt/consumer.camel.yaml
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/consumer.camel.yaml
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transformation/xslt/consumer.camel.yaml
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/input/account.xml
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transformation/xslt/input/account.xml
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/input/account.xml
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transformation/xslt/input/account.xml
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/stylesheet.xsl
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transformation/xslt/stylesheet.xsl
similarity index 100%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/stylesheet.xsl
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/transformation/xslt/stylesheet.xsl
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunTest.java
index 707dffb184de..bf762ec03720 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunTest.java
@@ -81,7 +81,7 @@ class RunTest extends CamelCommandBaseTestSupport {
     @Test
     public void shouldSuggestSimilarExample() throws Exception {
         Run command = new Run(new CamelJBangMain().withPrinter(printer));
-        command.example = "circuit-brake";
+        command.example = "eip/circuit-brake";
         int exit = command.doCall();
 
         Assertions.assertEquals(1, exit);
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ExampleHelperTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ExampleHelperTest.java
index 09a8ce533be9..965c3417fa03 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ExampleHelperTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ExampleHelperTest.java
@@ -36,7 +36,7 @@ class ExampleHelperTest {
     @Test
     void shouldFindExample() {
         List<JsonObject> catalog = ExampleHelper.loadCatalog();
-        JsonObject entry = ExampleHelper.findExample(catalog, 
"circuit-breaker");
+        JsonObject entry = ExampleHelper.findExample(catalog, 
"eip/circuit-breaker");
         assertNotNull(entry);
         assertEquals("Circuit Breaker", entry.getString("title"));
     }
@@ -66,7 +66,7 @@ class ExampleHelperTest {
         List<JsonObject> catalog = ExampleHelper.loadCatalog();
         List<JsonObject> filtered = ExampleHelper.filterExamples(catalog, 
"mqtt");
         assertEquals(1, filtered.size());
-        assertEquals("mqtt", filtered.get(0).getString("name"));
+        assertEquals("messaging/mqtt", filtered.get(0).getString("name"));
     }
 
     @Test
@@ -79,27 +79,27 @@ class ExampleHelperTest {
     @Test
     void shouldDetectBundled() {
         List<JsonObject> catalog = ExampleHelper.loadCatalog();
-        JsonObject circuitBreaker = ExampleHelper.findExample(catalog, 
"circuit-breaker");
+        JsonObject circuitBreaker = ExampleHelper.findExample(catalog, 
"eip/circuit-breaker");
         assertTrue(ExampleHelper.isBundled(circuitBreaker));
 
-        JsonObject mqtt = ExampleHelper.findExample(catalog, "mqtt");
+        JsonObject mqtt = ExampleHelper.findExample(catalog, "messaging/mqtt");
         assertFalse(ExampleHelper.isBundled(mqtt));
     }
 
     @Test
     void shouldDetectDocker() {
         List<JsonObject> catalog = ExampleHelper.loadCatalog();
-        JsonObject mqtt = ExampleHelper.findExample(catalog, "mqtt");
+        JsonObject mqtt = ExampleHelper.findExample(catalog, "messaging/mqtt");
         assertTrue(ExampleHelper.requiresDocker(mqtt));
 
-        JsonObject circuitBreaker = ExampleHelper.findExample(catalog, 
"circuit-breaker");
+        JsonObject circuitBreaker = ExampleHelper.findExample(catalog, 
"eip/circuit-breaker");
         assertFalse(ExampleHelper.requiresDocker(circuitBreaker));
     }
 
     @Test
     void shouldGetFiles() {
         List<JsonObject> catalog = ExampleHelper.loadCatalog();
-        JsonObject routes = ExampleHelper.findExample(catalog, "routes");
+        JsonObject routes = ExampleHelper.findExample(catalog, 
"beginner/routes");
         List<String> files = ExampleHelper.getFiles(routes);
         assertTrue(files.contains("routes.camel.yaml"));
         assertTrue(files.contains("Greeter.java"));
@@ -109,7 +109,7 @@ class ExampleHelperTest {
     @Test
     void shouldExtractBundledExample() throws Exception {
         List<JsonObject> catalog = ExampleHelper.loadCatalog();
-        JsonObject entry = ExampleHelper.findExample(catalog, 
"circuit-breaker");
+        JsonObject entry = ExampleHelper.findExample(catalog, 
"eip/circuit-breaker");
         Path tempDir = ExampleHelper.extractBundledExample(entry);
 
         assertTrue(Files.exists(tempDir.resolve("route.camel.yaml")));
@@ -120,7 +120,7 @@ class ExampleHelperTest {
     @Test
     void shouldExtractBundledExampleWithSubdirectory() throws Exception {
         List<JsonObject> catalog = ExampleHelper.loadCatalog();
-        JsonObject entry = ExampleHelper.findExample(catalog, "xslt");
+        JsonObject entry = ExampleHelper.findExample(catalog, 
"transformation/xslt");
         Path tempDir = ExampleHelper.extractBundledExample(entry);
 
         assertTrue(Files.exists(tempDir.resolve("consumer.camel.yaml")));
@@ -131,26 +131,26 @@ class ExampleHelperTest {
     @Test
     void shouldGetGithubUrl() {
         List<JsonObject> catalog = ExampleHelper.loadCatalog();
-        JsonObject entry = ExampleHelper.findExample(catalog, "mqtt");
+        JsonObject entry = ExampleHelper.findExample(catalog, 
"messaging/mqtt");
         String url = ExampleHelper.getGithubUrl(entry);
-        
assertEquals("https://github.com/apache/camel-jbang-examples/tree/main/mqtt";, 
url);
+        
assertEquals("https://github.com/apache/camel-jbang-examples/tree/main/messaging/mqtt";,
 url);
     }
 
     @Test
     void shouldGetGithubUrlForNestedExample() {
         List<JsonObject> catalog = ExampleHelper.loadCatalog();
-        JsonObject entry = ExampleHelper.findExample(catalog, "aws/aws-sqs");
+        JsonObject entry = ExampleHelper.findExample(catalog, "cloud/aws-sqs");
         String url = ExampleHelper.getGithubUrl(entry);
-        
assertEquals("https://github.com/apache/camel-jbang-examples/tree/main/aws/aws-sqs";,
 url);
+        
assertEquals("https://github.com/apache/camel-jbang-examples/tree/main/cloud/aws-sqs";,
 url);
     }
 
     @Test
     void shouldDetectCitrusTests() {
         List<JsonObject> catalog = ExampleHelper.loadCatalog();
-        JsonObject mqtt = ExampleHelper.findExample(catalog, "mqtt");
+        JsonObject mqtt = ExampleHelper.findExample(catalog, "messaging/mqtt");
         assertTrue(ExampleHelper.hasCitrusTests(mqtt));
 
-        JsonObject circuitBreaker = ExampleHelper.findExample(catalog, 
"circuit-breaker");
+        JsonObject circuitBreaker = ExampleHelper.findExample(catalog, 
"eip/circuit-breaker");
         assertFalse(ExampleHelper.hasCitrusTests(circuitBreaker));
     }
 
@@ -158,8 +158,8 @@ class ExampleHelperTest {
     void shouldGetExampleNames() {
         List<JsonObject> catalog = ExampleHelper.loadCatalog();
         List<String> names = ExampleHelper.getExampleNames(catalog);
-        assertTrue(names.contains("circuit-breaker"));
-        assertTrue(names.contains("mqtt"));
-        assertTrue(names.contains("aws/aws-sqs"));
+        assertTrue(names.contains("eip/circuit-breaker"));
+        assertTrue(names.contains("messaging/mqtt"));
+        assertTrue(names.contains("cloud/aws-sqs"));
     }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ExampleBrowserPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ExampleBrowserPopup.java
index 9baded611241..335a203653f4 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ExampleBrowserPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ExampleBrowserPopup.java
@@ -19,7 +19,9 @@ package org.apache.camel.dsl.jbang.core.commands.tui;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.function.BiConsumer;
 
 import dev.tamboui.layout.Rect;
@@ -46,6 +48,8 @@ import org.apache.camel.util.json.JsonObject;
 
 class ExampleBrowserPopup {
 
+    private static final String BACK_MARKER = "__BACK__";
+
     private boolean visible;
     private final ListState listState = new ListState();
     private Rect popupRect;
@@ -53,6 +57,11 @@ class ExampleBrowserPopup {
     private List<JsonObject> catalog;
     private JsonObject selectedExample;
 
+    private String currentFolder;
+    private String currentLevel;
+    private int savedSelection;
+    private List<Object> itemData;
+
     private final DocViewerPopup docViewerPopup;
     private final LaunchManager launchManager;
     private Runnable burstCallback;
@@ -92,6 +101,7 @@ class ExampleBrowserPopup {
             notify("No examples found", true);
             return;
         }
+        currentFolder = null;
         visible = true;
         listState.select(1);
     }
@@ -102,7 +112,11 @@ class ExampleBrowserPopup {
 
     boolean handleKeyEvent(KeyEvent ke) {
         if (ke.isCancel()) {
-            close();
+            if (currentFolder != null) {
+                navigateBack();
+            } else {
+                close();
+            }
             return true;
         }
         if (ke.isUp()) {
@@ -122,7 +136,7 @@ class ExampleBrowserPopup {
             return true;
         }
         if (ke.isChar('r')) {
-            launchSelected();
+            activateSelected(false);
             return true;
         }
         if (ke.isChar('d')) {
@@ -130,7 +144,7 @@ class ExampleBrowserPopup {
             return true;
         }
         if (ke.isConfirm()) {
-            openNameInput();
+            activateSelected(true);
             return true;
         }
         return true;
@@ -184,7 +198,8 @@ class ExampleBrowserPopup {
             return;
         }
         int popupW = Math.min(100, area.width() - 4);
-        int popupH = Math.min(catalog.size() + 10, Math.min(22, area.height() 
- 4));
+        int visibleItems = Math.max(10, catalog.size() + 10);
+        int popupH = Math.min(visibleItems, Math.min(22, area.height() - 4));
         int x = area.left() + Math.max(0, (area.width() - popupW) / 2);
         int y = area.top() + 2;
         Rect popup = new Rect(x, y, Math.min(popupW, area.width()), 
Math.min(popupH, area.height() - 2));
@@ -193,6 +208,9 @@ class ExampleBrowserPopup {
         frame.renderWidget(Clear.INSTANCE, popup);
 
         List<ListItem> items = buildListItems(popupW - 4);
+        String title = currentFolder != null
+                ? " " + ExampleHelper.formatCategory(currentFolder) + " "
+                : " Run an Example (" + catalog.size() + ") ";
         ListWidget list = ListWidget.builder()
                 .items(items.toArray(ListItem[]::new))
                 .highlightStyle(Theme.selectionBg())
@@ -200,7 +218,7 @@ class ExampleBrowserPopup {
                 .scrollMode(ScrollMode.AUTO_SCROLL)
                 .block(Block.builder()
                         .borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                        .title(" Run an Example (" + catalog.size() + ") ")
+                        .title(title)
                         .build())
                 .build();
         frame.renderStatefulWidget(list, popup, listState);
@@ -209,28 +227,31 @@ class ExampleBrowserPopup {
     void renderFooter(List<Span> spans) {
         TuiHelper.hint(spans, "↑↓", "navigate");
         TuiHelper.hint(spans, "r", "run");
-        TuiHelper.hint(spans, "Enter", "run...");
+        TuiHelper.hint(spans, "Enter", currentFolder != null ? "run..." : 
"open/run...");
         TuiHelper.hint(spans, "d", "docs");
         TuiHelper.hintLast(spans, "Esc", "back");
     }
 
     SelectionContext getSelectionContext() {
-        if (!visible || catalog == null) {
+        if (!visible || itemData == null) {
             return null;
         }
         List<String> items = new ArrayList<>();
-        String currentLevel = null;
-        for (JsonObject ex : catalog) {
-            String level = ex.getStringOrDefault("level", "beginner");
-            if (!level.equals(currentLevel)) {
-                currentLevel = level;
-                items.add("── " + TuiHelper.capitalize(level) + " ──");
+        for (Object data : itemData) {
+            if (data == null) {
+                items.add("──");
+            } else if (BACK_MARKER.equals(data)) {
+                items.add("..");
+            } else if (data instanceof String folder) {
+                int slash = folder.indexOf('/');
+                String cat = slash > 0 ? folder.substring(slash + 1) : folder;
+                items.add(TuiIcons.FOLDER + " " + 
ExampleHelper.formatCategory(cat));
+            } else if (data instanceof JsonObject ex) {
+                items.add(ExampleHelper.getShortName(ex));
             }
-            items.add(ex.getStringOrDefault("name", ""));
         }
-        int total = countListItems();
         Integer sel = listState.selected();
-        return new SelectionContext("list", items, sel != null ? sel : -1, 
total, "Examples");
+        return new SelectionContext("list", items, sel != null ? sel : -1, 
itemData.size(), "Examples");
     }
 
     void doLaunch(String exampleName, String displayName, List<String> 
extraArgs) {
@@ -266,15 +287,40 @@ class ExampleBrowserPopup {
 
     // ---- Private ----
 
-    private void launchSelected() {
+    private void activateSelected(boolean withOptions) {
         Integer sel = listState.selected();
         if (sel == null || isSeparatorIndex(sel)) {
             return;
         }
-        JsonObject example = getExampleAtListIndex(sel);
-        if (example == null) {
-            return;
+        Object data = itemData.get(sel);
+        if (BACK_MARKER.equals(data)) {
+            navigateBack();
+        } else if (data instanceof String folder) {
+            enterFolder(folder);
+        } else if (data instanceof JsonObject example) {
+            if (withOptions) {
+                openNameInput(example);
+            } else {
+                launchExample(example);
+            }
         }
+    }
+
+    private void enterFolder(String folder) {
+        Integer sel = listState.selected();
+        savedSelection = sel != null ? sel : 0;
+        int slash = folder.indexOf('/');
+        currentLevel = folder.substring(0, slash);
+        currentFolder = folder.substring(slash + 1);
+        listState.select(0);
+    }
+
+    private void navigateBack() {
+        currentFolder = null;
+        listState.select(savedSelection);
+    }
+
+    private void launchExample(JsonObject example) {
         String exampleName = example.getStringOrDefault("name", "");
         visible = false;
 
@@ -292,15 +338,7 @@ class ExampleBrowserPopup {
         doLaunch(exampleName, exampleName, List.of());
     }
 
-    private void openNameInput() {
-        Integer sel = listState.selected();
-        if (sel == null || isSeparatorIndex(sel)) {
-            return;
-        }
-        JsonObject example = getExampleAtListIndex(sel);
-        if (example == null) {
-            return;
-        }
+    private void openNameInput(JsonObject example) {
         selectedExample = example;
         visible = false;
         if (onNameInputRequest != null) {
@@ -310,11 +348,11 @@ class ExampleBrowserPopup {
 
     private void loadDocFromExample() {
         Integer sel = listState.selected();
-        if (sel == null || isSeparatorIndex(sel)) {
+        if (sel == null || sel >= itemData.size()) {
             return;
         }
-        JsonObject example = getExampleAtListIndex(sel);
-        if (example == null) {
+        Object data = itemData.get(sel);
+        if (!(data instanceof JsonObject example)) {
             return;
         }
         String name = example.getStringOrDefault("name", "");
@@ -345,10 +383,10 @@ class ExampleBrowserPopup {
     }
 
     private void navigate(int direction) {
-        if (catalog == null || catalog.isEmpty()) {
+        if (itemData == null || itemData.isEmpty()) {
             return;
         }
-        int totalItems = countListItems();
+        int totalItems = itemData.size();
         Integer current = listState.selected();
         if (current == null) {
             current = 0;
@@ -375,127 +413,182 @@ class ExampleBrowserPopup {
         listState.select(next);
     }
 
-    private int countListItems() {
-        if (catalog == null) {
-            return 0;
-        }
-        int count = 0;
-        String currentLevel = null;
-        for (JsonObject ex : catalog) {
-            String level = ex.getStringOrDefault("level", "beginner");
-            if (!level.equals(currentLevel)) {
-                currentLevel = level;
-                count++;
-            }
-            count++;
-        }
-        return count + 2;
-    }
-
     private boolean isSeparatorIndex(int index) {
-        if (catalog == null) {
-            return false;
-        }
-        int pos = 0;
-        String currentLevel = null;
-        for (JsonObject ex : catalog) {
-            String level = ex.getStringOrDefault("level", "beginner");
-            if (!level.equals(currentLevel)) {
-                currentLevel = level;
-                if (pos == index) {
-                    return true;
-                }
-                pos++;
-            }
-            if (pos == index) {
-                return false;
-            }
-            pos++;
+        if (itemData == null || index < 0 || index >= itemData.size()) {
+            return true;
         }
-        return true;
+        return itemData.get(index) == null;
     }
 
-    private JsonObject getExampleAtListIndex(int index) {
-        if (catalog == null) {
-            return null;
-        }
-        int pos = 0;
-        String currentLevel = null;
-        for (JsonObject ex : catalog) {
-            String level = ex.getStringOrDefault("level", "beginner");
-            if (!level.equals(currentLevel)) {
-                currentLevel = level;
-                pos++;
-            }
-            if (pos == index) {
-                return ex;
-            }
-            pos++;
+    private List<ListItem> buildListItems(int width) {
+        if (currentFolder != null) {
+            return buildFolderItems(width);
         }
-        return null;
+        return buildTopLevelItems(width);
     }
 
-    private List<ListItem> buildListItems(int width) {
+    private List<ListItem> buildTopLevelItems(int width) {
         List<ListItem> items = new ArrayList<>();
         List<Integer> heights = new ArrayList<>();
-        String currentLevel = null;
+        List<Object> data = new ArrayList<>();
+
+        Map<String, List<JsonObject>> levelGroups = new LinkedHashMap<>();
+        for (String level : new String[] { "beginner", "intermediate", 
"advanced" }) {
+            levelGroups.put(level, new ArrayList<>());
+        }
         for (JsonObject ex : catalog) {
-            String level = ex.getStringOrDefault("level", "beginner");
-            if (!level.equals(currentLevel)) {
-                currentLevel = level;
-                String header = "── " + TuiHelper.capitalize(level) + " ──";
-                items.add(ListItem.from(header).style(Style.EMPTY.dim()));
-                heights.add(1);
+            String level = ex.getStringOrDefault("level", "intermediate");
+            levelGroups.computeIfAbsent(level, k -> new ArrayList<>()).add(ex);
+        }
+
+        boolean firstLevel = true;
+        for (Map.Entry<String, List<JsonObject>> group : 
levelGroups.entrySet()) {
+            List<JsonObject> entries = group.getValue();
+            if (entries.isEmpty()) {
+                continue;
             }
-            String name = ex.getStringOrDefault("name", "");
-            String desc = ex.getStringOrDefault("description", "");
-            boolean docker = ExampleHelper.requiresDocker(ex);
-            boolean bundled = ExampleHelper.isBundled(ex);
-            boolean citrus = ExampleHelper.hasCitrusTests(ex);
-            boolean infra = !ExampleHelper.getInfraServices(ex).isEmpty();
-
-            String icons = (bundled ? TuiIcons.BUNDLED : TuiIcons.ONLINE) + 
(docker ? TuiIcons.DOCKER : "  ")
-                           + (infra ? TuiIcons.INFRA : "  ") + (citrus ? 
TuiIcons.CITRUS : "  ");
-            int nameCol = Math.min(30, width / 3);
-            String padded = String.format("%-" + nameCol + "s", 
TuiHelper.truncate(name, nameCol));
-            String prefix = " " + icons + " " + padded + " ";
-            int descCol = Math.max(10, width - prefix.length());
-
-            Style style = bundled ? Style.EMPTY : Style.EMPTY.dim();
-            if (desc.length() <= descCol) {
-                items.add(ListItem.from(prefix + desc).style(style));
-                heights.add(1);
-            } else {
-                String indent = " ".repeat(prefix.length());
-                List<Line> lines = new ArrayList<>();
-                List<String> wrapped = TuiHelper.wrapWords(desc, descCol);
-                lines.add(Line.from(prefix + wrapped.get(0)));
-                for (int w = 1; w < wrapped.size(); w++) {
-                    lines.add(Line.from(indent + wrapped.get(w)));
+            String levelName = group.getKey();
+
+            String label = " " + TuiHelper.capitalize(levelName) + " ";
+            int pad = Math.max(0, (width - label.length()) / 2);
+            String header = "─".repeat(pad) + label + "─".repeat(pad);
+            items.add(ListItem.from(header).style(Style.EMPTY.dim()));
+            heights.add(1);
+            data.add(null);
+
+            entries.sort((a, b) -> {
+                String catA = ExampleHelper.getCategory(a);
+                String catB = ExampleHelper.getCategory(b);
+                String sortA = catA.equals(levelName) ? "" : catA;
+                String sortB = catB.equals(levelName) ? "" : catB;
+                int cc = sortA.compareTo(sortB);
+                return cc != 0 ? cc : 
a.getString("name").compareTo(b.getString("name"));
+            });
+
+            Map<String, List<JsonObject>> categoryGroups = new 
LinkedHashMap<>();
+            for (JsonObject ex : entries) {
+                String cat = ExampleHelper.getCategory(ex);
+                categoryGroups.computeIfAbsent(cat, k -> new 
ArrayList<>()).add(ex);
+            }
+
+            for (Map.Entry<String, List<JsonObject>> catGroup : 
categoryGroups.entrySet()) {
+                String category = catGroup.getKey();
+                List<JsonObject> catEntries = catGroup.getValue();
+
+                if (category.equals(levelName)) {
+                    for (JsonObject ex : catEntries) {
+                        addExampleItem(items, heights, data, ex, width);
+                    }
+                } else {
+                    String folderLabel = " " + TuiIcons.FOLDER + " " + 
ExampleHelper.formatCategory(category)
+                                         + " (" + catEntries.size() + ")";
+                    items.add(ListItem.from(folderLabel));
+                    heights.add(1);
+                    data.add(levelName + "/" + category);
                 }
-                
items.add(ListItem.from(Text.from(lines.toArray(Line[]::new))).style(style));
-                heights.add(wrapped.size());
             }
         }
+
         items.add(ListItem.from(""));
         heights.add(1);
+        data.add(null);
         items.add(ListItem.from(" " + TuiIcons.BUNDLED + " = bundled  " + 
TuiIcons.ONLINE + " = online  "
                                 + TuiIcons.DOCKER + " = Docker  " + 
TuiIcons.INFRA + " = infra services  " + TuiIcons.CITRUS
                                 + " = Citrus tests")
                 .style(Style.EMPTY.dim()));
         heights.add(1);
+        data.add(null);
+
+        this.itemHeights = 
heights.stream().mapToInt(Integer::intValue).toArray();
+        this.itemData = data;
+        return items;
+    }
+
+    private List<ListItem> buildFolderItems(int width) {
+        List<ListItem> items = new ArrayList<>();
+        List<Integer> heights = new ArrayList<>();
+        List<Object> data = new ArrayList<>();
+
+        items.add(ListItem.from(" ..").style(Style.EMPTY.dim()));
+        heights.add(1);
+        data.add(BACK_MARKER);
+
+        for (JsonObject ex : catalog) {
+            String level = ex.getStringOrDefault("level", "intermediate");
+            if (currentFolder.equals(ExampleHelper.getCategory(ex)) && 
currentLevel.equals(level)) {
+                addExampleItem(items, heights, data, ex, width);
+            }
+        }
+
         this.itemHeights = 
heights.stream().mapToInt(Integer::intValue).toArray();
+        this.itemData = data;
         return items;
     }
 
+    private void addExampleItem(
+            List<ListItem> items, List<Integer> heights, List<Object> data,
+            JsonObject ex, int width) {
+        String name = ExampleHelper.getShortName(ex);
+        String desc = ex.getStringOrDefault("description", "");
+        boolean docker = ExampleHelper.requiresDocker(ex);
+        boolean bundled = ExampleHelper.isBundled(ex);
+        boolean citrus = ExampleHelper.hasCitrusTests(ex);
+        boolean infra = !ExampleHelper.getInfraServices(ex).isEmpty();
+
+        String icons = (bundled ? TuiIcons.BUNDLED : TuiIcons.ONLINE) + 
(docker ? TuiIcons.DOCKER : "  ")
+                       + (infra ? TuiIcons.INFRA : "  ") + (citrus ? 
TuiIcons.CITRUS : "  ");
+        int nameCol = Math.min(30, width / 3);
+        String padded = String.format("%-" + nameCol + "s", 
TuiHelper.truncate(name, nameCol));
+        String prefix = " " + icons + " " + padded + " ";
+        int descCol = Math.max(10, width - prefix.length());
+
+        Style style = bundled ? Style.EMPTY : Style.EMPTY.dim();
+        if (desc.length() <= descCol) {
+            items.add(ListItem.from(prefix + desc).style(style));
+            heights.add(1);
+        } else {
+            String indent = " ".repeat(prefix.length());
+            List<Line> lines = new ArrayList<>();
+            List<String> wrapped = TuiHelper.wrapWords(desc, descCol);
+            lines.add(Line.from(prefix + wrapped.get(0)));
+            for (int w = 1; w < wrapped.size(); w++) {
+                lines.add(Line.from(indent + wrapped.get(w)));
+            }
+            
items.add(ListItem.from(Text.from(lines.toArray(Line[]::new))).style(style));
+            heights.add(wrapped.size());
+        }
+        data.add(ex);
+    }
+
+    private int folderExampleCount(String folder) {
+        int count = 0;
+        for (JsonObject ex : catalog) {
+            String level = ex.getStringOrDefault("level", "intermediate");
+            if (folder.equals(ExampleHelper.getCategory(ex)) && 
currentLevel.equals(level)) {
+                count++;
+            }
+        }
+        return count;
+    }
+
     private static List<JsonObject> loadAndSortExamples() {
         List<JsonObject> list = ExampleHelper.loadCatalog();
         list.sort((a, b) -> {
-            int la = levelOrder(a.getStringOrDefault("level", "beginner"));
-            int lb = levelOrder(b.getStringOrDefault("level", "beginner"));
+            String levelA = a.getStringOrDefault("level", "beginner");
+            String levelB = b.getStringOrDefault("level", "beginner");
+            int la = levelOrder(levelA);
+            int lb = levelOrder(levelB);
             if (la != lb) {
                 return Integer.compare(la, lb);
             }
+            String catA = ExampleHelper.getCategory(a);
+            String catB = ExampleHelper.getCategory(b);
+            String sortCatA = catA.equals(levelA) ? "" : catA;
+            String sortCatB = catB.equals(levelB) ? "" : catB;
+            int cc = sortCatA.compareTo(sortCatB);
+            if (cc != 0) {
+                return cc;
+            }
             return a.getStringOrDefault("name", 
"").compareTo(b.getStringOrDefault("name", ""));
         });
         return list;

Reply via email to