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

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

commit 29ead53d5a1630cae1c20beef74c762ec58268d3
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu May 21 15:07:19 2026 +0200

    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]>
---
 .../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  | 386 ++++++++++++++++++++-
 .../dsl/jbang/core/commands/tui/CamelMonitor.java  |   5 +
 .../jbang/core/commands/tui/IntegrationInfo.java   |   1 +
 16 files changed, 550 insertions(+), 319 deletions(-)

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..5a1958921028 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
@@ -17,6 +17,9 @@
 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 java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -24,8 +27,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 +52,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 +61,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 +81,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 +119,8 @@ class ActionsPopup {
         showActionsMenu = false;
         showExampleBrowser = false;
         showNameInput = false;
+        showDocPicker = false;
+        showDocViewer = false;
     }
 
     String notification() {
@@ -106,6 +132,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 +200,8 @@ class ActionsPopup {
                 navigateExampleBrowser(10);
             } else if (ke.isChar('r')) {
                 openNameInput();
+            } else if (ke.isChar('d')) {
+                loadDocFromExample();
             } else if (ke.isConfirm()) {
                 launchSelectedExample();
             }
@@ -157,11 +216,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 +242,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 +270,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 +301,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 +338,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())
@@ -334,6 +417,289 @@ 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") ? 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 = loadResourceContent("examples/" + name + "/README.md");
+        } else {
+            String base = 
"https://raw.githubusercontent.com/apache/camel-jbang-examples/main/"; + name + 
"/";
+            content = downloadContent(base + "README.md");
+            if (content == null) {
+                content = downloadContent(base + "README.adoc");
+                isAdoc = content != null;
+            }
+        }
+        if (content != null && !content.isEmpty()) {
+            docContent = isAdoc ? asciidocToMarkdown(content) : content;
+            docTitle = name;
+            docScroll = 0;
+            showExampleBrowser = false;
+            showDocViewer = true;
+            docViewerFromExampleBrowser = true;
+        } else {
+            setNotification("No documentation available for: " + name, true);
+        }
+    }
+
+    private 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;
+    }
+
+    private 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) {
+                // also handle bare URL[text] pattern
+                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();
+    }
+
+    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..172add2ed08c 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);
@@ -2367,6 +2371,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/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