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 ad13e3204682 CAMEL-25433: camel-jbang - camel run --example and the 
monitor show what an example needs before it runs (#27532)
ad13e3204682 is described below

commit ad13e320468225b13558fb64b37ed5afaabe6ffd
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Oct 8 08:18:01 2026 +0200

    CAMEL-25433: camel-jbang - camel run --example and the monitor show what an 
example needs before it runs (#27532)
    
    * CAMEL-25433: camel-jbang - camel run --example and the monitor show what 
an example needs before it runs
    
    Some examples need something the run does not do: a model pulled into
    Ollama, an API key, another example running first. Without it they fail on
    every message or at startup, and nothing said why before the run.
    
    The example catalog carries it (needs), and the tools now show it:
    camel run --example prints "Needs: ..." before it starts the example and in
    the group listing; the monitor shows it under the example in the example
    browser and in the notice when it starts; the MCP tools that list and run
    examples return it. The bundled catalog is synced from camel-jbang-examples
    for the concrete needs of three examples.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
    
    * CAMEL-25433: camel-jbang - the bundled example catalog leaves out 
showcase/smart-log-analyzer
    
    It is several apps, which camel run --example cannot start as one; it is
    excluded from the camel-jbang-examples catalog 
(apache/camel-jbang-examples#102)
    and stays in that repository with its README.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
    
    * CAMEL-25433: camel-jbang - read the needs of an example once
    
    Review follow-up.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
    
    ---------
    
    Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../apache/camel/dsl/jbang/core/commands/Run.java  | 11 ++++
 .../dsl/jbang/core/commands/ai/ToolRegistry.java   |  4 ++
 .../camel/dsl/jbang/core/common/ExampleHelper.java | 13 +++++
 .../examples/camel-jbang-example-catalog.json      | 64 +---------------------
 .../dsl/jbang/core/common/ExampleHelperTest.java   | 15 +++++
 .../dsl/jbang/core/commands/mcp/ExampleTools.java  |  4 +-
 .../core/commands/tui/ExampleBrowserPopup.java     | 15 ++++-
 .../jbang/core/commands/tui/TuiToolRegistry.java   | 11 ++++
 8 files changed, 72 insertions(+), 65 deletions(-)

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 597bce0c72d4..7b5035e48997 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
@@ -478,6 +478,12 @@ public class Run extends CamelCommand {
             if (!infra.isEmpty()) {
                 printer().println(pad + "needs: camel infra run " + 
String.join(" ", infra));
             }
+            String needs = ExampleHelper.getNeeds(entry);
+            if (needs != null) {
+                for (String line : ExampleHelper.wrap("needs: " + needs, width 
- indent)) {
+                    printer().println(pad + line);
+                }
+            }
             String teaches = ExampleHelper.getTeachesSummary(entry);
             for (String line : ExampleHelper.wrap(teaches, width - indent)) {
                 printer().println(pad + line);
@@ -567,6 +573,11 @@ public class Run extends CamelCommand {
     private int runExampleIn(JsonObject entry, Path dir) throws Exception {
         String eName = entry.getString("name");
         printer().println("Running example: " + eName);
+        String needs = ExampleHelper.getNeeds(entry);
+        if (needs != null) {
+            // what the example needs that the run does not do (a model to 
pull, a key to set): the user does it
+            printer().println("Needs: " + needs);
+        }
         if (exportRun || transformRun || spec == null) {
             for (String f : ExampleHelper.getFiles(entry)) {
                 files.add(dir.resolve(f).toString());
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
index 63c722cbb60d..2b9549d6f1ce 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
@@ -1112,6 +1112,10 @@ public final class ToolRegistry {
                             jo.put("tags", entry.get("tags"));
                             jo.put("teaches", ExampleHelper.getTeaches(entry));
                             jo.put("infraServices", 
ExampleHelper.getInfraServices(entry));
+                            String needs = ExampleHelper.getNeeds(entry);
+                            if (needs != null) {
+                                jo.put("needs", needs);
+                            }
                             jo.put("bundled", ExampleHelper.isBundled(entry));
                             jo.put("files", ExampleHelper.getFiles(entry));
                             results.add(jo);
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 3ffb45a09df4..357113f3d11c 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
@@ -420,6 +420,19 @@ public final class ExampleHelper {
         return citrus != null && citrus;
     }
 
+    /**
+     * What an example needs beyond its infra services, for the user to do 
before the run (a model to pull, an API key
+     * to set, another example to run first), as plain text; null when it 
needs nothing else.
+     */
+    public static String getNeeds(JsonObject entry) {
+        String needs = entry.getString("needs");
+        if (needs == null || needs.isBlank()) {
+            return null;
+        }
+        // the catalog writes it as Markdown for the README tables: `ollama 
pull granite4:3b`
+        return needs.replace("`", "").trim();
+    }
+
     @SuppressWarnings("unchecked")
     public static List<String> getInfraServices(JsonObject entry) {
         Collection<String> services = (Collection<String>) 
entry.get("infraServices");
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 2175a3b5fdc8..4c5c0274f615 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
@@ -90,7 +90,7 @@
         "infraServices": [
             "ollama"
         ],
-        "needs": "a local model",
+        "needs": "a local model: `ollama pull granite4:3b`",
         "ciSkip": true,
         "order": 1
     },
@@ -167,7 +167,7 @@
             "pii-redaction.camel.yaml",
             "pii.schema.json"
         ],
-        "needs": "an OpenAI-compatible API",
+        "needs": "an OpenAI-compatible API: set `OPENAI_API_KEY`",
         "ciSkip": true,
         "order": 3
     },
@@ -704,6 +704,7 @@
             "orders/order-1003.json",
             "stock-api.json"
         ],
+        "needs": "`contracts/openapi-server` running first",
         "order": 2
     },
     {
@@ -1292,65 +1293,6 @@
         ],
         "order": 3
     },
-    {
-        "name": "showcase/smart-log-analyzer",
-        "title": "Smart Log Analyzer",
-        "description": "A multi-part showcase: a load generator produces logs 
and traces, a correlator maps OpenTelemetry logs to traces, an LLM analyses the 
correlated records, and a small web console with a REST API shows the results.",
-        "level": "showcase",
-        "teaches": {
-            "components": [
-                "platform-http",
-                "jms",
-                "file",
-                "langchain4j-chat"
-            ],
-            "eips": [
-                "rest"
-            ]
-        },
-        "tags": [
-            "ai",
-            "observability",
-            "opentelemetry",
-            "logging"
-        ],
-        "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"
-        ],
-        "order": 4
-    },
     {
         "name": "showcase/tui-hello-world",
         "title": "TUI Hello World",
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 40a4e0fcfa60..de60251429cc 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
@@ -27,6 +27,21 @@ import static org.junit.jupiter.api.Assertions.*;
 
 class ExampleHelperTest {
 
+    @Test
+    void theNeedsOfAnExampleAreItsPlainText() {
+        // the catalog writes them as Markdown for the README tables
+        JsonObject entry = new JsonObject();
+        entry.put("needs", "a local model: `ollama pull granite4:3b`");
+        assertEquals("a local model: ollama pull granite4:3b", 
ExampleHelper.getNeeds(entry));
+        assertNull(ExampleHelper.getNeeds(new JsonObject()));
+
+        // and the bundled catalog has them for the examples that need 
something
+        List<JsonObject> catalog = ExampleHelper.loadCatalog();
+        JsonObject client = ExampleHelper.findExample(catalog, 
"contracts/openapi-client");
+        assertNotNull(client);
+        assertEquals("contracts/openapi-server running first", 
ExampleHelper.getNeeds(client));
+    }
+
     @Test
     void shouldLoadCatalog() {
         List<JsonObject> catalog = ExampleHelper.loadCatalog();
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ExampleTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ExampleTools.java
index 7811acb5a0df..d857ff0b2737 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ExampleTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ExampleTools.java
@@ -154,6 +154,7 @@ public class ExampleTools {
                 tags != null ? new ArrayList<>(tags) : List.of(),
                 ExampleHelper.getTeaches(entry),
                 ExampleHelper.getInfraServices(entry),
+                ExampleHelper.getNeeds(entry),
                 ExampleHelper.isBundled(entry),
                 ExampleHelper.requiresDocker(entry),
                 ExampleHelper.getFiles(entry));
@@ -169,7 +170,8 @@ public class ExampleTools {
 
     public record ExampleInfo(String name, String title, String description, 
String level, Integer order,
             List<String> tags, Map<String, List<String>> teaches, List<String> 
infraServices,
-            boolean bundled, boolean requiresDocker, List<String> files) {
+            // what the user does before the run, beyond the infra services: a 
model to pull, a key to set
+            String needs, boolean bundled, boolean requiresDocker, 
List<String> files) {
     }
 
     public record ExampleFileResult(String example, String file, String 
content, String githubUrl) {
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 30c36e9818df..5412bf82c3d6 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
@@ -298,7 +298,9 @@ class ExampleBrowserPopup {
             if (burstCallback != null) {
                 burstCallback.run();
             }
-            notify("Starting: " + displayName, false);
+            JsonObject example = catalog != null ? 
ExampleHelper.findExample(catalog, exampleName) : null;
+            String needs = example != null ? ExampleHelper.getNeeds(example) : 
null;
+            notify("Starting: " + displayName + (needs != null ? " (needs " + 
needs + ")" : ""), false);
         } catch (Exception e) {
             notify("Failed to start: " + exampleName + " - " + e.getMessage(), 
true);
         }
@@ -556,7 +558,8 @@ class ExampleBrowserPopup {
         int descCol = Math.max(10, width - prefix.length());
 
         Style style = bundled ? Style.EMPTY : Style.EMPTY.dim();
-        if (desc.length() <= descCol) {
+        String needs = ExampleHelper.getNeeds(ex);
+        if (desc.length() <= descCol && needs == null) {
             items.add(ListItem.from(prefix + desc).style(style));
             heights.add(1);
         } else {
@@ -567,8 +570,14 @@ class ExampleBrowserPopup {
             for (int w = 1; w < wrapped.size(); w++) {
                 lines.add(Line.from(indent + wrapped.get(w)));
             }
+            if (needs != null) {
+                // what the user does before the run (a model to pull, a key 
to set): the run does not do it
+                for (String w : TuiHelper.wrapWords("needs: " + needs, 
descCol)) {
+                    lines.add(Line.from(Span.raw(indent), Span.styled(w, 
Theme.warning())));
+                }
+            }
             
items.add(ListItem.from(Text.from(lines.toArray(Line[]::new))).style(style));
-            heights.add(wrapped.size());
+            heights.add(lines.size());
         }
         data.add(ex);
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java
index a11d076ce17d..4061f84e11e1 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java
@@ -1851,6 +1851,7 @@ class TuiToolRegistry {
                 ex.put("bundled", ExampleHelper.isBundled(entry));
                 ex.put("requiresDocker", ExampleHelper.requiresDocker(entry));
                 ex.put("infraServices", 
toJsonArray(ExampleHelper.getInfraServices(entry)));
+                putNeeds(ex, entry);
                 examples.add(ex);
             }
         }
@@ -1904,6 +1905,7 @@ class TuiToolRegistry {
                     });
             JsonObject result = new JsonObject();
             result.put("status", "starting_infra");
+            putNeeds(result, example);
             result.put("message", "Starting infra: " + String.join(", ", 
missing) + " → then: " + displayName);
             result.put("infraServices", toJsonArray(missing));
             return Jsoner.serialize(result);
@@ -1922,6 +1924,7 @@ class TuiToolRegistry {
         }
         JsonObject result = new JsonObject();
         result.put("name", name);
+        putNeeds(result, example);
         if (outcome == null) {
             result.put("status", "starting");
             result.put("message", "Still starting: " + name + " (a first run 
downloads its dependencies); "
@@ -1939,6 +1942,14 @@ class TuiToolRegistry {
         return Jsoner.serialize(result);
     }
 
+    /** What the example needs that the run does not do, for the agent to tell 
the user (or do it). */
+    private static void putNeeds(JsonObject result, JsonObject example) {
+        String needs = ExampleHelper.getNeeds(example);
+        if (needs != null) {
+            result.put("needs", needs);
+        }
+    }
+
     // how long tui_run_example waits for an example to start or fail
     static final long RUN_EXAMPLE_WAIT_MS = 30_000;
 

Reply via email to