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 072590ed4f97 CAMEL-23385: camel-diagram - Add watch option (#23121)
072590ed4f97 is described below

commit 072590ed4f9727b48273f727516f15365e598172
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon May 11 16:13:53 2026 +0200

    CAMEL-23385: camel-diagram - Add watch option (#23121)
    
    * CAMEL-23385: camel-diagram - Add watch option
    * CAMEL-23385: camel-diagram - Auto refresh and remove text tree dump (used 
internally for testing)
---
 .../main/camel-main-configuration-metadata.json    |   2 +-
 .../camel-diagram/src/main/docs/diagram.adoc       |  26 +----
 .../apache/camel/diagram/DiagramDevConsole.java    |  54 ++++------
 .../apache/camel/diagram/RouteDiagramRenderer.java |  10 +-
 .../camel/diagram/DiagramDevConsoleTest.java       |  11 --
 .../camel-main-configuration-metadata.json         |   2 +-
 core/camel-main/src/main/docs/main.adoc            |   2 +-
 .../camel/main/DefaultConfigurationProperties.java |   4 +-
 .../camel-jbang-cmd-route-diagram.adoc             |   3 +-
 .../META-INF/camel-jbang-commands-metadata.json    |   2 +-
 .../core/commands/action/ActionWatchCommand.java   |  15 ++-
 .../commands/action/CamelRouteDiagramAction.java   | 117 +++++++++++++--------
 12 files changed, 125 insertions(+), 123 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
index 64c944a9d65a..8cb1b1af58f7 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
@@ -55,7 +55,7 @@
     { "name": "camel.main.contextReloadEnabled", "required": false, 
"description": "Used for enabling context reloading. If enabled then Camel 
allow external systems such as security vaults (AWS secrets manager, etc.) to 
trigger refreshing Camel by updating property placeholders and reload all 
existing routes to take changes into effect.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": false, "secret": false },
     { "name": "camel.main.description", "required": false, "description": 
"Sets the description (intended for humans) of the Camel application.", 
"sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": 
"string", "javaType": "java.lang.String", "secret": false },
     { "name": "camel.main.devConsoleEnabled", "required": false, 
"description": "Whether to enable developer console (requires camel-console on 
classpath). The developer console is only for assisting during development. 
This is NOT for production usage.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": false, "secret": false, "security": 
"insecure:dev" },
-    { "name": "camel.main.dumpRoutes", "required": false, "description": "If 
dumping is enabled then Camel will during startup dump all loaded routes (incl 
rests and route templates) represented as XML\/YAML DSL into the log. This is 
intended for trouble shooting or to assist during development. Sensitive 
information that may be configured in the route endpoints could potentially be 
included in the dump output and is therefore not recommended being used for 
production usage. This require [...]
+    { "name": "camel.main.dumpRoutes", "required": false, "description": "If 
dumping is enabled then Camel will during startup dump all loaded routes (incl 
rests and route templates) represented as XML\/YAML DSL into the log. This is 
intended for trouble shooting or to assist during development. Sensitive 
information that may be configured in the route endpoints could potentially be 
included in the dump output and is therefore not recommended being used for 
production usage. This require [...]
     { "name": "camel.main.dumpRoutesGeneratedIds", "required": false, 
"description": "Whether to include auto generated IDs in the dumped output. 
Default is false.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": false, "secret": false },
     { "name": "camel.main.dumpRoutesInclude", "required": false, 
"description": "Controls what to include in output for route dumping. Possible 
values: all, routes, rests, routeConfigurations, routeTemplates, beans, 
dataFormats. Multiple values can be separated by comma. Default is routes.", 
"sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": 
"string", "javaType": "java.lang.String", "defaultValue": "routes", "secret": 
false },
     { "name": "camel.main.dumpRoutesLog", "required": false, "description": 
"Whether to log route dumps to Logger", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": true, "secret": false },
diff --git a/components/camel-diagram/src/main/docs/diagram.adoc 
b/components/camel-diagram/src/main/docs/diagram.adoc
index 814acfacb812..d4517d27a0c8 100644
--- a/components/camel-diagram/src/main/docs/diagram.adoc
+++ b/components/camel-diagram/src/main/docs/diagram.adoc
@@ -10,13 +10,11 @@
 *Since Camel {since}*
 
 The Diagram module provides route diagram rendering capabilities for Apache 
Camel routes.
-It can generate visual route diagrams as PNG images or text-based tree 
representations
-from route structure data.
+It can generate visual route diagrams as PNG images representations from route 
structure data.
 
 == Features
 
 * Render route diagrams as PNG images with colored nodes and scope boxes
-* Generate text-based tree diagrams for terminal output
 * Support for all Camel EIPs: choice, doTry/doCatch, filter, split, loop, 
multicast, and more
 * Scope boxes visually group branching and scoping EIPs
 * Multiple color themes: dark, light, transparent, or custom
@@ -70,16 +68,6 @@ BufferedImage image = renderer.renderDiagram(List.of(lr), 
lr.maxY + RouteDiagram
 ImageIO.write(image, "PNG", new File("diagram.png"));
 ----
 
-=== Text diagram
-
-For terminal or log output, generate a text-based tree:
-
-[source,java]
-----
-List<String> lines = renderer.printTextDiagram(routes);
-lines.forEach(System.out::println);
-----
-
 === With Camel JBang
 
 The diagram rendering is used by the `camel cmd route-diagram` command in 
Camel JBang:
@@ -96,11 +84,10 @@ The following built-in themes are available:
 * `dark` - dark background (default)
 * `light` - light background
 * `transparent` - transparent background
-* `text` - to use tree diagram instead of visual diagram
 
 Custom colors can be specified using the format:
 
-[source,text]
+[source,properties]
 ----
 bg=#1e1e1e:from=#2e7d32:to=#1565c0:eip=#8957e5:choice=#d29922
 ----
@@ -113,12 +100,3 @@ To use dark theme
 ----
 camel cmd route-diagram MyRoute.java --theme=dark
 ----
-
-To print textual tree diagram instead of visual
-
-[source,bash]
-----
-camel cmd route-diagram MyRoute.java --theme=tree
-----
-
-TIP: You can also show diagrams for an existing running integration by 
executing `camel cmd diagram`.
diff --git 
a/components/camel-diagram/src/main/java/org/apache/camel/diagram/DiagramDevConsole.java
 
b/components/camel-diagram/src/main/java/org/apache/camel/diagram/DiagramDevConsole.java
index 261ae7e50646..74265961117b 100644
--- 
a/components/camel-diagram/src/main/java/org/apache/camel/diagram/DiagramDevConsole.java
+++ 
b/components/camel-diagram/src/main/java/org/apache/camel/diagram/DiagramDevConsole.java
@@ -20,7 +20,6 @@ import java.awt.image.BufferedImage;
 import java.util.Map;
 import java.util.StringJoiner;
 
-import org.apache.camel.console.DevConsoleRegistry;
 import org.apache.camel.spi.RouteDiagramDumper;
 import org.apache.camel.spi.annotations.DevConsole;
 import org.apache.camel.support.PluginHelper;
@@ -36,7 +35,7 @@ public class DiagramDevConsole extends AbstractDevConsole {
     public static final String FILTER = "filter";
 
     /**
-     * Theme to use: dark, light, or text
+     * Theme to use: dark or light
      */
     public static final String THEME = "theme";
 
@@ -60,6 +59,11 @@ public class DiagramDevConsole extends AbstractDevConsole {
      */
     public static final String METRIC = "metric";
 
+    /**
+     * Whether to auto-refresh page every 5 seconds
+     */
+    public static final String AUTO_REFRESH = "autoRefresh";
+
     public DiagramDevConsole() {
         super("camel", "route-diagram", "Route Diagram", "Visual route 
diagrams");
     }
@@ -76,24 +80,24 @@ public class DiagramDevConsole extends AbstractDevConsole {
                 .parseInt(options.getOrDefault(NODE_WIDTH, "" + 
RouteDiagramLayoutEngine.DEFAULT_BOX_WIDTH).toString());
         String nodeLabel = (String) options.getOrDefault(NODE_LABEL, 
RouteDiagramDumper.NodeLabelMode.CODE.name());
         boolean metric = "true".equalsIgnoreCase((String) 
options.getOrDefault(METRIC, "true"));
+        boolean refresh = "true".equalsIgnoreCase((String) 
options.getOrDefault(AUTO_REFRESH, "true"));
 
-        // special for text
-        if ("text".equalsIgnoreCase(theme)) {
-            sj.add(renderTextTheme(filter));
-        } else {
-            try {
-                RouteDiagramDumper dumper = 
PluginHelper.getRouteDiagramDumper(getCamelContext());
-                BufferedImage image = dumper.dumpRoutesAsImage(filter, 
RouteDiagramDumper.Theme.valueOf(theme.toUpperCase()),
-                        metric, 
RouteDiagramDumper.NodeLabelMode.valueOf(nodeLabel.toUpperCase()), nodeWidth, 
fontSize);
-                String base64 = dumper.imageToBase64(image);
-                // For HTML embedding:
-                String html = String.format(
-                        "<html>\n<body>\n<img src=\"data:image/png;base64,%s\" 
alt=\"Route Diagram\">\n</body>\n</html>",
-                        base64);
-                sj.add(html);
-            } catch (Exception e) {
-                // ignore
+        try {
+            RouteDiagramDumper dumper = 
PluginHelper.getRouteDiagramDumper(getCamelContext());
+            BufferedImage image = dumper.dumpRoutesAsImage(filter, 
RouteDiagramDumper.Theme.valueOf(theme.toUpperCase()),
+                    metric, 
RouteDiagramDumper.NodeLabelMode.valueOf(nodeLabel.toUpperCase()), nodeWidth, 
fontSize);
+            String base64 = dumper.imageToBase64(image);
+            // For HTML embedding:
+            String html = String.format(
+                    "  <body>\n    <img src=\"data:image/png;base64,%s\" 
alt=\"Route Diagram\">\n  </body>\n",
+                    base64);
+            if (refresh) {
+                html = "<head><meta http-equiv=\"refresh\" 
content=\"5\"></head>\n" + html;
             }
+            html = "<html>\n" + html + "</html>\n";
+            sj.add(html);
+        } catch (Exception e) {
+            // ignore
         }
 
         return sj.toString();
@@ -124,18 +128,4 @@ public class DiagramDevConsole extends AbstractDevConsole {
         return root;
     }
 
-    private String renderTextTheme(String filter) {
-        final StringJoiner sj = new StringJoiner("\n");
-
-        org.apache.camel.console.DevConsole dc
-                = 
getCamelContext().getCamelContextExtension().getContextPlugin(DevConsoleRegistry.class)
-                        .resolveById("route-structure");
-        JsonObject root = (JsonObject) dc.call(MediaType.JSON, 
Map.of("filter", filter));
-        var routes = RouteDiagramHelper.parseRoutes(root);
-        RouteDiagramRenderer renderer = new RouteDiagramRenderer();
-        var lines = renderer.printTextDiagram(routes);
-        lines.forEach(sj::add);
-        sj.add("");
-        return sj.toString();
-    }
 }
diff --git 
a/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramRenderer.java
 
b/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramRenderer.java
index d261ffbbd135..89f4839c5158 100644
--- 
a/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramRenderer.java
+++ 
b/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramRenderer.java
@@ -451,11 +451,17 @@ public class RouteDiagramRenderer {
         };
     }
 
-    public List<String> printTextDiagram(List<RouteInfo> routes) {
+    /**
+     * Used for testing
+     */
+    List<String> printTextDiagram(List<RouteInfo> routes) {
         return printTextDiagram(routes, 
RouteDiagramLayoutEngine.NodeLabelMode.CODE);
     }
 
-    public List<String> printTextDiagram(List<RouteInfo> routes, 
RouteDiagramLayoutEngine.NodeLabelMode mode) {
+    /**
+     * Used for testing
+     */
+    List<String> printTextDiagram(List<RouteInfo> routes, 
RouteDiagramLayoutEngine.NodeLabelMode mode) {
         List<String> lines = new ArrayList<>();
         for (RouteInfo route : routes) {
             lines.add("");
diff --git 
a/components/camel-diagram/src/test/java/org/apache/camel/diagram/DiagramDevConsoleTest.java
 
b/components/camel-diagram/src/test/java/org/apache/camel/diagram/DiagramDevConsoleTest.java
index 017118151793..681a48877ad1 100644
--- 
a/components/camel-diagram/src/test/java/org/apache/camel/diagram/DiagramDevConsoleTest.java
+++ 
b/components/camel-diagram/src/test/java/org/apache/camel/diagram/DiagramDevConsoleTest.java
@@ -17,7 +17,6 @@
 package org.apache.camel.diagram;
 
 import java.util.Base64;
-import java.util.Map;
 
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.console.DevConsole;
@@ -74,16 +73,6 @@ class DiagramDevConsoleTest extends CamelTestSupport {
         assertThat(text).contains("data:image/png;base64,");
     }
 
-    @Test
-    void testTextOutputTextTheme() {
-        DevConsole console = resolveConsole();
-        String text = (String) console.call(DevConsole.MediaType.TEXT, 
Map.of(DiagramDevConsole.THEME, "text"));
-        assertThat(text).contains("Route: myRoute");
-        assertThat(text).contains("Route: otherRoute");
-        assertThat(text).contains("[from]");
-        assertThat(text).contains("[choice]");
-    }
-
     @Test
     void testJsonOutput() {
         System.setProperty("java.awt.headless", "true");
diff --git 
a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
 
b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
index 64c944a9d65a..8cb1b1af58f7 100644
--- 
a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
+++ 
b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
@@ -55,7 +55,7 @@
     { "name": "camel.main.contextReloadEnabled", "required": false, 
"description": "Used for enabling context reloading. If enabled then Camel 
allow external systems such as security vaults (AWS secrets manager, etc.) to 
trigger refreshing Camel by updating property placeholders and reload all 
existing routes to take changes into effect.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": false, "secret": false },
     { "name": "camel.main.description", "required": false, "description": 
"Sets the description (intended for humans) of the Camel application.", 
"sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": 
"string", "javaType": "java.lang.String", "secret": false },
     { "name": "camel.main.devConsoleEnabled", "required": false, 
"description": "Whether to enable developer console (requires camel-console on 
classpath). The developer console is only for assisting during development. 
This is NOT for production usage.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": false, "secret": false, "security": 
"insecure:dev" },
-    { "name": "camel.main.dumpRoutes", "required": false, "description": "If 
dumping is enabled then Camel will during startup dump all loaded routes (incl 
rests and route templates) represented as XML\/YAML DSL into the log. This is 
intended for trouble shooting or to assist during development. Sensitive 
information that may be configured in the route endpoints could potentially be 
included in the dump output and is therefore not recommended being used for 
production usage. This require [...]
+    { "name": "camel.main.dumpRoutes", "required": false, "description": "If 
dumping is enabled then Camel will during startup dump all loaded routes (incl 
rests and route templates) represented as XML\/YAML DSL into the log. This is 
intended for trouble shooting or to assist during development. Sensitive 
information that may be configured in the route endpoints could potentially be 
included in the dump output and is therefore not recommended being used for 
production usage. This require [...]
     { "name": "camel.main.dumpRoutesGeneratedIds", "required": false, 
"description": "Whether to include auto generated IDs in the dumped output. 
Default is false.", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": false, "secret": false },
     { "name": "camel.main.dumpRoutesInclude", "required": false, 
"description": "Controls what to include in output for route dumping. Possible 
values: all, routes, rests, routeConfigurations, routeTemplates, beans, 
dataFormats. Multiple values can be separated by comma. Default is routes.", 
"sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": 
"string", "javaType": "java.lang.String", "defaultValue": "routes", "secret": 
false },
     { "name": "camel.main.dumpRoutesLog", "required": false, "description": 
"Whether to log route dumps to Logger", "sourceType": 
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": true, "secret": false },
diff --git a/core/camel-main/src/main/docs/main.adoc 
b/core/camel-main/src/main/docs/main.adoc
index cb17c720af87..aabcf2682df1 100644
--- a/core/camel-main/src/main/docs/main.adoc
+++ b/core/camel-main/src/main/docs/main.adoc
@@ -49,7 +49,7 @@ The camel.main supports 131 options, which are listed below.
 | *camel.main.contextReloadEnabled* | Used for enabling context reloading. If 
enabled then Camel allow external systems such as security vaults (AWS secrets 
manager, etc.) to trigger refreshing Camel by updating property placeholders 
and reload all existing routes to take changes into effect. | false | boolean
 | *camel.main.description* | Sets the description (intended for humans) of the 
Camel application. |  | String
 | *camel.main.devConsoleEnabled* | Whether to enable developer console 
(requires camel-console on classpath). The developer console is only for 
assisting during development. This is NOT for production usage. | false | 
boolean
-| *camel.main.dumpRoutes* | If dumping is enabled then Camel will during 
startup dump all loaded routes (incl rests and route templates) represented as 
XML/YAML DSL into the log. This is intended for trouble shooting or to assist 
during development. Sensitive information that may be configured in the route 
endpoints could potentially be included in the dump output and is therefore not 
recommended being used for production usage. This requires to have 
camel-xml-io/camel-yaml-io on the cla [...]
+| *camel.main.dumpRoutes* | If dumping is enabled then Camel will during 
startup dump all loaded routes (incl rests and route templates) represented as 
XML/YAML DSL into the log. This is intended for trouble shooting or to assist 
during development. Sensitive information that may be configured in the route 
endpoints could potentially be included in the dump output and is therefore not 
recommended being used for production usage. This requires to have 
camel-xml-io/camel-yaml-io on the cla [...]
 | *camel.main.dumpRoutesGeneratedIds* | Whether to include auto generated IDs 
in the dumped output. Default is false. | false | boolean
 | *camel.main.dumpRoutesInclude* | Controls what to include in output for 
route dumping. Possible values: all, routes, rests, routeConfigurations, 
routeTemplates, beans, dataFormats. Multiple values can be separated by comma. 
Default is routes. | routes | String
 | *camel.main.dumpRoutesLog* | Whether to log route dumps to Logger | true | 
boolean
diff --git 
a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
 
b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
index 33059fba3bee..966cb50486c9 100644
--- 
a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
+++ 
b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
@@ -1581,7 +1581,7 @@ public abstract class DefaultConfigurationProperties<T> {
      *
      * You can also use png to save route diagrams as PNG image files either 
all combined in a single file (default
      * camel-route-diagrams.png) or to a given folder, where routes are 
grouped by source file name(s) and saved as
-     * corresponding .png files.\ This requires to have camel-diagram on the 
classpath to be able to render PNG
+     * corresponding .png files. This requires to have camel-diagram on the 
classpath to be able to render PNG
      * diagrams.
      */
     public void setDumpRoutes(String dumpRoutes) {
@@ -2814,7 +2814,7 @@ public abstract class DefaultConfigurationProperties<T> {
      *
      * You can also use png to save route diagrams as PNG image files either 
all combined in a single file (default
      * camel-route-diagrams.png) or to a given folder, where routes are 
grouped by source file name(s) and saved as
-     * corresponding .png files.\ This requires to have camel-diagram on the 
classpath to be able to render PNG
+     * corresponding .png files. This requires to have camel-diagram on the 
classpath to be able to render PNG
      * diagrams.
      */
     public T withDumpRoutes(String dumpRoutes) {
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-route-diagram.adoc
 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-route-diagram.adoc
index be9889a6c1cd..d118b519b5c9 100644
--- 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-route-diagram.adoc
+++ 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-route-diagram.adoc
@@ -26,7 +26,8 @@ camel cmd route-diagram [options]
 | `--metric` | Whether to include live metrics (only possible for running 
Camel application) | true | boolean
 | `--node-label` | What text to display in diagram nodes: code, description, 
or both (default) | both | String
 | `--output` | Save diagram to a PNG file instead of displaying in terminal |  
| String
-| `--theme,--colors` | Color theme preset (dark, light, transparent, text) or 
custom colors (e.g. bg=#1e1e1e:from=#2e7d32:to=#1565c0). Values can be #hex or 
ANSI color names (e.g. from=seagreen:to=steelblue). Use bg= for transparent. 
Can also be set via DIAGRAM_COLORS env var. | dark | String
+| `--theme` | Color theme preset (dark, light, transparent) or custom colors 
(e.g. bg=#1e1e1e:from=#2e7d32:to=#1565c0). Values can be #hex or ANSI color 
names (e.g. from=seagreen:to=steelblue). Use bg= for transparent. Can also be 
set via DIAGRAM_COLORS env var. | transparent | String
+| `--watch` | Execute periodically and showing output fullscreen |  | boolean
 | `--width` | Image width in pixels (0 = auto) | 0 | int
 | `-h,--help` | Display the help and sub-commands |  | boolean
 |===
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
index 9e3f577faf42..9e7a209d62c0 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
@@ -2,7 +2,7 @@
   "commands": [
     { "name": "bind", "fullName": "bind", "description": "DEPRECATED: Bind 
source and sink Kamelets as a new Camel integration", "deprecated": true, 
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.bind.Bind", "options": 
[ { "names": "--error-handler", "description": "Add error handler 
(none|log|sink:<endpoint>). Sink endpoints are expected in the format 
[[apigroup\/]version:]kind:[namespace\/]name, plain Camel URIs or Kamelet 
name.", "javaType": "java.lang.String", "type": "stri [...]
     { "name": "catalog", "fullName": "catalog", "description": "List artifacts 
from Camel Catalog", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.catalog.CatalogCommand", "options": [ 
{ "names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": 
"component", "fullName": "catalog component", "description": "List components 
from the Camel Catalog", "sourceClass": "org.apache.camel.dsl.jbang.co [...]
-    { "name": "cmd", "fullName": "cmd", "description": "Performs commands in 
the running Camel integrations, such as start\/stop route, or change logging 
levels.", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.action.CamelAction", "options": [ { 
"names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": 
"browse", "fullName": "cmd browse", "description": "Browse pending messages on 
endpoints [...]
+    { "name": "cmd", "fullName": "cmd", "description": "Performs commands in 
the running Camel integrations, such as start\/stop route, or change logging 
levels.", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.action.CamelAction", "options": [ { 
"names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": 
"browse", "fullName": "cmd browse", "description": "Browse pending messages on 
endpoints [...]
     { "name": "completion", "fullName": "completion", "description": "Generate 
completion script for bash\/zsh", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Complete", "options": [ { "names": 
"-h,--help", "description": "Display the help and sub-commands", "javaType": 
"boolean", "type": "boolean" } ] },
     { "name": "config", "fullName": "config", "description": "Get and set user 
configuration values", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.config.ConfigCommand", "options": [ { 
"names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "get", 
"fullName": "config get", "description": "Display user configuration value", 
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.config. [...]
     { "name": "debug", "fullName": "debug", "description": "Debug local Camel 
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Debug", 
"options": [ { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd 
HH:mm:ss in timestamp.", "javaType": "boolean", "type": "boolean" }, { "names": 
"--background", "description": "Run in the background", "defaultValue": 
"false", "javaType": "boolean", "type": "boolean" }, { "names": 
"--background-wait", "description": "To  [...]
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/ActionWatchCommand.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/ActionWatchCommand.java
index 976a356c2337..5bc634eed4dd 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/ActionWatchCommand.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/ActionWatchCommand.java
@@ -31,7 +31,8 @@ abstract class ActionWatchCommand extends ActionBaseCommand {
                         description = "Execute periodically and showing output 
fullscreen")
     boolean watch;
 
-    private CommandHelper.ReadConsoleTask waitUserTask;
+    private Runnable waitUserTask;
+    final AtomicBoolean running = new AtomicBoolean(true);
 
     protected ActionWatchCommand(CamelJBangMain main) {
         super(main);
@@ -43,7 +44,7 @@ abstract class ActionWatchCommand extends ActionBaseCommand {
         final AtomicBoolean running = new AtomicBoolean(true);
         if (watch) {
             Thread t = new Thread(() -> {
-                waitUserTask = new CommandHelper.ReadConsoleTask(() -> 
running.set(false));
+                waitUserTask = waitForUserEnter();
                 waitUserTask.run();
             }, "WaitForUser");
             t.start();
@@ -53,7 +54,7 @@ abstract class ActionWatchCommand extends ActionBaseCommand {
                     // use 2-sec delay in watch mode
                     try {
                         StopWatch watch = new StopWatch();
-                        while (running.get() && watch.taken() < 2000) {
+                        while (running.get() && watchWait(watch)) {
                             Thread.sleep(100);
                         }
                     } catch (Exception e) {
@@ -67,10 +68,18 @@ abstract class ActionWatchCommand extends ActionBaseCommand 
{
         return exit;
     }
 
+    protected Runnable waitForUserEnter() {
+        return new CommandHelper.ReadConsoleTask(() -> running.set(false));
+    }
+
     protected void clearScreen() {
         AnsiConsole.out().print(Ansi.ansi().eraseScreen().cursor(1, 1));
     }
 
+    protected boolean watchWait(StopWatch watch) {
+        return watch.taken() < 2000;
+    }
+
     protected abstract Integer doWatchCall() throws Exception;
 
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDiagramAction.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDiagramAction.java
index c4989bfc8485..d79fdc876a10 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDiagramAction.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDiagramAction.java
@@ -23,7 +23,6 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
 
 import javax.imageio.ImageIO;
 
@@ -41,17 +40,21 @@ import 
org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
 import org.apache.camel.dsl.jbang.core.common.PathUtils;
 import org.apache.camel.main.KameletMain;
 import org.apache.camel.support.PatternHelper;
+import org.apache.camel.util.StopWatch;
 import org.apache.camel.util.json.JsonObject;
+import org.jline.reader.LineReader;
+import org.jline.reader.LineReaderBuilder;
 import org.jline.terminal.Terminal;
 import org.jline.terminal.TerminalBuilder;
 import org.jline.terminal.impl.TerminalGraphics;
 import org.jline.terminal.impl.TerminalGraphicsManager;
+import org.jline.utils.InfoCmp;
 import picocli.CommandLine;
 import picocli.CommandLine.Command;
 
 @Command(name = "route-diagram", description = "Display Camel route diagram in 
the terminal", sortOptions = false,
          showDefaultValues = true)
-public class CamelRouteDiagramAction extends ActionBaseCommand {
+public class CamelRouteDiagramAction extends ActionWatchCommand {
 
     @CommandLine.Parameters(description = "Source file name, or name/pid of a 
running Camel integration", arity = "0..1")
     String name = "*";
@@ -68,12 +71,12 @@ public class CamelRouteDiagramAction extends 
ActionBaseCommand {
                         description = "Save diagram to a PNG file instead of 
displaying in terminal")
     String output;
 
-    @CommandLine.Option(names = { "--theme", "--colors" },
-                        description = "Color theme preset (dark, light, 
transparent, text) or custom colors "
+    @CommandLine.Option(names = { "--theme" },
+                        description = "Color theme preset (dark, light, 
transparent) or custom colors "
                                       + "(e.g. 
bg=#1e1e1e:from=#2e7d32:to=#1565c0). Values can be #hex or "
                                       + "ANSI color names (e.g. 
from=seagreen:to=steelblue). "
                                       + "Use bg= for transparent. Can also be 
set via DIAGRAM_COLORS env var.",
-                        defaultValue = "dark")
+                        defaultValue = "transparent")
     String theme;
 
     @CommandLine.Option(names = { "--font-size" },
@@ -99,6 +102,11 @@ public class CamelRouteDiagramAction extends 
ActionBaseCommand {
 
     private volatile long pid;
 
+    private DiagramColors colors;
+    private Terminal terminal;
+    private TerminalGraphics terminalGraphics;
+    private LineReader lineReader;
+
     public CamelRouteDiagramAction(CamelJBangMain main) {
         super(main);
     }
@@ -107,9 +115,25 @@ public class CamelRouteDiagramAction extends 
ActionBaseCommand {
     public Integer doCall() throws Exception {
         System.setProperty("java.awt.headless", "true");
 
-        String colorSpec = System.getenv("DIAGRAM_COLORS");
-        DiagramColors colors = !"text".equals(theme) ? 
DiagramColors.parse(colorSpec != null ? colorSpec : theme) : null;
+        // if output in terminal then ensure terminal supports this
+        if (output == null) {
+            String colorSpec = System.getenv("DIAGRAM_COLORS");
+            colors = DiagramColors.parse(colorSpec != null ? colorSpec : 
theme);
+            terminal = TerminalBuilder.builder().system(true).build();
+            terminalGraphics = 
TerminalGraphicsManager.getBestProtocol(terminal).orElse(null);
+            if (terminalGraphics == null) {
+                printer().println("Terminal does not support graphics 
protocols (Kitty, iTerm2, or Sixel).");
+                printer().println("Try running in a supported terminal: Kitty, 
iTerm2, WezTerm, Ghostty, or VS Code.");
+                return 1;
+            }
+            lineReader = 
LineReaderBuilder.builder().terminal(terminal).build();
+        }
 
+        return super.doCall();
+    }
+
+    @Override
+    protected Integer doWatchCall() throws Exception {
         Path outputFile;
         int exit = 0;
         List<Long> pids = findPids(name);
@@ -143,7 +167,7 @@ public class CamelRouteDiagramAction extends 
ActionBaseCommand {
             List<RouteInfo> routes = parseRoutes(jo);
             if (routes.isEmpty()) {
                 printer().println("No routes found");
-                return 0;
+                return 1;
             }
 
             if (filter != null) {
@@ -155,7 +179,7 @@ public class CamelRouteDiagramAction extends 
ActionBaseCommand {
 
             if (routes.isEmpty()) {
                 printer().println("No routes match filter: " + filter);
-                return 0;
+                return 1;
             }
 
             NodeLabelMode labelMode = parseNodeLabelMode(nodeLabel);
@@ -164,13 +188,6 @@ public class CamelRouteDiagramAction extends 
ActionBaseCommand {
                     engine.getNodeWidth(), fontSize * 
RouteDiagramLayoutEngine.SCALE, engine.getNodeTextPadding(),
                     pid > 0 && metric);
 
-            if ("text".equals(theme)) {
-                for (String line : renderer.printTextDiagram(routes, 
labelMode)) {
-                    printer().println(line);
-                }
-                return 0;
-            }
-
             List<LayoutRoute> layoutRoutes = new ArrayList<>();
             int currentY = RouteDiagramLayoutEngine.PADDING;
             for (RouteInfo route : routes) {
@@ -196,35 +213,10 @@ public class CamelRouteDiagramAction extends 
ActionBaseCommand {
                 ImageIO.write(image, "PNG", file);
                 printer().println("Diagram saved to: " + 
file.getAbsolutePath());
             } else {
-                try (Terminal terminal = 
TerminalBuilder.builder().system(true).build()) {
-                    try {
-                        Optional<TerminalGraphics> protocol = 
TerminalGraphicsManager.getBestProtocol(terminal);
-                        if (protocol.isPresent()) {
-                            TerminalGraphics.ImageOptions opts = new 
TerminalGraphics.ImageOptions()
-                                    .preserveAspectRatio(true);
-                            if (width > 0) {
-                                opts.width(width);
-                            }
-                            protocol.get().displayImage(terminal, image, opts);
-                            terminal.writer().println();
-                            terminal.flush();
-                        } else {
-                            printer().println(
-                                    "Terminal does not support graphics 
protocols (Kitty, iTerm2, or Sixel).");
-                            printer().println(
-                                    "Try running in a supported terminal: 
Kitty, iTerm2, WezTerm, Ghostty, or VS Code.");
-                            for (String line : 
renderer.printTextDiagram(routes, labelMode)) {
-                                printer().println(line);
-                            }
-                        }
-                    } catch (IOException | UnsupportedOperationException e) {
-                        printer().println("Failed to display diagram in 
terminal: " + e.getMessage());
-                        printer().println("Falling back to text diagram.");
-                        for (String line : renderer.printTextDiagram(routes, 
labelMode)) {
-                            printer().println(line);
-                        }
-                    }
+                if (watch) {
+                    clearScreen();
                 }
+                doDisplayDiagram(image);
             }
 
             return 0;
@@ -233,6 +225,43 @@ public class CamelRouteDiagramAction extends 
ActionBaseCommand {
         }
     }
 
+    @Override
+    protected boolean watchWait(StopWatch watch) {
+        return watch.taken() < 5000;
+    }
+
+    @Override
+    protected void clearScreen() {
+        if (terminal != null) {
+            terminal.puts(InfoCmp.Capability.clear_screen);
+            terminal.flush();
+        }
+    }
+
+    @Override
+    protected Runnable waitForUserEnter() {
+        return () -> {
+            if (lineReader != null) {
+                try {
+                    lineReader.readLine();
+                } catch (Exception e) {
+                    // ignore
+                }
+            }
+        };
+    }
+
+    private void doDisplayDiagram(BufferedImage image) throws IOException {
+        TerminalGraphics.ImageOptions opts = new 
TerminalGraphics.ImageOptions()
+                .preserveAspectRatio(true);
+        if (width > 0) {
+            opts.width(width);
+        }
+        terminalGraphics.displayImage(terminal, image, opts);
+        terminal.writer().println();
+        terminal.flush();
+    }
+
     private void doCallPid(Long pid) {
         this.pid = pid;
 


Reply via email to