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 c11ed80425c1 CAMEL-23572: camel-tui: Add documentation viewer and 
README support (#23425)
c11ed80425c1 is described below

commit c11ed80425c1ca4cefd47628d76684d1a42daa1f
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu May 21 17:17:14 2026 +0200

    CAMEL-23572: camel-tui: Add documentation viewer and README support (#23425)
    
    * CAMEL-23572: camel-jbang: Include README files when running integrations
    
    README files (README.md, README.adoc, etc.) are no longer filtered out
    by camel run. They are added to the classpath and tracked via the new
    camel.jbang.readmeFiles property. The CLI connector exposes them in the
    status JSON and supports a readme action to fetch content on demand.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * CAMEL-23572: camel-tui: Add documentation viewer with MarkdownView
    
    - Add "Show Documentation" to F2 actions menu for viewing README files
      from running integrations via the readme action
    - Add `d` key in example browser to view bundled or online README docs
    - Use TamboUI MarkdownView widget for rendering with scroll support
    - Convert bundled example READMEs from AsciiDoc to Markdown
    - Include AsciiDoc-to-Markdown converter for online .adoc fallback
    - Esc from doc viewer returns to example browser when opened from there
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * CAMEL-23572: camel-tui: Auto-select first row in circuit breaker tab
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * CAMEL-23572: camel-tui: Extract DocHelper for download and AsciiDoc 
conversion
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * CAMEL-23572: camel-jbang: Use citrus emoji for Citrus tests indicator
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * CAMEL-23572: Regenerate JBang configuration metadata for readmeFiles
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    ---------
    
    Co-authored-by: Claude Opus 4.6 <[email protected]>
---
 .../jbang/camel-jbang-configuration-metadata.json  |   1 +
 .../ROOT/pages/camel-4x-upgrade-guide-4_21.adoc    |   4 +
 .../modules/ROOT/pages/camel-jbang.adoc            |   3 +-
 .../camel/cli/connector/LocalCliConnector.java     |  27 +++
 .../camel-jbang-configuration-metadata.json        |   1 +
 .../apache/camel/dsl/jbang/core/commands/Run.java  |  26 ++-
 .../dsl/jbang/core/common/CamelJBangConstants.java |   4 +
 .../examples/camel-jbang-example-catalog.json      |  42 ++--
 .../resources/examples/circuit-breaker/README.adoc |  61 -----
 .../resources/examples/circuit-breaker/README.md   |  24 ++
 .../examples/cron-log/{README.adoc => README.md}   |   4 +-
 .../src/main/resources/examples/groovy/README.adoc |  76 ------
 .../src/main/resources/examples/groovy/README.md   |  39 ++++
 .../examples/rest-api/{README.adoc => README.md}   |   6 +-
 .../src/main/resources/examples/routes/README.adoc |  78 -------
 .../src/main/resources/examples/routes/README.md   |  41 ++++
 .../examples/timer-log/{README.adoc => README.md}  |   4 +-
 .../src/main/resources/examples/xslt/README.adoc   |  66 ------
 .../src/main/resources/examples/xslt/README.md     |  31 +++
 dsl/camel-jbang/camel-jbang-plugin-tui/pom.xml     |   5 +
 .../dsl/jbang/core/commands/tui/ActionsPopup.java  | 257 ++++++++++++++++++++-
 .../dsl/jbang/core/commands/tui/CamelMonitor.java  |   8 +
 .../jbang/core/commands/tui/CircuitBreakerTab.java |   8 +
 .../dsl/jbang/core/commands/tui/DocHelper.java     | 159 +++++++++++++
 .../jbang/core/commands/tui/IntegrationInfo.java   |   1 +
 25 files changed, 646 insertions(+), 330 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/jbang/camel-jbang-configuration-metadata.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/jbang/camel-jbang-configuration-metadata.json
index 20ef3f10818a..a961aea2f78f 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/jbang/camel-jbang-configuration-metadata.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/jbang/camel-jbang-configuration-metadata.json
@@ -42,6 +42,7 @@
     { "name": "camel.jbang.quarkusExtensionRegistryBaseUri", "required": 
false, "description": "The base URI of Quarkus Extension Registry", "label": 
"quarkus", "type": "string", "javaType": "String", "secret": false },
     { "name": "camel.jbang.quarkusGroupId", "required": false, "description": 
"groupId of Quarkus Platform BOM", "label": "quarkus", "type": "string", 
"javaType": "String", "secret": false },
     { "name": "camel.jbang.quarkusVersion", "required": false, "description": 
"version of Quarkus Platform BOM", "label": "quarkus", "type": "string", 
"javaType": "String", "secret": false },
+    { "name": "camel.jbang.readmeFiles", "required": false, "description": 
"README files included with the integration (Use commas to separate multiple 
files)", "type": "string", "javaType": "String", "secret": false },
     { "name": "camel.jbang.repos", "required": false, "description": 
"Additional Maven repositories for download on-demand (Use commas to separate 
multiple repositories)", "label": "maven", "type": "string", "javaType": 
"String", "secret": false },
     { "name": "camel.jbang.runtime", "required": false, "description": "Which 
runtime to use (camel-main, spring-boot, quarkus)", "type": "enum", "javaType": 
"String", "secret": false, "enum": [ "camel-main", "spring-boot", "quarkus" ] },
     { "name": "camel.jbang.scriptFiles", "required": false, "description": 
"Additional shell script files to export to src\/main\/scripts directory", 
"type": "string", "javaType": "String", "secret": false },
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
index 98c4e40e01b4..f9e4f6fa80e1 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
@@ -49,6 +49,10 @@ and dev consoles for nodes inside Choice EIP branches.
 
 === camel-jbang
 
+README files (`README.md`, `README.adoc`, etc.) are now included when running 
integrations with `camel run`.
+Previously these files were silently excluded. The README files are added to 
the classpath
+and exposed via the CLI connector so tools such as `camel monitor` can display 
them.
+
 The `camel wrapper` command now installs the scripts as `camel` instead of 
`camelw`.
 You can use the `--command-name=camelw` to use the old name.
 
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
index 5d32d52f3dc0..d95a9d8160a2 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
@@ -4210,7 +4210,7 @@ The follow options related to _exporting_ or _running_, 
can be configured in `ap
 
 // jbang options: START
 === Camel JBang configurations
-The camel.jbang supports 47 options, which are listed below.
+The camel.jbang supports 48 options, which are listed below.
 
 [width="100%",cols="2,5,^1,2",options="header"]
 |===
@@ -4254,6 +4254,7 @@ The camel.jbang supports 47 options, which are listed 
below.
 | *camel.jbang.quarkusExtensionRegistryBaseUri* | The base URI of Quarkus 
Extension Registry |  | String
 | *camel.jbang.quarkusGroupId* | groupId of Quarkus Platform BOM |  | String
 | *camel.jbang.quarkusVersion* | version of Quarkus Platform BOM |  | String
+| *camel.jbang.readmeFiles* | README files included with the integration (Use 
commas to separate multiple files) |  | String
 | *camel.jbang.repos* | Additional Maven repositories for download on-demand 
(Use commas to separate multiple repositories) |  | String
 | *camel.jbang.runtime* | Which runtime to use (camel-main, spring-boot, 
quarkus) |  | String
 | *camel.jbang.scriptFiles* | Additional shell script files to export to 
src/main/scripts directory |  | String
diff --git 
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
 
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
index 2345d81ec63a..7eb379358811 100644
--- 
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
+++ 
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
@@ -334,6 +334,8 @@ public class LocalCliConnector extends ServiceSupport 
implements CliConnector, C
                 doActionBrowseTask(root);
             } else if ("receive".equals(action)) {
                 doActionReceiveTask(root);
+            } else if ("readme".equals(action)) {
+                doActionReadmeTask(root);
             } else if ("cli-debug".equals(action)) {
                 doActionCliDebug(root);
             }
@@ -348,6 +350,26 @@ public class LocalCliConnector extends ServiceSupport 
implements CliConnector, C
         }
     }
 
+    private void doActionReadmeTask(JsonObject root) throws Exception {
+        String readmeFiles = camelContext.getPropertiesComponent()
+                .resolveProperty("camel.jbang.readmeFiles").orElse(null);
+        JsonObject json = new JsonObject();
+        if (readmeFiles != null) {
+            String filter = root.getString("filter");
+            for (String f : readmeFiles.split(",")) {
+                if (filter == null || f.contains(filter)) {
+                    File file = new File(f);
+                    if (file.isFile() && file.exists()) {
+                        json.put("file", f);
+                        json.put("content", Files.readString(file.toPath()));
+                        break;
+                    }
+                }
+            }
+        }
+        IOHelper.writeText(json.toJson(), outputFile);
+    }
+
     private void doActionCliDebug(JsonObject root) {
         String command = root.getString("command");
         String breakpoint = root.getString("breakpoint");
@@ -1120,6 +1142,11 @@ public class LocalCliConnector extends ServiceSupport 
implements CliConnector, C
                 rc.put("javaVendor", mb.getVmVendor());
                 rc.put("javaVmName", mb.getVmName());
             }
+            String readmeFiles = camelContext.getPropertiesComponent()
+                    .resolveProperty("camel.jbang.readmeFiles").orElse(null);
+            if (readmeFiles != null) {
+                rc.put("readmeFiles", readmeFiles);
+            }
             root.put("runtime", rc);
 
             DevConsoleRegistry dcr = 
camelContext.getCamelContextExtension().getContextPlugin(DevConsoleRegistry.class);
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-configuration-metadata.json
 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-configuration-metadata.json
index 20ef3f10818a..a961aea2f78f 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-configuration-metadata.json
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-configuration-metadata.json
@@ -42,6 +42,7 @@
     { "name": "camel.jbang.quarkusExtensionRegistryBaseUri", "required": 
false, "description": "The base URI of Quarkus Extension Registry", "label": 
"quarkus", "type": "string", "javaType": "String", "secret": false },
     { "name": "camel.jbang.quarkusGroupId", "required": false, "description": 
"groupId of Quarkus Platform BOM", "label": "quarkus", "type": "string", 
"javaType": "String", "secret": false },
     { "name": "camel.jbang.quarkusVersion", "required": false, "description": 
"version of Quarkus Platform BOM", "label": "quarkus", "type": "string", 
"javaType": "String", "secret": false },
+    { "name": "camel.jbang.readmeFiles", "required": false, "description": 
"README files included with the integration (Use commas to separate multiple 
files)", "type": "string", "javaType": "String", "secret": false },
     { "name": "camel.jbang.repos", "required": false, "description": 
"Additional Maven repositories for download on-demand (Use commas to separate 
multiple repositories)", "label": "maven", "type": "string", "javaType": 
"String", "secret": false },
     { "name": "camel.jbang.runtime", "required": false, "description": "Which 
runtime to use (camel-main, spring-boot, quarkus)", "type": "enum", "javaType": 
"String", "secret": false, "enum": [ "camel-main", "spring-boot", "quarkus" ] },
     { "name": "camel.jbang.scriptFiles", "required": false, "description": 
"Additional shell script files to export to src\/main\/scripts directory", 
"type": "string", "javaType": "String", "secret": false },
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 f0b64906eedf..0389c1aaf122 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
@@ -419,7 +419,7 @@ public class Run extends CamelCommand {
                     icons.append("  ");
                 }
                 if (ExampleHelper.hasCitrusTests(entry)) {
-                    icons.append("๐Ÿงช");
+                    icons.append("๐Ÿ‹");
                 } else {
                     icons.append("  ");
                 }
@@ -428,7 +428,7 @@ public class Run extends CamelCommand {
         }
         printer().println();
         printer().println(
-                "  ๐Ÿ“ฆ = bundled (works offline)  ๐ŸŒ = online (fetched from 
GitHub)  ๐Ÿณ = requires Docker  ๐Ÿงช = Citrus tests");
+                "  ๐Ÿ“ฆ = bundled (works offline)  ๐ŸŒ = online (fetched from 
GitHub)  ๐Ÿณ = requires Docker  ๐Ÿ‹ = Citrus tests");
         printer().println();
         printer().println("Usage: camel run --example=<name>");
         printer().println("       camel run --example=<name> --dev");
@@ -910,6 +910,7 @@ public class Run extends CamelCommand {
         StringJoiner sjTlsFiles = new StringJoiner(",");
         StringJoiner sjKamelets = new StringJoiner(",");
         StringJoiner sjJKubeFiles = new StringJoiner(",");
+        StringJoiner sjReadmeFiles = new StringJoiner(",");
 
         // include generated openapi to files to run
         if (openapi != null) {
@@ -970,6 +971,10 @@ public class Run extends CamelCommand {
                 // jkube
                 sjJKubeFiles.add(file);
                 continue;
+            } else if (isReadmeFile(file)) {
+                sjReadmeFiles.add(file);
+                sjClasspathFiles.add(file);
+                continue;
             } else if (!knownFile(file) && !file.endsWith(".properties")) {
                 // unknown files to be added on classpath
                 sjClasspathFiles.add(file);
@@ -1070,6 +1075,12 @@ public class Run extends CamelCommand {
         } else {
             writeSetting(main, profileProperties, CLASSPATH_FILES, () -> null);
         }
+        if (sjReadmeFiles.length() > 0) {
+            main.addInitialProperty(README_FILES, sjReadmeFiles.toString());
+            writeSettings(README_FILES, sjReadmeFiles.toString());
+        } else {
+            writeSetting(main, profileProperties, README_FILES, () -> null);
+        }
         if (sjScriptFiles.length() > 0) {
             main.addInitialProperty(SCRIPT_FILES, sjScriptFiles.toString());
             writeSettings(SCRIPT_FILES, sjScriptFiles.toString());
@@ -2216,12 +2227,6 @@ public class Run extends CamelCommand {
             return true;
         }
 
-        String on = FileUtil.onlyName(name, true);
-        on = on.toLowerCase(Locale.ROOT);
-        if (on.startsWith("readme")) {
-            return true;
-        }
-
         return false;
     }
 
@@ -2253,6 +2258,11 @@ public class Run extends CamelCommand {
         return name.endsWith(".jkube.yaml") || name.endsWith(".jkube.yml");
     }
 
+    private static boolean isReadmeFile(String name) {
+        String on = FileUtil.onlyName(FileUtil.stripPath(name), true);
+        return on.toLowerCase(Locale.ROOT).startsWith("readme");
+    }
+
     private void writeSettings(String key, String value) {
         try {
             // use java.util.Properties to ensure the value is escaped 
correctly
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CamelJBangConstants.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CamelJBangConstants.java
index feb9a6365475..96da1957be04 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CamelJBangConstants.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CamelJBangConstants.java
@@ -36,6 +36,10 @@ public final class CamelJBangConstants {
               javaType = "String")
     public static final String CLASSPATH_FILES = "camel.jbang.classpathFiles";
 
+    @Metadata(description = "README files included with the integration (Use 
commas to separate multiple files)",
+              javaType = "String")
+    public static final String README_FILES = "camel.jbang.readmeFiles";
+
     @Metadata(description = "Local file directory for loading custom Kamelets",
               javaType = "String")
     public static final String LOCAL_KAMELET_DIR = 
"camel.jbang.localKameletDir";
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 a45a48e1027b..a9b467615531 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
@@ -13,7 +13,7 @@
         "requiresDocker": false,
         "hasCitrusTests": false,
         "files": [
-            "README.adoc",
+            "README.md",
             "application.properties",
             "consumer.camel.yaml",
             "producer.camel.yaml"
@@ -35,7 +35,7 @@
         "requiresDocker": false,
         "hasCitrusTests": true,
         "files": [
-            "README.adoc",
+            "README.md",
             "application.properties",
             "aws-s3-cdc-log.camel.yaml",
             "example-file.txt",
@@ -58,7 +58,7 @@
         "requiresDocker": false,
         "hasCitrusTests": true,
         "files": [
-            "README.adoc",
+            "README.md",
             "application.properties",
             "http-to-aws-sqs.camel.yaml"
         ]
@@ -76,7 +76,7 @@
         "requiresDocker": false,
         "hasCitrusTests": false,
         "files": [
-            "README.adoc",
+            "README.md",
             "route.camel.yaml"
         ]
     },
@@ -95,7 +95,7 @@
         "requiresDocker": false,
         "hasCitrusTests": false,
         "files": [
-            "README.adoc",
+            "README.md",
             "cron-log.camel.yaml"
         ]
     },
@@ -114,7 +114,7 @@
         "requiresDocker": true,
         "hasCitrusTests": false,
         "files": [
-            "README.adoc",
+            "README.md",
             "application.properties",
             "compose.yaml",
             "docling-langchain4j-rag.yaml",
@@ -136,7 +136,7 @@
         "requiresDocker": false,
         "hasCitrusTests": false,
         "files": [
-            "README.adoc",
+            "README.md",
             "application.properties",
             "examples/banking-sector-brief.pdf",
             "examples/magnificent-seven-update.pdf",
@@ -159,7 +159,7 @@
         "requiresDocker": true,
         "hasCitrusTests": true,
         "files": [
-            "README.adoc",
+            "README.md",
             "application.properties",
             "compose.yaml",
             "ftp.camel.yaml",
@@ -180,7 +180,7 @@
         "requiresDocker": false,
         "hasCitrusTests": false,
         "files": [
-            "README.adoc",
+            "README.md",
             "application.properties",
             "groovy.camel.yaml"
         ]
@@ -200,7 +200,7 @@
         "requiresDocker": false,
         "hasCitrusTests": false,
         "files": [
-            "README.adoc",
+            "README.md",
             "application.properties",
             "rest-api.camel.yaml"
         ]
@@ -220,7 +220,7 @@
         "requiresDocker": false,
         "hasCitrusTests": false,
         "files": [
-            "README.adoc",
+            "README.md",
             "application.properties",
             "rest-api.camel.yaml"
         ]
@@ -239,7 +239,7 @@
         "requiresDocker": true,
         "hasCitrusTests": true,
         "files": [
-            "README.adoc",
+            "README.md",
             "application.properties",
             "compose.yaml",
             "infra/mosquitto.conf",
@@ -262,7 +262,7 @@
         "requiresDocker": false,
         "hasCitrusTests": false,
         "files": [
-            "README.adoc",
+            "README.md",
             "application.properties",
             "pii-redaction.camel.yaml",
             "pii.schema.json"
@@ -282,7 +282,7 @@
         "requiresDocker": false,
         "hasCitrusTests": true,
         "files": [
-            "README.adoc",
+            "README.md",
             "application.properties",
             "examples/1001.json",
             "petstore-api.json",
@@ -303,7 +303,7 @@
         "requiresDocker": false,
         "hasCitrusTests": true,
         "files": [
-            "README.adoc",
+            "README.md",
             "application.properties",
             "examples/pet/1000.json",
             "petstore-api.json",
@@ -324,7 +324,7 @@
         "requiresDocker": false,
         "hasCitrusTests": false,
         "files": [
-            "README.adoc",
+            "README.md",
             "rest-api.camel.yaml"
         ]
     },
@@ -343,7 +343,7 @@
         "hasCitrusTests": false,
         "files": [
             "Greeter.java",
-            "README.adoc",
+            "README.md",
             "beans.yaml",
             "routes.camel.yaml"
         ]
@@ -363,7 +363,7 @@
         "requiresDocker": false,
         "hasCitrusTests": true,
         "files": [
-            "README.adoc",
+            "README.md",
             "analyzer/application-dev.properties",
             "analyzer/error-analyzer.camel.yaml",
             "containers/caches/infinispan-events-config.json",
@@ -410,7 +410,7 @@
         "requiresDocker": true,
         "hasCitrusTests": false,
         "files": [
-            "README.adoc",
+            "README.md",
             "application.properties",
             "compose.yaml",
             "sql.camel.yaml"
@@ -430,7 +430,7 @@
         "requiresDocker": false,
         "hasCitrusTests": false,
         "files": [
-            "README.adoc",
+            "README.md",
             "timer-log.camel.yaml"
         ]
     },
@@ -448,7 +448,7 @@
         "requiresDocker": false,
         "hasCitrusTests": false,
         "files": [
-            "README.adoc",
+            "README.md",
             "consumer.camel.yaml",
             "input/account.xml",
             "stylesheet.xsl"
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/circuit-breaker/README.adoc
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/circuit-breaker/README.adoc
deleted file mode 100644
index cca5a275abf1..000000000000
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/circuit-breaker/README.adoc
+++ /dev/null
@@ -1,61 +0,0 @@
-== Circuit Breaker
-
-This example shows how Camel JBang can use circuit breaker EIP.
-
-=== 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:
-
-[source,sh]
-----
-$ jbang --version
-----
-
-This will output the version of JBang.
-
-To run this example you can either install Camel on JBang via:
-
-[source,sh]
-----
-$ jbang app install camel@apache/camel
-----
-
-Which allows to run Camel JBang with `camel` as shown below.
-
-=== How to run
-
-You can run this example using:
-
-[source,sh]
-----
-$ camel run *
-----
-
-While the Camel integration is running, then from another terminal type:
-
-[source,sh]
-----
-$ camel get circuit-breaker
-----
-
-Which then output the state of the circuit breaker. You can run this command 
with `--watch` and see
-how the state of the circuit breaker changes from closed to open due to many 
failures.
-
-[source,sh]
-----
-$ camel get circuit-breaker --watch
-----
-
-
-
-=== Help and contributions
-
-If you hit any problem using Camel or have some feedback, then please
-https://camel.apache.org/community/support/[let us know].
-
-We also love contributors, so
-https://camel.apache.org/community/contributing/[get involved] :-)
-
-The Camel riders!
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/circuit-breaker/README.md
new file mode 100644
index 000000000000..38a0209d9641
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/circuit-breaker/README.md
@@ -0,0 +1,24 @@
+# Circuit Breaker
+
+This example shows how Camel JBang can use circuit breaker EIP.
+
+## How to run
+
+You can run this example using:
+
+```sh
+camel run *
+```
+
+While the Camel integration is running, then from another terminal type:
+
+```sh
+camel get circuit-breaker
+```
+
+Which then output the state of the circuit breaker. You can run this command 
with `--watch` and see
+how the state of the circuit breaker changes from closed to open due to many 
failures.
+
+```sh
+camel get circuit-breaker --watch
+```
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/README.adoc
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/README.md
similarity index 81%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/README.adoc
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/README.md
index 7a5721140db9..cd926f1f9b8f 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/README.adoc
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/cron-log/README.md
@@ -1,7 +1,7 @@
-== Cron Log
+# Cron Log
 
 This example shows a scheduled task that logs the current time every 5 seconds.
 
-=== How to run
+## How to run
 
     camel run cron-log.camel.yaml
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/README.adoc
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/README.adoc
deleted file mode 100644
index d6910f56215e..000000000000
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/README.adoc
+++ /dev/null
@@ -1,76 +0,0 @@
-== Groovy
-
-This example shows how to use Groovy with extra dependencies in Camel JBang.
-
-The route uses `EmailValidator` from 
https://commons.apache.org/proper/commons-validator/[Apache Commons Validator]
-to validate an email address and route the message accordingly using 
content-based routing.
-
-The extra dependency is declared in `application.properties` using the
-`camel.jbang.dependencies` property:
-
-[source,properties]
-----
-camel.jbang.dependencies=commons-validator:commons-validator:1.10.1
-----
-
-=== 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:
-
-[source,sh]
-----
-$ jbang --version
-----
-
-This will output the version of JBang.
-
-To run this example you can either install Camel on JBang via:
-
-[source,sh]
-----
-$ jbang app install camel@apache/camel
-----
-
-Which allows to run Camel JBang with `camel` as shown below.
-
-=== How to run
-
-You can run this example using:
-
-[source,sh]
-----
-$ camel run *
-----
-
-To see the invalid email branch, edit `groovy.camel.yaml` and change the 
`contactEmail` header in the `once` URI to an invalid value:
-
-[source,yaml]
-----
-uri: once:validate?header.contactEmail=not-a-valid-email
-----
-
-You can also declare dependencies as a modeline comment at the top of the YAML 
route file:
-
-[source,yaml]
-----
-#//DEPS commons-validator:commons-validator:1.10.1
-----
-
-Or pass the dependency on the command line:
-
-[source,sh]
-----
-$ camel run groovy.camel.yaml --dep=commons-validator:commons-validator:1.10.1
-----
-
-=== Help and contributions
-
-If you hit any problem using Camel or have some feedback, then please
-https://camel.apache.org/community/support/[let us know].
-
-We also love contributors, so
-https://camel.apache.org/community/contributing/[get involved] :-)
-
-The Camel riders!
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/groovy/README.md
new file mode 100644
index 000000000000..46dcc024a0bb
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/groovy/README.md
@@ -0,0 +1,39 @@
+# Groovy
+
+This example shows how to use Groovy with extra dependencies in Camel JBang.
+
+The route uses `EmailValidator` from [Apache Commons 
Validator](https://commons.apache.org/proper/commons-validator/)
+to validate an email address and route the message accordingly using 
content-based routing.
+
+The extra dependency is declared in `application.properties` using the
+`camel.jbang.dependencies` property:
+
+```properties
+camel.jbang.dependencies=commons-validator:commons-validator:1.10.1
+```
+
+## How to run
+
+You can run this example using:
+
+```sh
+camel run *
+```
+
+To see the invalid email branch, edit `groovy.camel.yaml` and change the 
`contactEmail` header in the `once` URI to an invalid value:
+
+```yaml
+uri: once:validate?header.contactEmail=not-a-valid-email
+```
+
+You can also declare dependencies as a modeline comment at the top of the YAML 
route file:
+
+```yaml
+#//DEPS commons-validator:commons-validator:1.10.1
+```
+
+Or pass the dependency on the command line:
+
+```sh
+camel run groovy.camel.yaml --dep=commons-validator:commons-validator:1.10.1
+```
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/README.adoc
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/README.md
similarity index 82%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/README.adoc
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/README.md
index c68c2872843b..9ade804eab16 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/README.adoc
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/rest-api/README.md
@@ -1,12 +1,12 @@
-== REST API
+# REST API
 
 This example shows a REST API with hello endpoints.
 
-=== How to run
+## How to run
 
     camel run rest-api.camel.yaml
 
-=== Try it
+## Try it
 
     curl http://localhost:8080/api/hello
     curl http://localhost:8080/api/hello/World
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/README.adoc
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/README.adoc
deleted file mode 100644
index 5cf864ca5b61..000000000000
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/README.adoc
+++ /dev/null
@@ -1,78 +0,0 @@
-== Routes
-
-This example shows how routes are defined in Yaml.
-
-=== 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:
-
-[source,sh]
-----
-$ jbang --version
-----
-
-This will output the version of JBang.
-
-To run this example you can either install Camel on JBang via:
-
-[source,sh]
-----
-$ jbang app install camel@apache/camel
-----
-
-Which allows to run Camel JBang with `camel` as shown below.
-
-=== How to run
-
-You can run this example using:
-
-[source,sh]
-----
-$ camel run *
-----
-
-Camel will start a route that periodically provides a greeting message.
-
-=== Live reload
-
-You can run the example in dev mode which allows you to edit the example,
-and hot-reload when the file is saved.
-
-[source,sh]
-----
-$ camel run * --dev
-----
-
-=== Run directly from GitHub
-
-The example can also be run directly by referring to the GitHub URL as shown:
-
-[source,sh]
-----
-$ camel run https://github.com/apache/camel-jbang-examples/tree/main/routes
-----
-
-=== Developer Web Console
-
-You can enable the developer console via `--console` flag as show:
-
-[source,sh]
-----
-$ camel run * --console
-----
-
-Then you can browse: http://localhost:8080/q/dev to introspect the running 
Camel Application.
-Under "beans" Camel should display bean `greeter`.
-
-
-=== Help and contributions
-
-If you hit any problem using Camel or have some feedback, then please
-https://camel.apache.org/community/support/[let us know].
-
-We also love contributors, so
-https://camel.apache.org/community/contributing/[get involved] :-)
-
-The Camel riders!
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/routes/README.md
new file mode 100644
index 000000000000..7dc86fc5a31c
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/routes/README.md
@@ -0,0 +1,41 @@
+# Routes
+
+This example shows how routes are defined in Yaml.
+
+## How to run
+
+You can run this example using:
+
+```sh
+camel run *
+```
+
+Camel will start a route that periodically provides a greeting message.
+
+## Live reload
+
+You can run the example in dev mode which allows you to edit the example,
+and hot-reload when the file is saved.
+
+```sh
+camel run * --dev
+```
+
+## Run directly from GitHub
+
+The example can also be run directly by referring to the GitHub URL as shown:
+
+```sh
+camel run https://github.com/apache/camel-jbang-examples/tree/main/routes
+```
+
+## Developer Web Console
+
+You can enable the developer console via `--console` flag as show:
+
+```sh
+camel run * --console
+```
+
+Then you can browse: http://localhost:8080/q/dev to introspect the running 
Camel Application.
+Under "beans" Camel should display bean `greeter`.
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/README.adoc
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/README.md
similarity index 80%
rename from 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/README.adoc
rename to 
dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/README.md
index 3abb3a0f9b31..fd5b8ac6199f 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/README.adoc
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/timer-log/README.md
@@ -1,7 +1,7 @@
-== Timer Log
+# Timer Log
 
 This example shows a simple timer that logs a hello message every second.
 
-=== How to run
+## How to run
 
     camel run timer-log.camel.yaml
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/README.adoc 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/README.adoc
deleted file mode 100644
index 0d3cc77abf8e..000000000000
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/README.adoc
+++ /dev/null
@@ -1,66 +0,0 @@
-== XSLT Transformation
-
-This example shows a basic XML transformation using XSLT style sheet.
-
-=== 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:
-
-[source,sh]
-----
-$ jbang --version
-----
-
-This will output the version of JBang.
-
-To run this example you can either install Camel on JBang via:
-
-[source,sh]
-----
-$ jbang app install camel@apache/camel
-----
-
-Which allows to run Camel JBang with `camel` as shown below.
-
-=== How to run
-
-Then you can run this example using:
-
-[source,sh]
-----
-$ camel run *
-----
-
-This reads the XML input file from _./input/account.xml_ and applies XSL 
transformation.
-
-=== Live updates of message transformation
-
-You can do live changes to the stylesheet and see the output in real-time with 
Camel JBang by running:
-
-[source,bash]
-----
-$ camel transform message --body=file:input/account.xml --component=xslt 
--template=file:stylesheet.xsl --pretty --watch
-----
-
-You can then edit the `stylesheet.xsl` file, and save the file, and watch the 
terminal for updated result.
-
-=== Run directly from GitHub
-
-The example can also be run directly by referring to the GitHub URL as shown:
-
-[source,sh]
-----
-$ camel run https://github.com/apache/camel-jbang-examples/tree/main/xslt
-----
-
-=== Help and contributions
-
-If you hit any problem using Camel or have some feedback, then please
-https://camel.apache.org/community/support/[let us know].
-
-We also love contributors, so
-https://camel.apache.org/community/contributing/[get involved] :-)
-
-The Camel riders!
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/xslt/README.md
new file mode 100644
index 000000000000..ec989f624348
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/xslt/README.md
@@ -0,0 +1,31 @@
+# XSLT Transformation
+
+This example shows a basic XML transformation using XSLT style sheet.
+
+## How to run
+
+You can run this example using:
+
+```sh
+camel run *
+```
+
+This reads the XML input file from `./input/account.xml` and applies XSL 
transformation.
+
+## Live updates of message transformation
+
+You can do live changes to the stylesheet and see the output in real-time with 
Camel JBang by running:
+
+```sh
+camel transform message --body=file:input/account.xml --component=xslt 
--template=file:stylesheet.xsl --pretty --watch
+```
+
+You can then edit the `stylesheet.xsl` file, and save the file, and watch the 
terminal for updated result.
+
+## Run directly from GitHub
+
+The example can also be run directly by referring to the GitHub URL as shown:
+
+```sh
+camel run https://github.com/apache/camel-jbang-examples/tree/main/xslt
+```
diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/pom.xml 
b/dsl/camel-jbang/camel-jbang-plugin-tui/pom.xml
index 64f490af9b89..4a3745e64f3b 100644
--- a/dsl/camel-jbang/camel-jbang-plugin-tui/pom.xml
+++ b/dsl/camel-jbang/camel-jbang-plugin-tui/pom.xml
@@ -73,6 +73,11 @@
             <artifactId>tamboui-image</artifactId>
             <version>${tamboui-version}</version>
         </dependency>
+        <dependency>
+            <groupId>dev.tamboui</groupId>
+            <artifactId>tamboui-markdown</artifactId>
+            <version>${tamboui-version}</version>
+        </dependency>
     </dependencies>
 
 </project>
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
index 964db24c4712..673ea1ee3e0a 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
@@ -24,8 +24,10 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 import dev.tamboui.layout.Rect;
+import dev.tamboui.markdown.MarkdownView;
 import dev.tamboui.style.Color;
 import dev.tamboui.style.Style;
 import dev.tamboui.terminal.Frame;
@@ -47,6 +49,7 @@ import dev.tamboui.widgets.list.ScrollMode;
 import dev.tamboui.widgets.paragraph.Paragraph;
 import org.apache.camel.dsl.jbang.core.common.ExampleHelper;
 import org.apache.camel.dsl.jbang.core.common.LauncherHelper;
+import org.apache.camel.dsl.jbang.core.common.PathUtils;
 import org.apache.camel.util.json.JsonObject;
 
 import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.hint;
@@ -55,11 +58,14 @@ import static 
org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.hintLa
 class ActionsPopup {
 
     private static final int ACTION_RUN_EXAMPLE = 0;
-    private static final int ACTION_SCREENSHOT = 1;
-    private static final int ACTION_COUNT = 2;
+    private static final int ACTION_SHOW_DOCS = 1;
+    private static final int ACTION_SCREENSHOT = 2;
+    private static final int ACTION_COUNT = 3;
 
     private final Supplier<Set<String>> runningNames;
+    private final Supplier<List<IntegrationInfo>> integrations;
     private final Runnable screenshotAction;
+    private MonitorContext ctx;
 
     private boolean showActionsMenu;
     private final ListState actionsMenuState = new ListState();
@@ -72,18 +78,33 @@ class ActionsPopup {
     private TextInputState nameInputState;
     private JsonObject selectedExample;
 
+    private boolean showDocPicker;
+    private final ListState docPickerState = new ListState();
+    private List<IntegrationInfo> docPickerIntegrations;
+    private boolean showDocViewer;
+    private boolean docViewerFromExampleBrowser;
+    private String docContent;
+    private String docTitle;
+    private int docScroll;
+
     private final List<PendingLaunch> pendingLaunches = new ArrayList<>();
     private String launchNotification;
     private boolean launchNotificationError;
     private long launchNotificationExpiry;
 
-    ActionsPopup(Supplier<Set<String>> runningNames, Runnable 
screenshotAction) {
+    ActionsPopup(Supplier<Set<String>> runningNames, 
Supplier<List<IntegrationInfo>> integrations,
+                 Runnable screenshotAction) {
         this.runningNames = runningNames;
+        this.integrations = integrations;
         this.screenshotAction = screenshotAction;
     }
 
+    void setContext(MonitorContext ctx) {
+        this.ctx = ctx;
+    }
+
     boolean isVisible() {
-        return showActionsMenu || showExampleBrowser || showNameInput;
+        return showActionsMenu || showExampleBrowser || showNameInput || 
showDocPicker || showDocViewer;
     }
 
     void open() {
@@ -95,6 +116,8 @@ class ActionsPopup {
         showActionsMenu = false;
         showExampleBrowser = false;
         showNameInput = false;
+        showDocPicker = false;
+        showDocViewer = false;
     }
 
     String notification() {
@@ -106,6 +129,37 @@ class ActionsPopup {
     }
 
     boolean handleKeyEvent(KeyEvent ke) {
+        if (showDocViewer) {
+            if (ke.isCancel()) {
+                showDocViewer = false;
+                if (docViewerFromExampleBrowser) {
+                    docViewerFromExampleBrowser = false;
+                    showExampleBrowser = true;
+                }
+            } else if (ke.isUp() || ke.isChar('k')) {
+                docScroll = Math.max(0, docScroll - 1);
+            } else if (ke.isDown() || ke.isChar('j')) {
+                docScroll++;
+            } else if (ke.isPageUp() || ke.isKey(KeyCode.PAGE_UP)) {
+                docScroll = Math.max(0, docScroll - 10);
+            } else if (ke.isPageDown() || ke.isKey(KeyCode.PAGE_DOWN)) {
+                docScroll += 10;
+            }
+            return true;
+        }
+        if (showDocPicker) {
+            if (ke.isCancel()) {
+                showDocPicker = false;
+                showActionsMenu = true;
+            } else if (ke.isUp()) {
+                docPickerState.selectPrevious();
+            } else if (ke.isDown()) {
+                docPickerState.selectNext(docPickerIntegrations != null ? 
docPickerIntegrations.size() : 0);
+            } else if (ke.isConfirm()) {
+                loadDocFromSelectedIntegration();
+            }
+            return true;
+        }
         if (showNameInput) {
             if (ke.isCancel()) {
                 showNameInput = false;
@@ -143,6 +197,8 @@ class ActionsPopup {
                 navigateExampleBrowser(10);
             } else if (ke.isChar('r')) {
                 openNameInput();
+            } else if (ke.isChar('d')) {
+                loadDocFromExample();
             } else if (ke.isConfirm()) {
                 launchSelectedExample();
             }
@@ -157,11 +213,15 @@ class ActionsPopup {
                 actionsMenuState.selectNext(ACTION_COUNT);
             } else if (ke.isConfirm()) {
                 Integer sel = actionsMenuState.selected();
-                if (sel != null && sel == ACTION_SCREENSHOT) {
-                    showActionsMenu = false;
-                    screenshotAction.run();
-                } else {
-                    openExampleBrowser();
+                if (sel != null) {
+                    if (sel == ACTION_RUN_EXAMPLE) {
+                        openExampleBrowser();
+                    } else if (sel == ACTION_SHOW_DOCS) {
+                        openDocPicker();
+                    } else if (sel == ACTION_SCREENSHOT) {
+                        showActionsMenu = false;
+                        screenshotAction.run();
+                    }
                 }
             }
             return true;
@@ -179,9 +239,26 @@ class ActionsPopup {
         if (showNameInput) {
             renderNameInput(frame, area);
         }
+        if (showDocPicker) {
+            renderDocPicker(frame, area);
+        }
+        if (showDocViewer) {
+            renderDocViewer(frame, area);
+        }
     }
 
     void renderFooter(List<Span> spans) {
+        if (showDocViewer) {
+            hint(spans, "โ†‘โ†“", "scroll");
+            hintLast(spans, "Esc", "back");
+            return;
+        }
+        if (showDocPicker) {
+            hint(spans, "โ†‘โ†“", "navigate");
+            hint(spans, "Enter", "view");
+            hintLast(spans, "Esc", "back");
+            return;
+        }
         if (showNameInput) {
             hint(spans, "Enter", "launch");
             hintLast(spans, "Esc", "back");
@@ -190,7 +267,8 @@ class ActionsPopup {
         if (showExampleBrowser) {
             hint(spans, "โ†‘โ†“", "navigate");
             hint(spans, "Enter", "run");
-            hint(spans, "n", "name");
+            hint(spans, "r", "run...");
+            hint(spans, "d", "docs");
             hintLast(spans, "Esc", "back");
             return;
         }
@@ -220,6 +298,7 @@ class ActionsPopup {
         frame.renderWidget(Clear.INSTANCE, popup);
         ListWidget list = ListWidget.builder()
                 .items(ListItem.from("  Run an example..."),
+                        ListItem.from("  Show Documentation"),
                         ListItem.from("  Take Screenshot"))
                 .highlightStyle(Style.EMPTY.fg(Color.WHITE).bold().onBlue())
                 .highlightSymbol("")
@@ -256,6 +335,7 @@ class ActionsPopup {
                         .titleBottom(Title.from(Line.from(
                                 Span.styled(" Enter", 
MonitorContext.HINT_KEY_STYLE), Span.raw(" run โ”‚"),
                                 Span.styled(" r", 
MonitorContext.HINT_KEY_STYLE), Span.raw(" run... โ”‚"),
+                                Span.styled(" d", 
MonitorContext.HINT_KEY_STYLE), Span.raw(" docs โ”‚"),
                                 Span.styled(" โ†‘โ†“", 
MonitorContext.HINT_KEY_STYLE), Span.raw(" navigate โ”‚"),
                                 Span.styled(" Esc", 
MonitorContext.HINT_KEY_STYLE), Span.raw(" back "))))
                         .build())
@@ -279,7 +359,7 @@ class ActionsPopup {
             boolean bundled = ExampleHelper.isBundled(ex);
             boolean citrus = ExampleHelper.hasCitrusTests(ex);
 
-            String icons = (bundled ? "๐Ÿ“ฆ" : "๐ŸŒ") + (docker ? "๐Ÿณ" : "  ") + 
(citrus ? "๐Ÿงช" : "  ");
+            String icons = (bundled ? "๐Ÿ“ฆ" : "๐ŸŒ") + (docker ? "๐Ÿณ" : "  ") + 
(citrus ? "๐Ÿ‹" : "  ");
             int nameCol = Math.min(30, width / 3);
             String padded = String.format("%-" + nameCol + "s", 
TuiHelper.truncate(name, nameCol));
             String prefix = " " + icons + " " + padded + " ";
@@ -300,7 +380,7 @@ class ActionsPopup {
             }
         }
         items.add(ListItem.from(""));
-        items.add(ListItem.from(" ๐Ÿ“ฆ = bundled (offline)  ๐ŸŒ = online (GitHub)  
๐Ÿณ = Docker  ๐Ÿงช = Citrus tests")
+        items.add(ListItem.from(" ๐Ÿ“ฆ = bundled (offline)  ๐ŸŒ = online (GitHub)  
๐Ÿณ = Docker  ๐Ÿ‹ = Citrus tests")
                 .style(Style.EMPTY.dim()));
         return items;
     }
@@ -334,6 +414,159 @@ class ActionsPopup {
         frame.renderStatefulWidget(textInput, inputArea, nameInputState);
     }
 
+    // ---- Doc Viewer & Picker ----
+
+    private void renderDocViewer(Frame frame, Rect area) {
+        Rect popup = new Rect(area.left() + 2, area.top() + 1, area.width() - 
4, area.height() - 2);
+        frame.renderWidget(Clear.INSTANCE, popup);
+        MarkdownView view = MarkdownView.builder()
+                .source(docContent)
+                .scroll(docScroll)
+                .block(Block.builder()
+                        .borderType(BorderType.ROUNDED)
+                        .title(" " + docTitle + " ")
+                        .titleBottom(Title.from(Line.from(
+                                Span.styled(" โ†‘โ†“", 
MonitorContext.HINT_KEY_STYLE), Span.raw(" scroll โ”‚"),
+                                Span.styled(" Esc", 
MonitorContext.HINT_KEY_STYLE), Span.raw(" back "))))
+                        .build())
+                .build();
+        frame.renderWidget(view, popup);
+    }
+
+    private void renderDocPicker(Frame frame, Rect area) {
+        if (docPickerIntegrations == null || docPickerIntegrations.isEmpty()) {
+            return;
+        }
+        int popupW = Math.min(60, area.width() - 4);
+        int popupH = Math.min(docPickerIntegrations.size() + 2, Math.min(15, 
area.height() - 6));
+        int x = area.left() + Math.max(0, (area.width() - popupW) / 2);
+        int y = area.top() + Math.max(0, (area.height() - popupH) / 2);
+        Rect popup = new Rect(x, y, Math.min(popupW, area.width()), 
Math.min(popupH, area.height()));
+
+        frame.renderWidget(Clear.INSTANCE, popup);
+        List<ListItem> items = new ArrayList<>();
+        for (IntegrationInfo info : docPickerIntegrations) {
+            String label = "  " + (info.name != null ? info.name : info.pid);
+            items.add(ListItem.from(label));
+        }
+        ListWidget list = ListWidget.builder()
+                .items(items.toArray(ListItem[]::new))
+                .highlightStyle(Style.EMPTY.fg(Color.WHITE).bold().onBlue())
+                .highlightSymbol("")
+                .scrollMode(ScrollMode.AUTO_SCROLL)
+                .block(Block.builder()
+                        .borderType(BorderType.ROUNDED)
+                        .title(" Show Documentation ")
+                        .titleBottom(Title.from(Line.from(
+                                Span.styled(" Enter", 
MonitorContext.HINT_KEY_STYLE), Span.raw(" view โ”‚"),
+                                Span.styled(" Esc", 
MonitorContext.HINT_KEY_STYLE), Span.raw(" back "))))
+                        .build())
+                .build();
+        frame.renderStatefulWidget(list, popup, docPickerState);
+    }
+
+    private void openDocPicker() {
+        showActionsMenu = false;
+        List<IntegrationInfo> withDocs = integrations.get().stream()
+                .filter(i -> !i.vanishing && i.readmeFiles != null && 
!i.readmeFiles.isEmpty())
+                .collect(Collectors.toList());
+        if (withDocs.isEmpty()) {
+            launchNotification = "No integrations with documentation found";
+            launchNotificationError = true;
+            launchNotificationExpiry = System.currentTimeMillis() + 5000;
+            return;
+        }
+        if (withDocs.size() == 1) {
+            loadDocFromIntegration(withDocs.get(0));
+            return;
+        }
+        docPickerIntegrations = withDocs;
+        showDocPicker = true;
+        docPickerState.select(0);
+    }
+
+    private void loadDocFromSelectedIntegration() {
+        Integer sel = docPickerState.selected();
+        if (sel == null || docPickerIntegrations == null || sel >= 
docPickerIntegrations.size()) {
+            return;
+        }
+        IntegrationInfo info = docPickerIntegrations.get(sel);
+        loadDocFromIntegration(info);
+    }
+
+    private void loadDocFromIntegration(IntegrationInfo info) {
+        if (ctx == null) {
+            return;
+        }
+        showDocPicker = false;
+        try {
+            Path outputFile = ctx.getOutputFile(info.pid);
+            Files.deleteIfExists(outputFile);
+            JsonObject action = new JsonObject();
+            action.put("action", "readme");
+            PathUtils.writeTextSafely(action.toJson(), 
ctx.getActionFile(info.pid));
+            JsonObject response = MonitorContext.pollJsonResponse(outputFile, 
5000);
+            if (response != null && response.getString("content") != null) {
+                String raw = response.getString("content");
+                String file = response.getStringOrDefault("file", "README");
+                docContent = file.endsWith(".adoc") ? 
DocHelper.asciidocToMarkdown(raw) : raw;
+                docTitle = (info.name != null ? info.name : info.pid) + " - " 
+ Path.of(file).getFileName();
+                docScroll = 0;
+                showDocViewer = true;
+                docViewerFromExampleBrowser = false;
+            } else {
+                launchNotification = "Could not load documentation";
+                launchNotificationError = true;
+                launchNotificationExpiry = System.currentTimeMillis() + 5000;
+            }
+        } catch (Exception e) {
+            launchNotification = "Error loading documentation: " + 
e.getMessage();
+            launchNotificationError = true;
+            launchNotificationExpiry = System.currentTimeMillis() + 5000;
+        }
+    }
+
+    private void loadDocFromExample() {
+        Integer sel = exampleBrowserState.selected();
+        if (sel == null || isSeparatorIndex(sel)) {
+            return;
+        }
+        JsonObject example = getExampleAtListIndex(sel);
+        if (example == null) {
+            return;
+        }
+        String name = example.getStringOrDefault("name", "");
+        boolean bundled = ExampleHelper.isBundled(example);
+        String content = null;
+        boolean isAdoc = false;
+        if (bundled) {
+            content = DocHelper.loadResourceContent("examples/" + name + 
"/README.md");
+        } else {
+            String base = 
"https://raw.githubusercontent.com/apache/camel-jbang-examples/main/"; + name + 
"/";
+            content = DocHelper.downloadContent(base + "README.md");
+            if (content == null) {
+                content = DocHelper.downloadContent(base + "README.adoc");
+                isAdoc = content != null;
+            }
+        }
+        if (content != null && !content.isEmpty()) {
+            docContent = isAdoc ? DocHelper.asciidocToMarkdown(content) : 
content;
+            docTitle = name;
+            docScroll = 0;
+            showExampleBrowser = false;
+            showDocViewer = true;
+            docViewerFromExampleBrowser = true;
+        } else {
+            setNotification("No documentation available for: " + name, true);
+        }
+    }
+
+    private void setNotification(String msg, boolean error) {
+        launchNotification = msg;
+        launchNotificationError = error;
+        launchNotificationExpiry = System.currentTimeMillis() + 10000;
+    }
+
     // ---- Name Input ----
 
     private void openNameInput() {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
index 0bb354a258ac..413230648096 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
@@ -206,6 +206,9 @@ public class CamelMonitor extends CamelCommand {
                     .filter(i -> !i.vanishing && i.name != null)
                     .map(i -> i.name)
                     .collect(Collectors.toSet()),
+            () -> data.get().stream()
+                    .filter(i -> !i.vanishing)
+                    .collect(Collectors.toList()),
             () -> pendingScreenshot = true);
 
     private final AtomicBoolean refreshInProgress = new AtomicBoolean(false);
@@ -250,6 +253,7 @@ public class CamelMonitor extends CamelCommand {
 
         // Create shared context and tab instances
         ctx = new MonitorContext(data, infraData);
+        actionsPopup.setContext(ctx);
         logTab = new LogTab(ctx);
         routesTab = new RoutesTab(ctx);
         consumersTab = new ConsumersTab(ctx);
@@ -542,6 +546,9 @@ public class CamelMonitor extends CamelCommand {
             refreshTraceData(List.of(Long.parseLong(ctx.selectedPid)));
             historyTab.onTabSelected();
         }
+        if (tab == TAB_CIRCUIT_BREAKER) {
+            circuitBreakerTab.onTabSelected();
+        }
         tabsState.select(tab);
         return true;
     }
@@ -2367,6 +2374,7 @@ public class CamelMonitor extends CamelCommand {
         info.javaVersion = runtime != null ? runtime.getString("javaVersion") 
: null;
         info.javaVendor = runtime != null ? runtime.getString("javaVendor") : 
null;
         info.javaVmName = runtime != null ? runtime.getString("javaVmName") : 
null;
+        info.readmeFiles = runtime != null ? runtime.getString("readmeFiles") 
: null;
 
         Map<String, ?> stats = context.getMap("statistics");
         if (stats != null) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
index 1ffb49bd5685..bdbffcf13286 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
@@ -64,6 +64,14 @@ class CircuitBreakerTab implements MonitorTab {
         this.cbFailHistory = cbFailHistory;
     }
 
+    @Override
+    public void onTabSelected() {
+        IntegrationInfo info = ctx.findSelectedIntegration();
+        if (info != null && !info.circuitBreakers.isEmpty() && 
tableState.selected() == null) {
+            tableState.select(0);
+        }
+    }
+
     @Override
     public boolean handleKeyEvent(KeyEvent ke) {
         if (ke.isChar('s')) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DocHelper.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DocHelper.java
new file mode 100644
index 000000000000..009dcf92dcf3
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DocHelper.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.tui;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.camel.dsl.jbang.core.common.ExampleHelper;
+
+final class DocHelper {
+
+    private DocHelper() {
+    }
+
+    static String loadResourceContent(String resourcePath) {
+        try (InputStream is = 
ExampleHelper.class.getClassLoader().getResourceAsStream(resourcePath)) {
+            if (is != null) {
+                return new String(is.readAllBytes(), StandardCharsets.UTF_8);
+            }
+        } catch (IOException e) {
+            // ignore
+        }
+        return null;
+    }
+
+    static String downloadContent(String url) {
+        try (InputStream is = URI.create(url).toURL().openStream()) {
+            return new String(is.readAllBytes(), StandardCharsets.UTF_8);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    static String asciidocToMarkdown(String adoc) {
+        StringBuilder sb = new StringBuilder();
+        String[] lines = adoc.split("\n", -1);
+        String pendingLang = null;
+        boolean inCodeBlock = false;
+        for (String line : lines) {
+            if (!inCodeBlock && line.startsWith("[source")) {
+                int comma = line.indexOf(',');
+                int end = line.indexOf(']');
+                if (comma >= 0 && end > comma) {
+                    pendingLang = line.substring(comma + 1, end).trim();
+                } else {
+                    pendingLang = "";
+                }
+                continue;
+            }
+            if (line.equals("----")) {
+                if (inCodeBlock) {
+                    sb.append("```\n");
+                    inCodeBlock = false;
+                } else {
+                    sb.append("```").append(pendingLang != null ? pendingLang 
: "").append('\n');
+                    pendingLang = null;
+                    inCodeBlock = true;
+                }
+                continue;
+            }
+            if (inCodeBlock) {
+                sb.append(line).append('\n');
+                continue;
+            }
+            pendingLang = null;
+            if (line.startsWith("include::")) {
+                continue;
+            }
+            if (line.startsWith("=")) {
+                if (line.startsWith("==== ")) {
+                    sb.append("#### ").append(line.substring(5)).append('\n');
+                } else if (line.startsWith("=== ")) {
+                    sb.append("### ").append(line.substring(4)).append('\n');
+                } else if (line.startsWith("== ")) {
+                    sb.append("## ").append(line.substring(3)).append('\n');
+                } else if (line.startsWith("= ")) {
+                    sb.append("# ").append(line.substring(2)).append('\n');
+                } else {
+                    sb.append(line).append('\n');
+                }
+                continue;
+            }
+            String converted = line;
+            converted = convertImages(converted);
+            converted = convertLinks(converted);
+            sb.append(converted).append('\n');
+        }
+        if (inCodeBlock) {
+            sb.append("```\n");
+        }
+        return sb.toString();
+    }
+
+    private static String convertImages(String line) {
+        int idx = 0;
+        StringBuilder sb = new StringBuilder();
+        while (idx < line.length()) {
+            int imgStart = line.indexOf("image::", idx);
+            if (imgStart < 0) {
+                sb.append(line, idx, line.length());
+                break;
+            }
+            sb.append(line, idx, imgStart);
+            int bracketOpen = line.indexOf('[', imgStart);
+            int bracketClose = bracketOpen >= 0 ? line.indexOf(']', 
bracketOpen) : -1;
+            if (bracketOpen >= 0 && bracketClose >= 0) {
+                String file = line.substring(imgStart + 7, bracketOpen);
+                String alt = line.substring(bracketOpen + 1, bracketClose);
+                
sb.append("![").append(alt).append("](").append(file).append(')');
+                idx = bracketClose + 1;
+            } else {
+                sb.append("image::");
+                idx = imgStart + 7;
+            }
+        }
+        return sb.toString();
+    }
+
+    private static String convertLinks(String line) {
+        int idx = 0;
+        StringBuilder sb = new StringBuilder();
+        while (idx < line.length()) {
+            int linkStart = line.indexOf("link:", idx);
+            if (linkStart < 0) {
+                sb.append(line, idx, line.length());
+                break;
+            }
+            sb.append(line, idx, linkStart);
+            int bracketOpen = line.indexOf('[', linkStart);
+            int bracketClose = bracketOpen >= 0 ? line.indexOf(']', 
bracketOpen) : -1;
+            if (bracketOpen >= 0 && bracketClose >= 0) {
+                String url = line.substring(linkStart + 5, bracketOpen);
+                String text = line.substring(bracketOpen + 1, bracketClose);
+                
sb.append('[').append(text).append("](").append(url).append(')');
+                idx = bracketClose + 1;
+            } else {
+                sb.append("link:");
+                idx = linkStart + 5;
+            }
+        }
+        return sb.toString();
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/IntegrationInfo.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/IntegrationInfo.java
index 63f700c6eec0..921ab26ad816 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/IntegrationInfo.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/IntegrationInfo.java
@@ -64,4 +64,5 @@ class IntegrationInfo {
     final List<CircuitBreakerInfo> circuitBreakers = new ArrayList<>();
     final List<HttpEndpointInfo> httpEndpoints = new ArrayList<>();
     String httpServer;
+    String readmeFiles;
 }

Reply via email to